diff --git a/Cargo.lock b/Cargo.lock index bcdc662..ffcd60b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -318,31 +318,15 @@ dependencies = [ [[package]] name = "crossterm" -version = "0.22.1" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c85525306c4291d1b73ce93c8acf9c339f9b213aef6c1d85c3830cbf1c16325c" +checksum = "a84cda67535339806297f1b331d6dd6320470d2a0fe65381e79ee9e156dd3d13" dependencies = [ "bitflags", "crossterm_winapi", "libc", - "mio 0.7.14", - "parking_lot 0.11.2", - "signal-hook 0.3.14", - "signal-hook-mio", - "winapi", -] - -[[package]] -name = "crossterm" -version = "0.23.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2102ea4f781910f8a5b98dd061f4c2023f479ce7bb1236330099ceb5a93cf17" -dependencies = [ - "bitflags", - "crossterm_winapi", - "libc", - "mio 0.8.3", - "parking_lot 0.12.1", + "mio", + "parking_lot", "signal-hook 0.3.14", "signal-hook-mio", "winapi", @@ -655,26 +639,26 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "hwatch" -version = "0.3.9" +version = "0.3.10" dependencies = [ "ansi-parser", "async-std", "chrono", "clap", "crossbeam-channel", - "crossterm 0.22.1", + "crossterm", "ctrlc", "difference", "futures", "heapless", "question", + "ratatui", "regex", "serde", "serde_derive", "serde_json", "shell-words", "termwiz", - "tui", ] [[package]] @@ -764,19 +748,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a64a92489e2744ce060c349162be1c5f33c6969234104dbd99ddb5feb08b8c15" -[[package]] -name = "mio" -version = "0.7.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc" -dependencies = [ - "libc", - "log", - "miow", - "ntapi", - "winapi", -] - [[package]] name = "mio" version = "0.8.3" @@ -789,15 +760,6 @@ dependencies = [ "windows-sys", ] -[[package]] -name = "miow" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" -dependencies = [ - "winapi", -] - [[package]] name = "nix" version = "0.24.3" @@ -829,15 +791,6 @@ dependencies = [ "version_check 0.9.4", ] -[[package]] -name = "ntapi" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c28774a7fd2fbb4f0babd8237ce554b73af68021b5f695a3cebd6c59bac0980f" -dependencies = [ - "winapi", -] - [[package]] name = "num-derive" version = "0.3.3" @@ -911,17 +864,6 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" -[[package]] -name = "parking_lot" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" -dependencies = [ - "instant", - "lock_api", - "parking_lot_core 0.8.5", -] - [[package]] name = "parking_lot" version = "0.12.1" @@ -929,21 +871,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ "lock_api", - "parking_lot_core 0.9.3", -] - -[[package]] -name = "parking_lot_core" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" -dependencies = [ - "cfg-if 1.0.0", - "instant", - "libc", - "redox_syscall", - "smallvec", - "winapi", + "parking_lot_core", ] [[package]] @@ -1112,6 +1040,19 @@ dependencies = [ "rand_core", ] +[[package]] +name = "ratatui" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcc0d032bccba900ee32151ec0265667535c230169f5a011154cdcd984e16829" +dependencies = [ + "bitflags", + "cassowary", + "crossterm", + "unicode-segmentation", + "unicode-width", +] + [[package]] name = "redox_syscall" version = "0.2.13" @@ -1253,8 +1194,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af" dependencies = [ "libc", - "mio 0.7.14", - "mio 0.8.3", + "mio", "signal-hook 0.3.14", ] @@ -1417,19 +1357,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "tui" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96fe69244ec2af261bced1d9046a6fee6c8c2a6b0228e59e5ba39bc8ba4ed729" -dependencies = [ - "bitflags", - "cassowary", - "crossterm 0.23.2", - "unicode-segmentation", - "unicode-width", -] - [[package]] name = "typenum" version = "1.15.0" @@ -1450,9 +1377,9 @@ checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee" [[package]] name = "unicode-segmentation" -version = "1.9.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" [[package]] name = "unicode-width" diff --git a/Cargo.toml b/Cargo.toml index 6c35e59..a465fc1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ keywords = ["watch", "command", "monitoring"] license-file = "LICENSE" name = "hwatch" repository = "https://github.com/blacknon/hwatch" -version = "0.3.9" +version = "0.3.10" [dependencies] # TODO: [ansi-parser](https://crates.io/crates/ansi-parser)のバージョンアップ対応が必要になるので、その対応待ち. PRはmargeされたのでいい加減アップデートされてもいいと思うのだが…. @@ -16,19 +16,21 @@ async-std = {version = "1.11"} chrono = "0.4.19" clap = {version = "3.1.18", features = ["cargo"]} crossbeam-channel = "0.5.4" -crossterm = "0.22" +crossterm = "0.26.1" ctrlc = {version = "3.0", features = ["termination"]} difference = "2.0" futures = "0.3.21" question = "0.2.2" + +# tui = {version = "0.19.0", default-features = false, features = ['crossterm']} +ratatui = {version = "0.20.1", default-features = false, features = ['crossterm']} + regex = "1.3.0" serde = "1.0.104" serde_derive = "1.0.104" serde_json = "1.0.44" shell-words = "1.1.0" termwiz = "0.15.0" -tui = {version = "0.18.0", default-features = false, features = ['crossterm']} + # TODO: ansi-parserのバージョンアップグレードの対応が必要になるので、その対応をしたらバージョンアップが必要. -# heapless = "0.7.13" -# heapless = "0.6.1" heapless = "0.5.6" diff --git a/README.md b/README.md index 7e4bb5e..57e36b1 100644 --- a/README.md +++ b/README.md @@ -40,92 +40,98 @@ That records the result of command execution and can display it history and diff ## Usage - hwatch 0.3.9 - blacknon - A modern alternative to the watch command, records the differences in execution results and can - check this differences at after. + hwatch 0.3.10 + blacknon + A modern alternative to the watch command, records the differences in execution results and can + check this differences at after. - USAGE: - hwatch [OPTIONS] ... + USAGE: + hwatch [OPTIONS] ... - ARGS: - ... + ARGS: + ... - OPTIONS: - -B, --beep - beep if command has a change result + OPTIONS: + -B, --beep + beep if command has a change result - --mouse - enable mouse wheel support. With this option, copying text with your terminal may be - harder. Try holding the Shift key. + --mouse + enable mouse wheel support. With this option, copying text with your terminal may be + harder. Try holding the Shift key. - -A, --aftercommand - Executes the specified command if the output changes. Information about changes is - stored in json format in environment variable ${HWATCH_DATA}. + --tab_size + Specifying tab display size [default: 4] - -c, --color - interpret ANSI color and style sequences + -A, --aftercommand + Executes the specified command if the output changes. Information about changes is + stored in json format in environment variable ${HWATCH_DATA}. - -d, --differences - highlight changes between updates + -c, --color + interpret ANSI color and style sequences - -t, --no-title - hide the UI on start. Use `t` to toggle it. + -d, --differences + highlight changes between updates - -N, --line-number - show line number + -t, --no-title + hide the UI on start. Use `t` to toggle it. - --no-help-banner - hide the "Display help with h key" message + -N, --line-number + show line number - -x, --exec - Run the command directly, not through the shell. Much like the `-x` option of the watch - command. + --no-help-banner + hide the "Display help with h key" message - -l, --logfile - logging file + -x, --exec + Run the command directly, not through the shell. Much like the `-x` option of the watch + command. - -s, --shell - shell to use at runtime. can also insert the command to the location specified by - {COMMAND}. [default: "sh -c"] + -l, --logfile + logging file - -n, --interval - seconds to wait between updates [default: 2] + -s, --shell + shell to use at runtime. can also insert the command to the location specified by + {COMMAND}. [default: "sh -c"] - -h, --help - Print help information + -n, --interval + seconds to wait between updates [default: 2] + + -h, --help + Print help information + + -V, --version + Print version information - -V, --version - Print version information watch window keybind -| Key | Action -|----------------|------------------------- -| , | move selected screen(history/watch). -| | select watch screen. -| | select history screen. -| H | show help window. -| C | toggle color. -| D | switch diff mode. -| N | switch line number display. -| T | toggle the UI (history pane and header). -| Backspace | toggle the history pane. -| Q | exit hwatch. -| 0 | disable diff. -| 1 | switch watch type diff. -| 2 | switch line type diff. -| 3 | switch word type diff. -| O | switch output mode(output->stdout->stderr). -| Shift+O | show only lines with differences(line/word diff mode only). -| F1 | only stdout print. -| F2 | only stderr print. -| F3 | print output. -| Tab | toggle select screen(history/watch). -| / | filter history by string. -| * | filter history by regex. -| Esc | unfiltering. +| Key | Action | +|-------------------------------|-------------------------------------------------------------| +| , | move selected screen(history/watch). | +| | select watch screen. | +| | select history screen. | +| H | show help window. | +| C | toggle color. | +| D | switch diff mode. | +| N | switch line number display. | +| T | toggle the UI (history pane and header). | +| Backspace | toggle the history pane. | +| Q | exit hwatch. | +| 0 | disable diff. | +| 1 | switch watch type diff. | +| 2 | switch line type diff. | +| 3 | switch word type diff. | +| O | switch output mode(output->stdout->stderr). | +| Shift+O | show only lines with differences(line/word diff mode only). | +| F1 | only stdout print. | +| F2 | only stderr print. | +| F3 | print output. | +| + | increase interval. | +| - | decrease interval. | +| Tab | toggle select screen(history/watch). | +| / | filter history by string. | +| * | filter history by regex. | +| Esc | unfiltering. | ## Configuration diff --git a/man/man.md b/man/man.md index f85442a..74f83d1 100644 --- a/man/man.md +++ b/man/man.md @@ -130,6 +130,13 @@ F3 : Display *Stdout* and *Stderr*. ++ + +: Increase interval by .5 seconds. + +- + +: Decrease interval by .5 seconds (As long as it's positive). Tab diff --git a/src/app.rs b/src/app.rs index c57a736..386915a 100644 --- a/src/app.rs +++ b/src/app.rs @@ -6,7 +6,7 @@ use crossbeam_channel::{Receiver, Sender}; // module -use crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers, MouseEvent, MouseEventKind}; +use crossterm::event::{Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers, MouseEvent, MouseEventKind, KeyEventState}; use regex::Regex; use std::{collections::HashMap, io}; use tui::{ @@ -19,14 +19,16 @@ use tui::{ use std::thread; // local module -use crate::common::logging_result; use crate::event::AppEvent; -use crate::exec::{exec_after_command,CommandResult}; +use crate::exec::{exec_after_command, CommandResult}; use crate::header::HeaderArea; use crate::help::HelpWindow; use crate::history::{History, HistoryArea}; use crate::output; use crate::watch::WatchArea; +use crate::common::logging_result; +use crate::Interval; +use crate::DEFAULT_TAB_SIZE; // local const use crate::HISTORY_WIDTH; @@ -120,6 +122,12 @@ pub struct App<'a> { /// results: HashMap, + /// + interval: Interval, + + /// + tab_size: u16, + /// header_area: HeaderArea<'a>, @@ -146,7 +154,7 @@ pub struct App<'a> { /// Trail at watch view window. impl<'a> App<'a> { /// - pub fn new(tx: Sender, rx: Receiver) -> Self { + pub fn new(tx: Sender, rx: Receiver, interval: Interval) -> Self { // method at create new view trail. Self { area: ActiveArea::History, @@ -169,8 +177,10 @@ impl<'a> App<'a> { is_only_diffline: false, results: HashMap::new(), + interval: interval.clone(), + tab_size: DEFAULT_TAB_SIZE, - header_area: HeaderArea::new(), + header_area: HeaderArea::new(*interval.read().unwrap()), history_area: HistoryArea::new(), watch_area: WatchArea::new(), @@ -276,6 +286,7 @@ impl<'a> App<'a> { false => 0, }, }; + let header_height: u16 = match self.show_header { true => 2, false => 0, @@ -283,8 +294,16 @@ impl<'a> App<'a> { // get Area's chunks let top_chunks = Layout::default() - .constraints([Constraint::Length(header_height), Constraint::Max(0)].as_ref()) + .constraints( + [ + Constraint::Length(header_height), + Constraint::Max(total_area.height - header_height), + ] + .as_ref(), + ) .split(total_area); + self.header_area.set_area(top_chunks[0]); + let main_chunks = Layout::default() .constraints( [ @@ -296,7 +315,6 @@ impl<'a> App<'a> { .direction(Direction::Horizontal) .split(top_chunks[1]); - self.header_area.set_area(top_chunks[0]); self.watch_area.set_area(main_chunks[0]); self.history_area.set_area(main_chunks[1]); } @@ -360,11 +378,16 @@ impl<'a> App<'a> { self.is_filtered, self.is_regex_filter, &self.filtered_text, + self.tab_size ), - DiffMode::Watch => { - output::get_watch_diff(self.ansi_color, self.line_number, text_src, text_dst) - } + DiffMode::Watch => output::get_watch_diff( + self.ansi_color, + self.line_number, + text_src, + text_dst, + self.tab_size + ), DiffMode::Line => output::get_line_diff( self.ansi_color, @@ -372,6 +395,7 @@ impl<'a> App<'a> { self.is_only_diffline, text_src, text_dst, + self.tab_size ), DiffMode::Word => output::get_word_diff( @@ -380,14 +404,17 @@ impl<'a> App<'a> { self.is_only_diffline, text_src, text_dst, + self.tab_size ), }; + // TODO: output_dataのtabをスペース展開する処理を追加 + self.watch_area.update_output(output_data); } /// - pub fn set_after_command(&mut self, command: String){ + pub fn set_after_command(&mut self, command: String) { self.after_command = command; } @@ -428,9 +455,30 @@ impl<'a> App<'a> { self.set_output_data(selected); } + pub fn set_tab_size(&mut self, tab_size: u16) { + self.tab_size = tab_size; + } + /// pub fn set_interval(&mut self, interval: f64) { - self.header_area.set_interval(interval); + let mut cur_interval = self.interval.write().unwrap(); + *cur_interval = interval; + self.header_area.set_interval(*cur_interval); + self.header_area.update(); + } + + /// + fn increase_interval(&mut self) { + let cur_interval = *self.interval.read().unwrap(); + self.set_interval(cur_interval + 0.5); + } + + /// + fn decrease_interval(&mut self) { + let cur_interval = *self.interval.read().unwrap(); + if cur_interval > 0.5 { + self.set_interval(cur_interval - 0.5); + } } /// @@ -561,9 +609,14 @@ impl<'a> App<'a> { let after_result = _result.clone(); { - thread::spawn(move|| { - let _ = exec_after_command("sh -c".to_string(),after_command.clone(),before_result,after_result); - }); + thread::spawn(move || { + exec_after_command( + "sh -c".to_string(), + after_command.clone(), + before_result, + after_result, + ); + }); } } @@ -626,12 +679,16 @@ impl<'a> App<'a> { Event::Key(KeyEvent { code: KeyCode::Up, modifiers: KeyModifiers::NONE, + kind: KeyEventKind::Press, + state: KeyEventState::NONE, }) => self.input_key_up(), // down Event::Key(KeyEvent { code: KeyCode::Down, modifiers: KeyModifiers::NONE, + kind: KeyEventKind::Press, + state: KeyEventState::NONE, }) => self.input_key_down(), // mouse wheel up @@ -667,108 +724,160 @@ impl<'a> App<'a> { Event::Key(KeyEvent { code: KeyCode::Left, modifiers: KeyModifiers::NONE, + kind: KeyEventKind::Press, + state: KeyEventState::NONE, }) => self.input_key_left(), // right Event::Key(KeyEvent { code: KeyCode::Right, modifiers: KeyModifiers::NONE, + kind: KeyEventKind::Press, + state: KeyEventState::NONE, }) => self.input_key_right(), // c Event::Key(KeyEvent { code: KeyCode::Char('c'), modifiers: KeyModifiers::NONE, + kind: KeyEventKind::Press, + state: KeyEventState::NONE, }) => self.set_ansi_color(!self.ansi_color), // d Event::Key(KeyEvent { code: KeyCode::Char('d'), modifiers: KeyModifiers::NONE, + kind: KeyEventKind::Press, + state: KeyEventState::NONE, }) => self.toggle_diff_mode(), // n Event::Key(KeyEvent { code: KeyCode::Char('n'), modifiers: KeyModifiers::NONE, + kind: KeyEventKind::Press, + state: KeyEventState::NONE, }) => self.set_line_number(!self.line_number), // o(lower o) Event::Key(KeyEvent { code: KeyCode::Char('o'), modifiers: KeyModifiers::NONE, + kind: KeyEventKind::Press, + state: KeyEventState::NONE, }) => self.toggle_output(), // O(upper o). shift + o Event::Key(KeyEvent { code: KeyCode::Char('O'), modifiers: KeyModifiers::SHIFT, + kind: KeyEventKind::Press, + state: KeyEventState::NONE, }) => self.set_is_only_diffline(!self.is_only_diffline), // 0 (DiffMode::Disable) Event::Key(KeyEvent { code: KeyCode::Char('0'), modifiers: KeyModifiers::NONE, + kind: KeyEventKind::Press, + state: KeyEventState::NONE, }) => self.set_diff_mode(DiffMode::Disable), // 1 (DiffMode::Watch) Event::Key(KeyEvent { code: KeyCode::Char('1'), modifiers: KeyModifiers::NONE, + kind: KeyEventKind::Press, + state: KeyEventState::NONE, }) => self.set_diff_mode(DiffMode::Watch), // 2 (DiffMode::Line) Event::Key(KeyEvent { code: KeyCode::Char('2'), modifiers: KeyModifiers::NONE, + kind: KeyEventKind::Press, + state: KeyEventState::NONE, }) => self.set_diff_mode(DiffMode::Line), // 3 (DiffMode::Word) Event::Key(KeyEvent { code: KeyCode::Char('3'), modifiers: KeyModifiers::NONE, + kind: KeyEventKind::Press, + state: KeyEventState::NONE, }) => self.set_diff_mode(DiffMode::Word), // F1 (OutputMode::Stdout) Event::Key(KeyEvent { code: KeyCode::F(1), modifiers: KeyModifiers::NONE, + kind: KeyEventKind::Press, + state: KeyEventState::NONE, }) => self.set_output_mode(OutputMode::Stdout), // F2 (OutputMode::Stderr) Event::Key(KeyEvent { code: KeyCode::F(2), modifiers: KeyModifiers::NONE, + kind: KeyEventKind::Press, + state: KeyEventState::NONE, }) => self.set_output_mode(OutputMode::Stderr), // F3 (OutputMode::Output) Event::Key(KeyEvent { code: KeyCode::F(3), modifiers: KeyModifiers::NONE, + kind: KeyEventKind::Press, + state: KeyEventState::NONE, }) => self.set_output_mode(OutputMode::Output), + // + Increase interval + Event::Key(KeyEvent { + code: KeyCode::Char('+'), + modifiers: KeyModifiers::NONE, + kind: KeyEventKind::Press, + state: KeyEventState::NONE, + }) => self.increase_interval(), + + // - Decrease interval + Event::Key(KeyEvent { + code: KeyCode::Char('-'), + modifiers: KeyModifiers::NONE, + kind: KeyEventKind::Press, + state: KeyEventState::NONE, + }) => self.decrease_interval(), + // Tab ... Toggle Area(Watch or History). Event::Key(KeyEvent { code: KeyCode::Tab, modifiers: KeyModifiers::NONE, + kind: KeyEventKind::Press, + state: KeyEventState::NONE, }) => self.toggle_area(), // / ... Change Filter Mode(plane text). Event::Key(KeyEvent { code: KeyCode::Char('/'), modifiers: KeyModifiers::NONE, + kind: KeyEventKind::Press, + state: KeyEventState::NONE, }) => self.set_input_mode(InputMode::Filter), // / ... Change Filter Mode(regex text). Event::Key(KeyEvent { code: KeyCode::Char('*'), modifiers: KeyModifiers::NONE, + kind: KeyEventKind::Press, + state: KeyEventState::NONE, }) => self.set_input_mode(InputMode::RegexFilter), // ESC ... Reset. Event::Key(KeyEvent { code: KeyCode::Esc, modifiers: KeyModifiers::NONE, + kind: KeyEventKind::Press, + state: KeyEventState::NONE, }) => { self.is_filtered = false; self.is_regex_filter = false; @@ -788,6 +897,8 @@ impl<'a> App<'a> { Event::Key(KeyEvent { code: KeyCode::Backspace, modifiers: KeyModifiers::NONE, + kind: KeyEventKind::Press, + state: KeyEventState::NONE, }) => self.show_history(!self.show_history), // Common input key @@ -795,6 +906,8 @@ impl<'a> App<'a> { Event::Key(KeyEvent { code: KeyCode::Char('t'), modifiers: KeyModifiers::NONE, + kind: KeyEventKind::Press, + state: KeyEventState::NONE, }) => self.show_ui(!self.show_header), // Common input key @@ -802,12 +915,16 @@ impl<'a> App<'a> { Event::Key(KeyEvent { code: KeyCode::Char('h'), modifiers: KeyModifiers::NONE, + kind: KeyEventKind::Press, + state: KeyEventState::NONE, }) => self.toggle_window(), // q ... exit hwatch. Event::Key(KeyEvent { code: KeyCode::Char('q'), modifiers: KeyModifiers::NONE, + kind: KeyEventKind::Press, + state: KeyEventState::NONE, }) => self .tx .send(AppEvent::Exit) @@ -817,6 +934,8 @@ impl<'a> App<'a> { Event::Key(KeyEvent { code: KeyCode::Char('c'), modifiers: KeyModifiers::CONTROL, + kind: KeyEventKind::Press, + state: KeyEventState::NONE, }) => self .tx .send(AppEvent::Exit) @@ -832,24 +951,32 @@ impl<'a> App<'a> { Event::Key(KeyEvent { code: KeyCode::Up, modifiers: KeyModifiers::NONE, + kind: KeyEventKind::Press, + state: KeyEventState::NONE, }) => self.input_key_up(), // down Event::Key(KeyEvent { code: KeyCode::Down, modifiers: KeyModifiers::NONE, + kind: KeyEventKind::Press, + state: KeyEventState::NONE, }) => self.input_key_down(), // h ... toggle help window. Event::Key(KeyEvent { code: KeyCode::Char('h'), modifiers: KeyModifiers::NONE, + kind: KeyEventKind::Press, + state: KeyEventState::NONE, }) => self.toggle_window(), // q ... exit hwatch. Event::Key(KeyEvent { code: KeyCode::Char('q'), modifiers: KeyModifiers::NONE, + kind: KeyEventKind::Press, + state: KeyEventState::NONE, }) => self .tx .send(AppEvent::Exit) @@ -859,6 +986,8 @@ impl<'a> App<'a> { Event::Key(KeyEvent { code: KeyCode::Char('c'), modifiers: KeyModifiers::CONTROL, + kind: KeyEventKind::Press, + state: KeyEventState::NONE, }) => self .tx .send(AppEvent::Exit) @@ -1057,14 +1186,14 @@ impl<'a> App<'a> { // NOTE: TODO: // Not currently used. // It will not be supported until the following issues are resolved. - // - https://github.com/fdehau/tui-rs/issues/495 + // - https://github.com/tui-rs-revival/ratatui/pull/12 /// //fn input_key_pgup(&mut self) {} // NOTE: TODO: // Not currently used. // It will not be supported until the following issues are resolved. - // - https://github.com/fdehau/tui-rs/issues/495 + // - https://github.com/tui-rs-revival/ratatui/pull/12 /// //fn input_key_pgdn(&mut self) {} @@ -1092,7 +1221,7 @@ impl<'a> App<'a> { // NOTE: TODO: Currently does not do anything // Mouse clicks will not be supported until the following issues are resolved. - // - https://github.com/fdehau/tui-rs/issues/495 + // - https://github.com/tui-rs-revival/ratatui/pull/12 fn mouse_click_left(&mut self, _column: u16, _row: u16) { // // check in hisotry area // let is_history_area = check_in_area(self.history_area.area, column, row); @@ -1108,6 +1237,9 @@ impl<'a> App<'a> { } } +// NOTE: TODO: +// Not currently used. +// - https://github.com/tui-rs-revival/ratatui/pull/12 //fn check_in_area(area: Rect, column: u16, row: u16) -> bool { // let mut result = true; // diff --git a/src/common.rs b/src/common.rs index a7ab5a5..9976274 100644 --- a/src/common.rs +++ b/src/common.rs @@ -11,6 +11,7 @@ use std::io::prelude::*; // local module use crate::exec::CommandResult; +/// pub fn now_str() -> String { let date = Local::now(); return date.format("%Y-%m-%d %H:%M:%S%.3f").to_string(); diff --git a/src/exec.rs b/src/exec.rs index f8fbdaf..d4f43cc 100644 --- a/src/exec.rs +++ b/src/exec.rs @@ -167,8 +167,8 @@ pub struct ExecuteAfterResultData { pub fn exec_after_command(shell_command: String, after_command: String, before_result: CommandResult, after_result: CommandResult) { let result_data = ExecuteAfterResultData { - before_result: before_result, - after_result: after_result, + before_result, + after_result, }; // create json_data @@ -232,7 +232,7 @@ fn create_exec_cmd_args(is_exec: bool, shell_command: String, command: String) - } } - return exec_commands; + exec_commands } diff --git a/src/header.rs b/src/header.rs index 08d777f..9b14b22 100644 --- a/src/header.rs +++ b/src/header.rs @@ -18,9 +18,6 @@ use tui::{ use crate::app::{ActiveArea, DiffMode, InputMode, OutputMode}; use crate::exec::CommandResult; -// local const -use crate::DEFAULT_INTERVAL; - //const const POSITION_X_HELP_TEXT: usize = 53; const WIDTH_TEXT_INTERVAL: usize = 19; @@ -78,11 +75,11 @@ pub struct HeaderArea<'a> { /// Header Area Object Trait impl<'a> HeaderArea<'a> { - pub fn new() -> Self { + pub fn new(interval: f64) -> Self { Self { area: tui::layout::Rect::new(0, 0, 0, 0), - interval: DEFAULT_INTERVAL, + interval, command: "".to_string(), timestamp: "".to_string(), diff --git a/src/help.rs b/src/help.rs index b7de3f4..63d1e28 100644 --- a/src/help.rs +++ b/src/help.rs @@ -74,6 +74,7 @@ fn gen_help_text<'a>() -> Vec> { Spans::from(" - [h] key ... show this help message."), // toggle Spans::from(" - [c] key ... toggle color mode."), + Spans::from(" - [n] key ... toggle line number."), Spans::from(" - [d] key ... switch diff mode at None, Watch, Line, and Word mode. "), Spans::from(" - [t] key ... toggle ui (history pane & header both on/off). "), Spans::from(" - [Bkspace] ... toggle history pane. "), @@ -88,6 +89,9 @@ fn gen_help_text<'a>() -> Vec> { Spans::from(" - [F1] key ... change output mode as stdout."), Spans::from(" - [F2] key ... change output mode as stderr."), Spans::from(" - [F3] key ... change output mode as output(stdout/stderr set.)"), + // change interval + Spans::from(" - [+] key ... Increase interval by .5 seconds."), + Spans::from(" - [-] key ... Decrease interval by .5 seconds."), // change use area Spans::from(" - [Tab] key ... toggle current area at history or watch."), // filter text inpu diff --git a/src/main.rs b/src/main.rs index 526c199..75bf6af 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,7 +2,7 @@ // Use of this source code is governed by an MIT license // that can be found in the LICENSE file. -// v0.3.10 +// v0.3.11 // TODO(blacknon): セキュリティのため、heaplessのバージョンを上げる // TODO(blakcnon): batch modeの実装. // TODO(blacknon): 任意時点間のdiffが行えるようにする. @@ -38,7 +38,7 @@ extern crate regex; extern crate serde; extern crate shell_words; extern crate termwiz; -extern crate tui; +extern crate ratatui as tui; // macro crate #[macro_use] @@ -53,6 +53,7 @@ use clap::{AppSettings, Arg, Command}; use question::{Answer, Question}; use std::env::args; use std::path::Path; +use std::sync::{Arc, RwLock}; // use std::sync::mpsc::channel; use crossbeam_channel::unbounded; use std::thread; @@ -73,8 +74,10 @@ mod watch; // const pub const DEFAULT_INTERVAL: f64 = 2.0; +pub const DEFAULT_TAB_SIZE: u16 = 4; pub const HISTORY_WIDTH: u16 = 25; pub const SHELL_COMMAND_EXECCMD: &str = "{COMMAND}"; +type Interval = Arc>; // const at Windows #[cfg(windows)] @@ -135,13 +138,20 @@ fn build_app() -> clap::Command<'static> { .short('B') .long("beep"), ) - // Beep option - // [-B,--beep] + // mouse option + // [--mouse] .arg( Arg::new("mouse") .help("enable mouse wheel support. With this option, copying text with your terminal may be harder. Try holding the Shift key.") .long("mouse"), ) + .arg( + Arg::new("tab_size") + .help("Specifying tab display size") + .long("tab_size") + .takes_value(true) + .default_value("4"), + ) // Option to specify the command to be executed when the output fluctuates. // [-C,--changed-command] .arg( @@ -287,29 +297,37 @@ fn main() { // Create channel let (tx, rx) = unbounded(); - let interval: f64 = matcher.value_of_t_or_exit("interval"); + let override_interval = matcher.value_of_t("interval").unwrap_or(DEFAULT_INTERVAL); + let interval = Interval::new(override_interval.into()); + + let tab_size = matcher.value_of_t("tab_size").unwrap_or(DEFAULT_TAB_SIZE); + // Start Command Thread { let m = matcher.clone(); let tx = tx.clone(); + let shell_command = m.value_of("shell_command").unwrap().to_string(); + let command = m.values_of_lossy("command").unwrap(); + let is_exec = m.is_present("exec"); + let interval = interval.clone(); let _ = thread::spawn(move || loop { // Create cmd.. let mut exe = exec::ExecuteCommand::new(tx.clone()); // Set shell command - exe.shell_command = m.value_of("shell_command").unwrap().to_string(); + exe.shell_command = shell_command.clone(); // Set command - exe.command = m.values_of_lossy("command").unwrap(); + exe.command = command.clone(); // Set is exec flag. - exe.is_exec = m.is_present("exec"); + exe.is_exec = is_exec; // Exec command exe.exec_command(); - // sleep interval - std::thread::sleep(Duration::from_secs_f64(interval)); + let sleep_interval = *interval.read().unwrap(); + std::thread::sleep(Duration::from_secs_f64(sleep_interval)); }); } @@ -317,9 +335,10 @@ fn main() { // if !batch { // is watch mode // Create view - let mut view = view::View::new() + let mut view = view::View::new(interval.clone()) // Set interval on view.header .set_interval(interval) + .set_tab_size(tab_size) .set_beep(matcher.is_present("beep")) .set_mouse_events(matcher.is_present("mouse")) // Set color in view diff --git a/src/output.rs b/src/output.rs index eb4e614..78a23d9 100644 --- a/src/output.rs +++ b/src/output.rs @@ -12,23 +12,83 @@ use tui::{ style::{Color, Modifier, Style}, text::{Span, Spans}, }; +use std::borrow::Cow; +use std::fmt::Write; + // local const use crate::ansi; use crate::LINE_ENDING; +pub trait StringExt { + fn expand_tabs(&self, tab_size: u16) -> Cow; +} + +impl StringExt for T +where + T: AsRef, +{ + fn expand_tabs(&self, tab_size: u16) -> Cow { + let s = self.as_ref(); + let tab = '\t'; + + if s.contains(tab) { + let mut res = String::new(); + let mut last_pos = 0; + + while let Some(pos) = &s[last_pos..].find(tab) { + res.push_str(&s[last_pos..*pos + last_pos]); + + let spaces_to_add = if tab_size != 0 { + tab_size - (*pos as u16 % tab_size) + } else { + 0 + }; + + if spaces_to_add != 0 { + let _ = write!(res, "{:width$}", "", width = spaces_to_add as usize); + } + + last_pos += *pos + 1; + } + + res.push_str(&s[last_pos..]); + + Cow::from(res) + } else { + Cow::from(s) + } + } +} + +/// +fn expand_line_tab(data: &str, tab_size: u16) -> String { + let mut result_vec: Vec = vec![]; + for d in data.lines() { + let l = d.expand_tabs(tab_size).to_string(); + result_vec.push(l); + } + + let rslt = result_vec.join("\n"); + + return rslt + +} + // plane output // ========== - /// pub fn get_plane_output<'a>( color: bool, line_number: bool, - text: &str, + base_text: &str, is_filter: bool, is_regex_filter: bool, filtered_text: &str, + tab_size: u16, ) -> Vec> { + let text = &expand_line_tab(base_text, tab_size); + // set result `output_data`. let mut output_data = vec![]; @@ -169,12 +229,16 @@ pub fn get_plane_output<'a>( // ========== /// -pub fn get_watch_diff<'a>(color: bool, line_number: bool, old: &str, new: &str) -> Vec> { +pub fn get_watch_diff<'a>(color: bool, line_number: bool, old: &str, new: &str, tab_size: u16) -> Vec> { + // + let old_text = &expand_line_tab(old, tab_size); + let new_text = &expand_line_tab(new, tab_size); + let mut result = vec![]; // output to vector - let mut old_vec: Vec<&str> = old.lines().collect(); - let mut new_vec: Vec<&str> = new.lines().collect(); + let mut old_vec: Vec<&str> = old_text.lines().collect(); + let mut new_vec: Vec<&str> = new_text.lines().collect(); // get max line let max_line = cmp::max(old_vec.len(), new_vec.len()); @@ -192,9 +256,13 @@ pub fn get_watch_diff<'a>(color: bool, line_number: bool, old: &str, new: &str) new_vec.push(""); } + let old_line = old_vec[i]; + let new_line = new_vec[i]; + + let mut line_data = match color { - false => get_watch_diff_line(old_vec[i], new_vec[i]), - true => get_watch_diff_line_with_ansi(old_vec[i], new_vec[i]), + false => get_watch_diff_line(&old_line, &new_line), + true => get_watch_diff_line_with_ansi(&old_line, &new_line), }; if line_number { @@ -226,9 +294,9 @@ fn get_watch_diff_line<'a>(old_line: &str, new_line: &str) -> Spans<'a> { let mut old_line_chars: Vec = old_line.chars().collect(); let mut new_line_chars: Vec = new_line.chars().collect(); - // 007f ... delete char. - // NOTE: Use hidden characters to branch processing because tui-rs skips space characters. - let space: char = '\u{007f}'; + // 00a0 ... non-breaking space. + // NOTE: Used because tui-rs skips regular space characters. + let space: char = '\u{00a0}'; let max_char = cmp::max(old_line_chars.len(), new_line_chars.len()); let mut _result = vec![]; @@ -267,7 +335,7 @@ fn get_watch_diff_line<'a>(old_line: &str, new_line: &str) -> Spans<'a> { } // last char - // NOTE: Added hidden characters as tui-rs forces trimming of end-of-line spaces. + // NOTE: NBSP used as tui-rs trims regular spaces. _result.push(Span::styled(space.to_string(), Style::default())); Spans::from(_result) @@ -296,9 +364,9 @@ fn get_watch_diff_line_with_ansi<'a>(old_line: &str, new_line: &str) -> Spans<'a // break; } - // 007f ... delete char. - // NOTE: Use hidden characters to branch processing because tui-rs skips space characters. - let space = '\u{007f}'.to_string(); + // 00a0 ... non-breaking space. + // NOTE: Used because tui-rs skips regular space characters. + let space = '\u{00a0}'.to_string(); let max_span = cmp::max(old_spans.len(), new_spans.len()); // let mut _result = vec![]; @@ -350,13 +418,17 @@ pub fn get_line_diff<'a>( is_only_diffline: bool, old: &str, new: &str, + tab_size: u16 ) -> Vec> { + let old_text = &expand_line_tab(old, tab_size); + let new_text = &expand_line_tab(new, tab_size); + // Create changeset - let Changeset { diffs, .. } = Changeset::new(old, new, LINE_ENDING); + let Changeset { diffs, .. } = Changeset::new(&old_text, &new_text, LINE_ENDING); // old and new text's line count. - let old_len = &old.lines().count(); - let new_len = &new.lines().count(); + let old_len = &old_text.lines().count(); + let new_len = &new_text.lines().count(); // get line_number width let header_width = cmp::max(old_len, new_len).to_string().chars().count(); @@ -372,7 +444,8 @@ pub fn get_line_diff<'a>( match diffs[i] { // Same line. Difference::Same(ref diff_data) => { - for line in diff_data.lines() { + for l in diff_data.lines() { + let line = l.expand_tabs(tab_size); let mut data = if color { // ansi color code => rs-tui colored span. let mut colored_span = vec![Span::from(" ")]; @@ -410,10 +483,11 @@ pub fn get_line_diff<'a>( // Add line. Difference::Add(ref diff_data) => { - for line in diff_data.lines() { + for l in diff_data.lines() { + let line = l.expand_tabs(tab_size); let mut data = if color { // ansi color code => parse and delete. to rs-tui span(green). - let strip_str = get_ansi_strip_str(line); + let strip_str = get_ansi_strip_str(&line); Spans::from(Span::styled( format!("+ {strip_str}\n"), Style::default().fg(Color::Green), @@ -445,10 +519,11 @@ pub fn get_line_diff<'a>( // Remove line. Difference::Rem(ref diff_data) => { - for line in diff_data.lines() { + for l in diff_data.lines() { + let line = l.expand_tabs(tab_size); let mut data = if color { // ansi color code => parse and delete. to rs-tui span(green). - let strip_str = get_ansi_strip_str(line); + let strip_str = get_ansi_strip_str(&line); Spans::from(Span::styled( format!("- {strip_str}\n"), Style::default().fg(Color::Red), @@ -493,13 +568,17 @@ pub fn get_word_diff<'a>( is_only_diffline: bool, old: &str, new: &str, + tab_size: u16 ) -> Vec> { + let old_text = &expand_line_tab(old, tab_size); + let new_text = &expand_line_tab(new, tab_size); + // Create changeset - let Changeset { diffs, .. } = Changeset::new(old, new, LINE_ENDING); + let Changeset { diffs, .. } = Changeset::new(&old_text, &new_text, LINE_ENDING); // old and new text's line count. - let old_len = &old.lines().count(); - let new_len = &new.lines().count(); + let old_len = &old_text.lines().count(); + let new_len = &new_text.lines().count(); // get line_number width let header_width = cmp::max(old_len, new_len).to_string().chars().count(); @@ -515,7 +594,8 @@ pub fn get_word_diff<'a>( match diffs[i] { // Same line. Difference::Same(ref diff_data) => { - for line in diff_data.lines() { + for l in diff_data.lines() { + let line = l.expand_tabs(tab_size); let mut data = if color { // ansi color code => rs-tui colored span. let mut colored_span = vec![Span::from(" ")]; @@ -571,9 +651,10 @@ pub fn get_word_diff<'a>( lines_data = get_word_diff_addline(color, before_diffs, diff_data.to_string()) } else { - for line in diff_data.lines() { + for l in diff_data.lines() { + let line = l.expand_tabs(tab_size); let data = if color { - get_ansi_strip_str(line) + get_ansi_strip_str(&line) } else { line.to_string() }; diff --git a/src/view.rs b/src/view.rs index de6764b..0775e2e 100644 --- a/src/view.rs +++ b/src/view.rs @@ -9,7 +9,7 @@ use crossterm::{ execute, terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, }; -use std::{error::Error, io}; +use std::{error::Error, io, sync::{Arc, RwLock}}; use tui::{backend::CrosstermBackend, Terminal}; // local module @@ -17,13 +17,15 @@ use crate::app::{App, DiffMode}; use crate::event::AppEvent; // local const -use crate::DEFAULT_INTERVAL; +use crate::Interval; +use crate::DEFAULT_TAB_SIZE; /// Struct at run hwatch on tui #[derive(Clone)] pub struct View { after_command: String, - interval: f64, + interval: Interval, + tab_size: u16, beep: bool, mouse_events: bool, color: bool, @@ -36,10 +38,11 @@ pub struct View { /// impl View { - pub fn new() -> Self { + pub fn new(interval: Interval) -> Self { Self { after_command: "".to_string(), - interval: DEFAULT_INTERVAL, + interval, + tab_size: DEFAULT_TAB_SIZE, beep: false, mouse_events: false, color: false, @@ -56,11 +59,16 @@ impl View { self } - pub fn set_interval(mut self, interval: f64) -> Self { + pub fn set_interval(mut self, interval: Arc>) -> Self { self.interval = interval; self } + pub fn set_tab_size(mut self, tab_size: u16) -> Self { + self.tab_size = tab_size; + self + } + pub fn set_beep(mut self, beep: bool) -> Self { self.beep = beep; self @@ -131,14 +139,11 @@ impl View { } // Create App - let mut app = App::new(tx, rx); + let mut app = App::new(tx, rx, self.interval.clone()); // set after command app.set_after_command(self.after_command.clone()); - // set interval - app.set_interval(self.interval); - // set beep app.set_beep(self.beep); @@ -152,6 +157,8 @@ impl View { app.show_ui(self.show_ui); app.show_help_banner(self.show_help_banner); + app.set_tab_size(self.tab_size); + // set line_number app.set_line_number(self.line_number);