Skip to content

Commit a571596

Browse files
committed
Support 3 kinds of alert messages
1 parent d668772 commit a571596

File tree

8 files changed

+108
-88
lines changed

8 files changed

+108
-88
lines changed

src/app/config/default_config.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ open_selected_file_template = "pcmanfm %s"
1818
1919
[theme]
2020
alert_bg = "#373424"
21-
alert_fg = "#CCC8B0"
21+
alert_fg = "#9f8800"
2222
2323
alert_error_bg = "#373424"
2424
alert_error_fg = "#DC322F"
@@ -35,7 +35,7 @@ header_bg = "#373424"
3535
header_fg = "#CCC8B0"
3636
3737
help_bg = "#373424"
38-
help_fg = "#CCC8B0"
38+
help_fg = "#9C9977"
3939
4040
notice_clipboard_bg = "#70C0B1"
4141
notice_clipboard_fg = "#1D1F21"

src/file_system.rs

Lines changed: 14 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use crate::{
1616
};
1717

1818
pub struct FileSystem {
19-
directory: PathInfo,
19+
directory: Option<PathInfo>,
2020
open_current_directory_template: Option<String>,
2121
open_new_window_template: Option<String>,
2222
open_selected_file_template: Option<String>,
@@ -27,7 +27,7 @@ pub struct FileSystem {
2727
impl FileSystem {
2828
pub fn new(config: &Config) -> Self {
2929
Self {
30-
directory: PathInfo::default(),
30+
directory: None,
3131
open_current_directory_template: config.open_current_directory_template.clone(),
3232
open_new_window_template: config.open_new_window_template.clone(),
3333
open_selected_file_template: config.open_selected_file_template.clone(),
@@ -58,31 +58,22 @@ impl FileSystem {
5858
}
5959

6060
fn back(&mut self) -> CommandResult {
61-
match self.directory.parent() {
61+
let directory = self.directory.as_ref().unwrap();
62+
match directory.parent() {
6263
Some(parent) => self.cd(parent),
6364
None => CommandResult::none(),
6465
}
6566
}
66-
6767
fn cd(&mut self, directory: PathInfo) -> CommandResult {
68-
// Unwatch the current directory if we're watching it
69-
if let Some(watcher) = &mut self.watcher {
70-
if let Err(e) = watcher.unwatch_directory(PathBuf::from(&self.directory.path)) {
71-
error!("Failed to unwatch directory: {}", e);
72-
}
73-
}
68+
let watcher = self.watcher.as_mut().expect("Watcher not initialized");
7469

7570
(match sync::cd(&directory) {
7671
Ok(children) => {
77-
self.directory = directory.clone();
72+
self.directory = Some(directory.clone());
7873

79-
// Watch the new directory
80-
if let Some(watcher) = &mut self.watcher {
81-
if let Err(e) = watcher.watch_directory(PathBuf::from(&directory.path)) {
82-
error!("Failed to watch directory: {}", e);
83-
}
74+
if let Err(e) = watcher.watch_directory(PathBuf::from(&directory.path)) {
75+
error!("Failed to watch directory: {}", e);
8476
}
85-
8677
Command::SetDirectory(directory, children)
8778
}
8879
Err(error) => anyhow!("Failed to change to directory {directory:?}: {error}").into(),
@@ -111,9 +102,10 @@ impl FileSystem {
111102
}
112103

113104
fn open_current_directory(&self) -> CommandResult {
105+
let directory = self.directory.as_ref().unwrap();
114106
open_in(
115107
self.open_current_directory_template.clone(),
116-
&self.directory.path,
108+
&directory.path,
117109
)
118110
.map_or_else(|error| error.into(), |_| CommandResult::none())
119111
}
@@ -124,7 +116,8 @@ impl FileSystem {
124116
}
125117

126118
fn open_new_window(&self) -> CommandResult {
127-
open_in(self.open_new_window_template.clone(), &self.directory.path)
119+
let directory = self.directory.as_ref().unwrap();
120+
open_in(self.open_new_window_template.clone(), &directory.path)
128121
.map_or_else(|error| error.into(), |_| CommandResult::none())
129122
}
130123

@@ -138,6 +131,7 @@ impl FileSystem {
138131
}
139132

140133
fn refresh(&mut self) -> CommandResult {
141-
self.cd(self.directory.clone())
134+
let directory = self.directory.as_ref().unwrap();
135+
self.cd(directory.clone())
142136
}
143137
}

src/file_system/watcher.rs

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use crate::command::Command;
1212

1313
pub struct DirectoryWatcher {
1414
watcher: RecommendedWatcher,
15+
currently_watched: Option<PathBuf>,
1516
}
1617

1718
impl DirectoryWatcher {
@@ -21,17 +22,22 @@ impl DirectoryWatcher {
2122
let watcher = recommended_watcher(tx)?;
2223

2324
thread::spawn(move || background_watcher(command_tx, rx));
24-
Ok(Self { watcher })
25+
Ok(Self {
26+
watcher,
27+
currently_watched: None,
28+
})
2529
}
2630

2731
pub fn watch_directory(&mut self, path: PathBuf) -> Result<()> {
32+
if let Some(old_path) = &self.currently_watched {
33+
if let Err(e) = self.watcher.unwatch(old_path.as_path()) {
34+
error!("Failed to unwatch directory: {}", e);
35+
}
36+
}
37+
2838
self.watcher
2939
.watch(path.as_path(), notify::RecursiveMode::NonRecursive)?;
30-
Ok(())
31-
}
32-
33-
pub fn unwatch_directory(&mut self, path: PathBuf) -> Result<()> {
34-
self.watcher.unwatch(path.as_path())?;
40+
self.currently_watched = Some(path);
3541
Ok(())
3642
}
3743
}
@@ -53,8 +59,10 @@ fn background_watcher(
5359
_ => {}
5460
},
5561
Err(e) => {
56-
let error_command =
57-
Command::AlertError(format!("Failed to watch directory: {}", e));
62+
let error_command = Command::AlertError(format!(
63+
"Failed to run the directory watcher in the background: {}",
64+
e
65+
));
5866
if let Err(e) = command_tx.send(error_command) {
5967
error!("Failed to send error command: {}", e);
6068
}

src/views/alerts.rs

Lines changed: 46 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ use crate::{
1616
};
1717

1818
const MAX_NUMBER_ALERTS: usize = 5;
19+
const MIN_HEIGHT: u16 = 2;
1920

2021
#[derive(Clone, Debug, Eq, PartialEq)]
2122
enum AlertKind {
@@ -24,29 +25,28 @@ enum AlertKind {
2425
Error,
2526
}
2627

28+
impl AlertKind {
29+
fn to_style(&self, theme: &Theme) -> Style {
30+
match self {
31+
AlertKind::Info => theme.alert_info(),
32+
AlertKind::Warn => theme.alert_warning(),
33+
AlertKind::Error => theme.alert_error(),
34+
}
35+
}
36+
}
37+
2738
#[derive(Default)]
2839
pub(super) struct AlertsView {
2940
alerts: VecDeque<(AlertKind, String)>,
3041
area: Rect,
3142
}
3243

3344
impl AlertsView {
34-
pub(super) fn height(&self, width: u16) -> u16 {
35-
if self.should_show() {
36-
// TODO cache `self.list_items()` result for use in render()
37-
let width = width.saturating_sub(2); // -2 for horizontal borders
38-
let items = self.list_items(width);
39-
items.len() as u16 + 2 // +2 for vertical borders
40-
} else {
41-
0
42-
}
43-
}
44-
4545
fn add_alert(&mut self, kind: AlertKind, message: String) -> CommandResult {
4646
if self.alerts.len() == MAX_NUMBER_ALERTS {
47-
self.alerts.pop_front();
47+
self.alerts.pop_back();
4848
}
49-
self.alerts.push_back((kind, message));
49+
self.alerts.push_front((kind, message));
5050
CommandResult::none()
5151
}
5252

@@ -55,32 +55,35 @@ impl AlertsView {
5555
CommandResult::none()
5656
}
5757

58-
fn list_items(&self, width: u16) -> Vec<(Line<'_>, AlertKind)> {
58+
fn height(&self, area: &Rect) -> u16 {
59+
if !self.should_show(area) {
60+
return 0;
61+
}
62+
// First subtract borders from the outer area
63+
let inner_width = area.width.saturating_sub(2);
64+
let items = self.alerts(inner_width);
65+
items.len() as u16 + 2 // +2 for vertical borders
66+
}
67+
68+
fn alerts(&self, width_without_borders: u16) -> Vec<(AlertKind, Line<'_>)> {
69+
let width_without_prefix = width_without_borders.saturating_sub(2);
70+
5971
self.alerts
6072
.iter()
61-
.rev() // Newest alert messages near the top
6273
.flat_map(|(kind, message)| {
63-
split_with_ellipsis(message, width.saturating_sub(2))
74+
split_with_ellipsis(message, width_without_prefix)
6475
.into_iter()
6576
.enumerate()
6677
.map(|(i, line)| {
6778
let prefix = if i == 0 { "•" } else { " " };
68-
(Line::from(format!("{prefix} {line}")), kind.clone())
79+
(kind.clone(), Line::from(format!("{prefix} {line}")))
6980
})
7081
})
7182
.collect()
7283
}
7384

74-
fn should_show(&self) -> bool {
75-
!self.alerts.is_empty()
76-
}
77-
78-
fn get_style(&self, kind: &AlertKind, theme: &Theme) -> Style {
79-
match kind {
80-
AlertKind::Info => theme.alert_info(),
81-
AlertKind::Warn => theme.alert_warning(),
82-
AlertKind::Error => theme.alert_error(),
83-
}
85+
fn should_show(&self, area: &Rect) -> bool {
86+
!self.alerts.is_empty() && area.height >= MIN_HEIGHT
8487
}
8588
}
8689

@@ -103,7 +106,6 @@ impl CommandHandler for AlertsView {
103106
fn handle_mouse(&mut self, event: &MouseEvent) -> CommandResult {
104107
match event.kind {
105108
MouseEventKind::Down(MouseButton::Left) => {
106-
// `self.should_receive_mouse()` guards this method to ensure that the click intersects with this view.
107109
self.clear_alerts();
108110
CommandResult::none()
109111
}
@@ -118,25 +120,27 @@ impl CommandHandler for AlertsView {
118120

119121
impl View for AlertsView {
120122
fn constraint(&self, area: Rect, _: &InputMode) -> Constraint {
121-
Constraint::Length(self.height(area.width))
123+
Constraint::Length(self.height(&area))
122124
}
123125

124126
fn render(&mut self, area: Rect, buf: &mut Buffer, _: &InputMode, theme: &Theme) {
125-
self.area = area;
126-
if !self.should_show() {
127+
if !self.should_show(&area) {
127128
return;
128129
}
129-
130-
let bordered_area = bordered(buf, area, theme.alert(), Some("Alerts".into()));
131-
let items = self.list_items(bordered_area.width);
132-
let mut text: Text<'_> = Text::default();
133-
134-
for (line, kind) in items {
135-
let style = self.get_style(&kind, theme);
136-
text.lines.push(Line::from(line.spans).style(style));
137-
}
138-
139-
let widget = Paragraph::new(text);
130+
self.area = area;
131+
let bordered_area = bordered(
132+
buf,
133+
area,
134+
theme.alert(),
135+
Some("Alerts (Press \"a\" to clear)".into()),
136+
);
137+
let text = Text::from(
138+
self.alerts(bordered_area.width)
139+
.into_iter()
140+
.map(|(kind, line)| line.style(kind.to_style(theme)))
141+
.collect::<Vec<_>>(),
142+
);
143+
let widget = Paragraph::new(text).style(theme.alert());
140144
widget.render(bordered_area, buf);
141145
}
142146
}

src/views/help.rs

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use ratatui::crossterm::event::{KeyCode, KeyModifiers};
1+
use ratatui::crossterm::event::{KeyCode, KeyModifiers, MouseButton, MouseEvent, MouseEventKind};
22
use ratatui::{
33
buffer::Buffer,
44
layout::{Constraint, Rect},
@@ -13,33 +13,50 @@ use crate::{
1313
command::{handler::CommandHandler, mode::InputMode, result::CommandResult},
1414
};
1515

16+
const MIN_HEIGHT: u16 = 2;
17+
1618
#[derive(Default)]
1719
pub(super) struct HelpView {
18-
should_show: bool,
20+
area: Rect,
21+
is_visible: bool,
1922
}
2023

2124
impl HelpView {
2225
pub(super) fn height(&self) -> u16 {
23-
if self.should_show {
24-
4 // 2 + 2 for borders
26+
if self.is_visible {
27+
4 // 2 lines of text + 2 borders
2528
} else {
2629
0
2730
}
2831
}
2932

30-
fn toggle_show_help(&mut self) -> CommandResult {
31-
self.should_show = !self.should_show;
33+
fn toggle_visibility(&mut self) -> CommandResult {
34+
self.is_visible = !self.is_visible;
3235
CommandResult::none()
3336
}
3437
}
3538

3639
impl CommandHandler for HelpView {
3740
fn handle_key(&mut self, code: &KeyCode, modifiers: &KeyModifiers) -> CommandResult {
3841
match (*code, *modifiers) {
39-
(KeyCode::Char('?'), KeyModifiers::NONE) => self.toggle_show_help(),
42+
(KeyCode::Char('?'), KeyModifiers::NONE) => self.toggle_visibility(),
4043
(_, _) => CommandResult::NotHandled,
4144
}
4245
}
46+
47+
fn handle_mouse(&mut self, event: &MouseEvent) -> CommandResult {
48+
match event.kind {
49+
MouseEventKind::Down(MouseButton::Left) => {
50+
self.is_visible = false;
51+
CommandResult::none()
52+
}
53+
_ => CommandResult::none(),
54+
}
55+
}
56+
57+
fn should_receive_mouse(&self, x: u16, y: u16) -> bool {
58+
self.is_visible && self.area.intersects(Rect::new(x, y, 1, 1))
59+
}
4360
}
4461

4562
impl View for HelpView {
@@ -48,11 +65,13 @@ impl View for HelpView {
4865
}
4966

5067
fn render(&mut self, area: Rect, buf: &mut Buffer, mode: &InputMode, theme: &Theme) {
51-
if !self.should_show {
68+
if !self.is_visible || area.height < MIN_HEIGHT {
5269
return;
5370
}
71+
self.area = area;
72+
5473
let style = theme.help();
55-
let bordered_rect = bordered(buf, area, style, Some("Help".into()));
74+
let bordered_rect = bordered(buf, area, style, Some("Help (Press \"?\" to close)".into()));
5675
let spans = match *mode {
5776
InputMode::Prompt => prompt_help(),
5877
_ => content_help(),

src/views/notices.rs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,6 @@ impl NoticesView {
5656
CommandResult::none()
5757
}
5858

59-
pub(super) fn clear_filter(&mut self) -> CommandResult {
60-
self.filter.clear();
61-
CommandResult::none()
62-
}
63-
6459
pub(super) fn set_clipboard(
6560
&mut self,
6661
path: PathInfo,

0 commit comments

Comments
 (0)