From c1b652238a770934d8116837badadca2cbf488a0 Mon Sep 17 00:00:00 2001 From: blacknon Date: Tue, 5 Mar 2024 05:14:26 +0900 Subject: [PATCH 01/16] update. create branch --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d08dbbe..521663a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -790,7 +790,7 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "hwatch" -version = "0.3.11" +version = "0.3.12" dependencies = [ "async-std", "chrono", diff --git a/Cargo.toml b/Cargo.toml index 6426dca..c28b6ec 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.11" +version = "0.3.12" [dependencies] # TODO: ansi-parserが正式にバージョンアップしたらそちらに切り替える From bfc9318f65521c55d6d12d8593d216896e101e7c Mon Sep 17 00:00:00 2001 From: blacknon Date: Tue, 5 Mar 2024 05:39:32 +0900 Subject: [PATCH 02/16] update. pageup and pagedown key support --- src/app.rs | 60 ++++++++++++++++++++++++++++++++++++++++------------ src/watch.rs | 9 ++++++++ 2 files changed, 55 insertions(+), 14 deletions(-) diff --git a/src/app.rs b/src/app.rs index 7cb66b9..6e083f0 100644 --- a/src/app.rs +++ b/src/app.rs @@ -720,6 +720,22 @@ impl<'a> App<'a> { state: KeyEventState::NONE, }) => self.input_key_down(), + // pgup + Event::Key(KeyEvent { + code: KeyCode::PageUp, + modifiers: KeyModifiers::NONE, + kind: KeyEventKind::Press, + state: KeyEventState::NONE, + }) => self.input_key_pgup(), + + // pgdn + Event::Key(KeyEvent { + code: KeyCode::PageDown, + modifiers: KeyModifiers::NONE, + kind: KeyEventKind::Press, + state: KeyEventState::NONE, + }) => self.input_key_pgdn(), + // mouse wheel up Event::Mouse(MouseEvent { kind: MouseEventKind::ScrollUp, @@ -1214,6 +1230,36 @@ impl<'a> App<'a> { } } + /// + fn input_key_pgup(&mut self) { + if self.window == ActiveWindow::Normal { + if self.area == ActiveArea::Watch { + let mut page_height = self.watch_area.get_area_size(); + if page_height > 1 { + page_height = page_height - 1 + } + + // scroll up watch + self.watch_area.scroll_up(page_height); + } + } + } + + /// + fn input_key_pgdn(&mut self) { + if self.window == ActiveWindow::Normal { + if self.area == ActiveArea::Watch { + let mut page_height = self.watch_area.get_area_size(); + if page_height > 1 { + page_height = page_height - 1 + } + + // scroll up watch + self.watch_area.scroll_down(page_height); + } + } + } + // Mouse wheel always scrolls the main area fn mouse_scroll_up(&mut self) { self.watch_area.scroll_up(2); @@ -1223,20 +1269,6 @@ impl<'a> App<'a> { self.watch_area.scroll_down(2); } - // NOTE: TODO: - // Not currently used. - // It will not be supported until the following issues are resolved. - // - 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/tui-rs-revival/ratatui/pull/12 - /// - //fn input_key_pgdn(&mut self) {} - /// fn input_key_left(&mut self) { if let ActiveWindow::Normal = self.window { diff --git a/src/watch.rs b/src/watch.rs index 27efd5d..10c4f8b 100644 --- a/src/watch.rs +++ b/src/watch.rs @@ -45,6 +45,15 @@ impl<'a> WatchArea<'a> { self.area = area; } + /// + pub fn get_area_size(&mut self) -> i16 { + let height = self.area.height as i16; + + return height + } + + + /// pub fn update_output(&mut self, data: Vec>) { self.data = data; From 09de395fdb74a11c5a4d888e5fffdd248999acb1 Mon Sep 17 00:00:00 2001 From: blacknon Date: Thu, 21 Mar 2024 23:29:45 +0900 Subject: [PATCH 03/16] update. issue #87 works for now. --- src/exec.rs | 114 ++++++++++++++++++++++++++++++++++++--------------- src/main.rs | 7 ++-- src/watch.rs | 2 - 3 files changed, 85 insertions(+), 38 deletions(-) diff --git a/src/exec.rs b/src/exec.rs index 3d35578..4be8205 100644 --- a/src/exec.rs +++ b/src/exec.rs @@ -8,6 +8,8 @@ use std::io::prelude::*; use std::io::BufRead; use std::io::BufReader; use std::process::{Command, Stdio}; +use std::sync::{Arc, Mutex}; +use std::thread; // local module use crate::common; @@ -87,44 +89,90 @@ impl ExecuteCommand { let mut vec_stdout = Vec::new(); let mut vec_stderr = Vec::new(); + // TODO: bufferのdead lockが起きてるので、うまいこと非同期I/Oを使って修正する let status = match child_result { Ok(mut child) => { let child_stdout = child.stdout.take().expect(""); let child_stderr = child.stderr.take().expect(""); - let mut stdout = BufReader::new(child_stdout); - let mut stderr = BufReader::new(child_stderr); - - loop { - let (stdout_bytes, stderr_bytes) = match (stdout.fill_buf(), stderr.fill_buf()) - { - (Ok(stdout), Ok(stderr)) => { - // merge stdout/stderr - vec_output.write_all(stdout).expect(""); - vec_output.write_all(stderr).expect(""); - - // stdout - vec_stdout.write_all(stdout).expect(""); - - // stderr - vec_stderr.write_all(stderr).expect(""); - - (stdout.len(), stderr.len()) - } - other => panic!("Some better error handling here, {other:?}"), - }; - - if stdout_bytes == 0 && stderr_bytes == 0 { - break; - } - - stdout.consume(stdout_bytes); - stderr.consume(stderr_bytes); - } - - // Memory release. - drop(stdout); - drop(stderr); + // let mut stdout = BufReader::new(child_stdout); + // let mut stderr = BufReader::new(child_stderr); + + // stdout と stderr の出力を格納するためのベクターを準備する + let arc_vec_output = Arc::new(Mutex::new(Vec::new())); + let arc_vec_stdout = Arc::new(Mutex::new(Vec::new())); + let arc_vec_stderr = Arc::new(Mutex::new(Vec::new())); + + // ベクターへの書き込みをスレッド間で共有するために、Arc と Mutex を使用す + let arc_vec_output_stdout_clone = Arc::clone(&arc_vec_output); + let arc_vec_output_stderr_clone = Arc::clone(&arc_vec_output); + let arc_vec_stdout_clone = Arc::clone(&arc_vec_stdout); + let arc_vec_stderr_clone = Arc::clone(&arc_vec_stderr); + + // stdout を処理するスレッドを開始する + let stdout_thread = thread::spawn(move || { + // BufReader を使用して子プロセスの stdout を読み取る + let mut stdout = BufReader::new(child_stdout); + let mut buf = Vec::new(); + // stdout を完全に読み取り、ベクターに書き込む + stdout.read_to_end(&mut buf).expect("Failed to read stdout"); + arc_vec_stdout_clone.lock().unwrap().extend_from_slice(&buf); + arc_vec_output_stdout_clone.lock().unwrap().extend_from_slice(&buf); + }); + + // stderr を処理するスレッドを開始する + let stderr_thread = thread::spawn(move || { + // BufReader を使用して子プロセスの stderr を読み取る + let mut stderr = BufReader::new(child_stderr); + let mut buf = Vec::new(); + // stderr を完全に読み取り、ベクターに書き込む + stderr.read_to_end(&mut buf).expect("Failed to read stderr"); + arc_vec_stderr_clone.lock().unwrap().extend_from_slice(&buf); + arc_vec_output_stderr_clone.lock().unwrap().extend_from_slice(&buf); + }); + + // stdout と stderr のスレッドが終了するまで待機する + stdout_thread.join().expect("Failed to join stdout thread"); + stderr_thread.join().expect("Failed to join stderr thread"); + + // Arc をアンラップし、MutexGuard を取得してベクターを取り出す + vec_output = Arc::try_unwrap(arc_vec_output).unwrap().into_inner().unwrap(); + vec_stdout = Arc::try_unwrap(arc_vec_stdout).unwrap().into_inner().unwrap(); + vec_stderr = Arc::try_unwrap(arc_vec_stderr).unwrap().into_inner().unwrap(); + + // loop { + // // stdout + // let stdout_bytes = match stdout.fill_buf() { + // Ok(stdout) => { + // vec_output.write_all(stdout).expect(""); + // vec_stdout.write_all(stdout).expect(""); + + // stdout.len() + // }, + // other => panic!("Some better error handling here, {other:?}"), + // }; + // stdout.consume(stdout_bytes); + + // // stderr + // let stderr_bytes = match stderr.fill_buf() { + // Ok(stderr) => { + // vec_output.write_all(stderr).expect(""); + // vec_stderr.write_all(stderr).expect(""); + + // stderr.len() + // }, + // other => panic!("Some better error handling here, {other:?}"), + // }; + // stderr.consume(stderr_bytes); + + // if stdout_bytes == 0 && stderr_bytes == 0 { + // break; + // } + // } + + // // Memory release. + // drop(stdout); + // drop(stderr); // get command status let exit_status = child.wait().expect(""); diff --git a/src/main.rs b/src/main.rs index e57d2c9..ccbfe99 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,9 +3,10 @@ // that can be found in the LICENSE file. // v0.3.12 -// TODO(blacknon): pagedown/pageupスクロールの実装 -// TODO(blacknon): scrollで一番↓まで行くとき、ページの一番下がターミナルの最終行になるように変更する -// TODO(blacknon): issueの中で簡単に実装できそうなやつ +// TODO(blacknon): scrollで一番↓まで行くとき、ページの一番下がターミナルの最終行になるように変更する? +// TODO(blacknon): Home/Endのキーサポートを追加 +// TODO(blacknon): batch mode及びoutput/stdout/stderrごとにhistoryを分ける実装の前準備 +// TODO(blacknon): issueの中でマイルストーンに突っ込んでるやつ // v0.3.13 // TODO(blakcnon): batch modeの実装. diff --git a/src/watch.rs b/src/watch.rs index 10c4f8b..35e0016 100644 --- a/src/watch.rs +++ b/src/watch.rs @@ -52,8 +52,6 @@ impl<'a> WatchArea<'a> { return height } - - /// pub fn update_output(&mut self, data: Vec>) { self.data = data; From 26a09467b7eda7165902d7f50f66dbd121c031cd Mon Sep 17 00:00:00 2001 From: blacknon Date: Fri, 22 Mar 2024 01:46:41 +0900 Subject: [PATCH 04/16] update. issue #87. clean up code --- src/exec.rs | 62 +++++++++++------------------------------------------ 1 file changed, 13 insertions(+), 49 deletions(-) diff --git a/src/exec.rs b/src/exec.rs index 4be8205..7a4d21d 100644 --- a/src/exec.rs +++ b/src/exec.rs @@ -5,7 +5,6 @@ // module use crossbeam_channel::Sender; use std::io::prelude::*; -use std::io::BufRead; use std::io::BufReader; use std::process::{Command, Stdio}; use std::sync::{Arc, Mutex}; @@ -89,91 +88,56 @@ impl ExecuteCommand { let mut vec_stdout = Vec::new(); let mut vec_stderr = Vec::new(); - // TODO: bufferのdead lockが起きてるので、うまいこと非同期I/Oを使って修正する + // get output data let status = match child_result { Ok(mut child) => { let child_stdout = child.stdout.take().expect(""); let child_stderr = child.stderr.take().expect(""); - // let mut stdout = BufReader::new(child_stdout); - // let mut stderr = BufReader::new(child_stderr); - - // stdout と stderr の出力を格納するためのベクターを準備する + // Prepare vector for collapsing stdout and stderr output let arc_vec_output = Arc::new(Mutex::new(Vec::new())); let arc_vec_stdout = Arc::new(Mutex::new(Vec::new())); let arc_vec_stderr = Arc::new(Mutex::new(Vec::new())); - // ベクターへの書き込みをスレッド間で共有するために、Arc と Mutex を使用す + // Using Arc and Mutex to share sequential writes between threads let arc_vec_output_stdout_clone = Arc::clone(&arc_vec_output); let arc_vec_output_stderr_clone = Arc::clone(&arc_vec_output); let arc_vec_stdout_clone = Arc::clone(&arc_vec_stdout); let arc_vec_stderr_clone = Arc::clone(&arc_vec_stderr); - // stdout を処理するスレッドを開始する + // start stdout thread let stdout_thread = thread::spawn(move || { - // BufReader を使用して子プロセスの stdout を読み取る + // Use BufReader to read child process stdout let mut stdout = BufReader::new(child_stdout); let mut buf = Vec::new(); - // stdout を完全に読み取り、ベクターに書き込む + + // write to vector stdout.read_to_end(&mut buf).expect("Failed to read stdout"); arc_vec_stdout_clone.lock().unwrap().extend_from_slice(&buf); arc_vec_output_stdout_clone.lock().unwrap().extend_from_slice(&buf); }); - // stderr を処理するスレッドを開始する + // start stderr thread let stderr_thread = thread::spawn(move || { - // BufReader を使用して子プロセスの stderr を読み取る + // Use BufReader to read child process stderr let mut stderr = BufReader::new(child_stderr); let mut buf = Vec::new(); - // stderr を完全に読み取り、ベクターに書き込む + + // write to vector stderr.read_to_end(&mut buf).expect("Failed to read stderr"); arc_vec_stderr_clone.lock().unwrap().extend_from_slice(&buf); arc_vec_output_stderr_clone.lock().unwrap().extend_from_slice(&buf); }); - // stdout と stderr のスレッドが終了するまで待機する + // with thread stdout/stderr stdout_thread.join().expect("Failed to join stdout thread"); stderr_thread.join().expect("Failed to join stderr thread"); - // Arc をアンラップし、MutexGuard を取得してベクターを取り出す + // Unwrap Arc, get MutexGuard and extract vector vec_output = Arc::try_unwrap(arc_vec_output).unwrap().into_inner().unwrap(); vec_stdout = Arc::try_unwrap(arc_vec_stdout).unwrap().into_inner().unwrap(); vec_stderr = Arc::try_unwrap(arc_vec_stderr).unwrap().into_inner().unwrap(); - // loop { - // // stdout - // let stdout_bytes = match stdout.fill_buf() { - // Ok(stdout) => { - // vec_output.write_all(stdout).expect(""); - // vec_stdout.write_all(stdout).expect(""); - - // stdout.len() - // }, - // other => panic!("Some better error handling here, {other:?}"), - // }; - // stdout.consume(stdout_bytes); - - // // stderr - // let stderr_bytes = match stderr.fill_buf() { - // Ok(stderr) => { - // vec_output.write_all(stderr).expect(""); - // vec_stderr.write_all(stderr).expect(""); - - // stderr.len() - // }, - // other => panic!("Some better error handling here, {other:?}"), - // }; - // stderr.consume(stderr_bytes); - - // if stdout_bytes == 0 && stderr_bytes == 0 { - // break; - // } - // } - - // // Memory release. - // drop(stdout); - // drop(stderr); - // get command status let exit_status = child.wait().expect(""); exit_status.success() From 66e1f1b99671f085dca1d0e6eeac63eed024aa75 Mon Sep 17 00:00:00 2001 From: blacknon Date: Sat, 23 Mar 2024 00:17:49 +0900 Subject: [PATCH 05/16] =?UTF-8?q?update.=20-=20Home/End=20Key=20add=20-=20?= =?UTF-8?q?last=20line=E3=81=8C=E6=9C=80=E7=B5=82=E8=A1=8C=E3=81=AB?= =?UTF-8?q?=E3=81=AA=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=E3=82=B9=E3=82=AF?= =?UTF-8?q?=E3=83=AD=E3=83=BC=E3=83=AB=E4=BD=8D=E7=BD=AE=E3=82=92=E5=A4=89?= =?UTF-8?q?=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app.rs | 32 ++++++++++++++++++++++++++++++++ src/main.rs | 8 +++++--- src/watch.rs | 16 +++++++++++----- 3 files changed, 48 insertions(+), 8 deletions(-) diff --git a/src/app.rs b/src/app.rs index 6e083f0..5e1696c 100644 --- a/src/app.rs +++ b/src/app.rs @@ -736,6 +736,22 @@ impl<'a> App<'a> { state: KeyEventState::NONE, }) => self.input_key_pgdn(), + // Home + Event::Key(KeyEvent { + code: KeyCode::Home, + modifiers: KeyModifiers::NONE, + kind: KeyEventKind::Press, + state: KeyEventState::NONE, + }) => self.input_key_home(), + + // End + Event::Key(KeyEvent { + code: KeyCode::End, + modifiers: KeyModifiers::NONE, + kind: KeyEventKind::Press, + state: KeyEventState::NONE, + }) => self.input_key_end(), + // mouse wheel up Event::Mouse(MouseEvent { kind: MouseEventKind::ScrollUp, @@ -1260,6 +1276,22 @@ impl<'a> App<'a> { } } + fn input_key_home(&mut self) { + if self.window == ActiveWindow::Normal { + if self.area == ActiveArea::Watch { + self.watch_area.scroll_home(); + } + } + } + + fn input_key_end(&mut self) { + if self.window == ActiveWindow::Normal { + if self.area == ActiveArea::Watch { + self.watch_area.scroll_end(); + } + } + } + // Mouse wheel always scrolls the main area fn mouse_scroll_up(&mut self) { self.watch_area.scroll_up(2); diff --git a/src/main.rs b/src/main.rs index ccbfe99..2bbe15b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,13 +3,15 @@ // that can be found in the LICENSE file. // v0.3.12 -// TODO(blacknon): scrollで一番↓まで行くとき、ページの一番下がターミナルの最終行になるように変更する? -// TODO(blacknon): Home/Endのキーサポートを追加 +// TODO(blacknon): scrollで一番↓まで行くとき、ページの一番下がターミナルの最終行になるように変更する? => ok +// TODO(blacknon): Home/Endのキーサポートを追加 => ok // TODO(blacknon): batch mode及びoutput/stdout/stderrごとにhistoryを分ける実装の前準備 // TODO(blacknon): issueの中でマイルストーンに突っ込んでるやつ +// - https://github.com/blacknon/hwatch/issues/82 +// - https://github.com/blacknon/hwatch/issues/103 +// TODO(blakcnon): batch modeの実装. // v0.3.13 -// TODO(blakcnon): batch modeの実装. // TODO(blacknon): 任意時点間のdiffが行えるようにする. // TODO(blacknon): filtering時に、`指定したキーワードで差分が発生した場合のみ`を対象にするような機能にする // TODO(blacknon): コマンドが終了していなくても、インターバル間隔でコマンドを実行する diff --git a/src/watch.rs b/src/watch.rs index 35e0016..42ec865 100644 --- a/src/watch.rs +++ b/src/watch.rs @@ -72,12 +72,18 @@ impl<'a> WatchArea<'a> { self.position = std::cmp::max(0, self.position - num); } - // TODO: 折返しによって発生する行数差分の計算方法が思いつかないため、思いついたら対応を追加する。(うまく取得が出来ない) /// pub fn scroll_down(&mut self, num: i16) { - // get area data size - // let data_size = self.data.len() as i16; - // self.position = std::cmp::min(self.position + num, data_size - 1); - self.position = std::cmp::min(self.position + num, self.lines - 1); + // self.position = std::cmp::min(self.position + num, self.lines - 1); + self.position = std::cmp::min(self.position + num, self.lines - self.area.height as i16); } + + pub fn scroll_home(&mut self) { + self.position = 0; + } + + pub fn scroll_end(&mut self) { + self.position = self.lines - self.area.height as i16; + } + } From bf08eb54032a2cbaffb32c5d48e8d52248d5e92c Mon Sep 17 00:00:00 2001 From: blacknon Date: Sat, 23 Mar 2024 16:17:19 +0900 Subject: [PATCH 06/16] - add mouse event. select history at left click. - add home/end and pgup/pgdn key in history window event. --- src/app.rs | 243 ++++++++++++++++++++++++++++++++----------------- src/history.rs | 48 ++++++---- src/main.rs | 5 +- src/watch.rs | 11 ++- 4 files changed, 199 insertions(+), 108 deletions(-) diff --git a/src/app.rs b/src/app.rs index 5e1696c..c938f85 100644 --- a/src/app.rs +++ b/src/app.rs @@ -8,8 +8,7 @@ use crossbeam_channel::{Receiver, Sender}; // module use crossterm::{ event::{ - DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEvent, KeyEventKind, - KeyEventState, KeyModifiers, MouseEvent, MouseEventKind, + DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEvent, KeyEventKind, KeyEventState, KeyModifiers, MouseButton, MouseEvent, MouseEventKind }, execute, }; @@ -20,7 +19,7 @@ use std::{ }; use tui::{ backend::Backend, - layout::{Constraint, Direction, Layout}, + layout::{Constraint, Direction, Layout, Rect}, Frame, Terminal, }; @@ -214,7 +213,7 @@ impl<'a> App<'a> { /// pub fn run(&mut self, terminal: &mut Terminal) -> io::Result<()> { - self.history_area.next(); + self.history_area.next(1); let mut update_draw = true; loop { if self.done { @@ -249,6 +248,7 @@ impl<'a> App<'a> { update_draw = true; } + // Ok(AppEvent::ToggleMouseEvents) => { if self.mouse_events { execute!(terminal.backend_mut(), DisableMouseCapture)?; @@ -688,7 +688,7 @@ impl<'a> App<'a> { // update selected if selected != 0 { - self.history_area.previous(); + self.history_area.previous(1); } } selected = self.history_area.get_state_select(); @@ -767,19 +767,12 @@ impl<'a> App<'a> { }) => self.mouse_scroll_down(), Event::Mouse(MouseEvent { - kind: MouseEventKind::Down(crossterm::event::MouseButton::Left), + kind: MouseEventKind::Down(MouseButton::Left), column, row, modifiers: KeyModifiers::NONE, .. - }) => { - // Currently a no-op - self.mouse_click_left(column, row); - } - - // pgup - - // pgdn + }) => self.mouse_click_left(column, row), // left Event::Key(KeyEvent { @@ -1210,7 +1203,7 @@ impl<'a> App<'a> { } ActiveArea::History => { // move next history - self.history_area.next(); + self.history_area.next(1); // get now selected history let selected = self.history_area.get_state_select(); @@ -1233,7 +1226,7 @@ impl<'a> App<'a> { } ActiveArea::History => { // move previous history - self.history_area.previous(); + self.history_area.previous(1); // get now selected history let selected = self.history_area.get_state_select(); @@ -1249,14 +1242,32 @@ impl<'a> App<'a> { /// fn input_key_pgup(&mut self) { if self.window == ActiveWindow::Normal { - if self.area == ActiveArea::Watch { - let mut page_height = self.watch_area.get_area_size(); - if page_height > 1 { - page_height = page_height - 1 - } + match self.area { + ActiveArea::Watch => { + let mut page_height = self.watch_area.get_area_size(); + if page_height > 1 { + page_height = page_height - 1 + } - // scroll up watch - self.watch_area.scroll_up(page_height); + // scroll up watch + self.watch_area.scroll_up(page_height); + }, + ActiveArea::History => { + // move next history + let area_size = self.history_area.area.height; + let move_size = if area_size > 1 { + area_size - 1 + } else { + 1 + }; + + // up + self.history_area.next(move_size as usize); + + // get now selected history + let selected = self.history_area.get_state_select(); + self.set_output_data(selected); + } } } } @@ -1264,41 +1275,75 @@ impl<'a> App<'a> { /// fn input_key_pgdn(&mut self) { if self.window == ActiveWindow::Normal { - if self.area == ActiveArea::Watch { - let mut page_height = self.watch_area.get_area_size(); - if page_height > 1 { - page_height = page_height - 1 - } + match self.area { + ActiveArea::Watch => { + let mut page_height = self.watch_area.get_area_size(); + if page_height > 1 { + page_height = page_height - 1 + } - // scroll up watch - self.watch_area.scroll_down(page_height); + // scroll up watch + self.watch_area.scroll_down(page_height); + }, + ActiveArea::History => { + // move previous history + let area_size = self.history_area.area.height; + let move_size = if area_size > 1 { + area_size - 1 + } else { + 1 + }; + + // down + self.history_area.previous(move_size as usize); + + // get now selected history + let selected = self.history_area.get_state_select(); + self.set_output_data(selected); + }, } } } fn input_key_home(&mut self) { if self.window == ActiveWindow::Normal { - if self.area == ActiveArea::Watch { - self.watch_area.scroll_home(); + match self.area { + ActiveArea::Watch => self.watch_area.scroll_home(), + ActiveArea::History => { + // move latest history move size + let hisotory_size = self.history_area.get_history_size(); + self.history_area.next(hisotory_size); + + let selected = self.history_area.get_state_select(); + self.set_output_data(selected); + } } } } fn input_key_end(&mut self) { if self.window == ActiveWindow::Normal { - if self.area == ActiveArea::Watch { - self.watch_area.scroll_end(); - } - } - } + match self.area { + ActiveArea::Watch => self.watch_area.scroll_end(), + ActiveArea::History => { + // get end history move size + let hisotory_size = self.history_area.get_history_size(); + let move_size = if hisotory_size > 1 { + hisotory_size - 1 + } else { + 1 + }; - // Mouse wheel always scrolls the main area - fn mouse_scroll_up(&mut self) { - self.watch_area.scroll_up(2); - } + // move end + self.history_area.previous(move_size); - fn mouse_scroll_down(&mut self) { - self.watch_area.scroll_down(2); + // get now selected history + let selected = self.history_area.get_state_select(); + self.set_output_data(selected); + + }, + } + } } /// @@ -1323,46 +1368,78 @@ 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/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); - // if is_history_area { - // // let headline_count = self.history_area.area.y; - // // self.history_area.click_row(row - headline_count); - - // // self.history_area.previous(); - - // let selected = self.history_area.get_state_select(); - // self.set_output_data(selected); - // } + 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); + if is_history_area { + let headline_count = self.history_area.area.y; + self.history_area.click_row(row - headline_count); + // self.history_area.previous(1); + + let selected = self.history_area.get_state_select(); + self.set_output_data(selected); + } } + + // Mouse wheel always scrolls the main area + fn mouse_scroll_up(&mut self) { + match self.window { + ActiveWindow::Normal => match self.area { + ActiveArea::Watch => { + self.watch_area.scroll_up(2); + }, + ActiveArea::History => { + self.history_area.next(2); + + let selected = self.history_area.get_state_select(); + self.set_output_data(selected); + } + }, + ActiveWindow::Help => { + self.help_window.scroll_down(2); + }, + } + } + + fn mouse_scroll_down(&mut self) { + match self.window { + ActiveWindow::Normal => match self.area { + ActiveArea::Watch => { + self.watch_area.scroll_down(2); + }, + ActiveArea::History => { + self.history_area.previous(2); + + let selected = self.history_area.get_state_select(); + self.set_output_data(selected); + } + }, + ActiveWindow::Help => { + self.help_window.scroll_down(2); + }, + } + } + } -// 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; -// -// // get area range's -// let area_top = area.top(); -// let area_bottom = area.bottom(); -// let area_left = area.left(); -// let area_right = area.right(); -// -// let area_row_range = area_top..area_bottom; -// let area_column_range = area_left..area_right; -// -// if !area_row_range.contains(&row) { -// result = false; -// } -// -// if !area_column_range.contains(&column) { -// result = false; -// } -// -// result -//} +fn check_in_area(area: Rect, column: u16, row: u16) -> bool { + let mut result = true; + + // get area range's + let area_top = area.top(); + let area_bottom = area.bottom(); + let area_left = area.left(); + let area_right = area.right(); + + let area_row_range = area_top..area_bottom; + let area_column_range = area_left..area_right; + + if !area_row_range.contains(&row) { + result = false; + } + + if !area_column_range.contains(&column) { + result = false; + } + result +} diff --git a/src/history.rs b/src/history.rs index 7d4164b..3e8705a 100644 --- a/src/history.rs +++ b/src/history.rs @@ -2,6 +2,8 @@ // Use of this source code is governed by an MIT license // that can be found in the LICENSE file. +use std::borrow::BorrowMut; + use tui::{ layout::Constraint, style::{Color, Modifier, Style}, @@ -21,6 +23,7 @@ pub struct HistoryArea { /// pub area: tui::layout::Rect, + /// pub active: bool, /// @@ -126,6 +129,10 @@ impl HistoryArea { frame.render_stateful_widget(table, self.area, &mut self.state); } + pub fn get_history_size(&self) -> usize { + self.data.len() + } + pub fn get_state_select(&self) -> usize { let i = match self.state.selected() { Some(i) => i, @@ -135,40 +142,41 @@ impl HistoryArea { self.data[i][0].num as usize } - pub fn next(&mut self) { + /// + pub fn next(&mut self, num: usize) { let i = match self.state.selected() { - Some(i) => { - if i > 0 { - i - 1 + Some(i) =>{ + if i > num { + i - num } else { - i + 0 } - } + }, None => 0, }; self.state.select(Some(i)); } - pub fn previous(&mut self) { - let i = match self.state.selected() { + /// + pub fn previous(&mut self, num: usize) { + let i= match self.state.selected() { Some(i) => { - if i < self.data.len() - 1 { - i + 1 + if i + num < self.data.len() - 1 { + i + num } else { - i + self.data.len() - 1 } - } + }, None => 0, }; self.state.select(Some(i)); } - // NOTE: TODO: - // It will not be supported until the following issues are resolved. - // - https://github.com/fdehau/tui-rs/issues/495 - // - // pub fn click_row(&mut self, row: u16) { - // let select_num = row as usize; - // self.state.select(Some(select_num)); - // } + pub fn click_row(&mut self, row: u16) { + let first_row = self.state.offset(); + let select_num = row as usize; + if select_num < self.data.len() { + self.state.select(Some(select_num + first_row)); + } + } } diff --git a/src/main.rs b/src/main.rs index 2bbe15b..d908d6f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,8 +3,6 @@ // that can be found in the LICENSE file. // v0.3.12 -// TODO(blacknon): scrollで一番↓まで行くとき、ページの一番下がターミナルの最終行になるように変更する? => ok -// TODO(blacknon): Home/Endのキーサポートを追加 => ok // TODO(blacknon): batch mode及びoutput/stdout/stderrごとにhistoryを分ける実装の前準備 // TODO(blacknon): issueの中でマイルストーンに突っ込んでるやつ // - https://github.com/blacknon/hwatch/issues/82 @@ -30,6 +28,7 @@ // TODO(blacknon): diffのとき、stdout/stderrでの比較時におけるdiffでhistoryも変化させる? // - データの扱いが変わってきそう? // - どっちにしてもデータがあるなら、stdout/stderrのとこだけで比較するような何かがあればいい??? +// TODO(blacknon): キー入力のカスタマイズが行えるようにする // crate // extern crate ansi_parser; @@ -254,8 +253,10 @@ fn get_clap_matcher() -> clap::ArgMatches { let env_args: Vec<&str> = env_config.split_ascii_whitespace().collect(); let mut os_args = std::env::args_os(); let mut args: Vec = vec![]; + // First argument is the program name args.push(os_args.next().unwrap()); + // Environment variables go next so that they can be overridded // TODO: Currently, the opposites of command-line options are not // yet implemented. E.g., there is no `--no-color` to override diff --git a/src/watch.rs b/src/watch.rs index 42ec865..75cdbcd 100644 --- a/src/watch.rs +++ b/src/watch.rs @@ -74,16 +74,21 @@ impl<'a> WatchArea<'a> { /// pub fn scroll_down(&mut self, num: i16) { - // self.position = std::cmp::min(self.position + num, self.lines - 1); - self.position = std::cmp::min(self.position + num, self.lines - self.area.height as i16); + if self.lines > self.area.height as i16 { + self.position = std::cmp::min(self.position + num, self.lines - self.area.height as i16); + } } + /// pub fn scroll_home(&mut self) { self.position = 0; } + /// pub fn scroll_end(&mut self) { - self.position = self.lines - self.area.height as i16; + if self.lines > self.area.height as i16 { + self.position = self.lines - self.area.height as i16; + } } } From 6515a8315507de272a858805937e2a4fd20f8e0d Mon Sep 17 00:00:00 2001 From: blacknon Date: Tue, 26 Mar 2024 12:45:49 +0900 Subject: [PATCH 07/16] - Add output/stdout/stderr history change. #82. There may still be some bugs, but it will work for now. - batch.rs empty file. #24 --- src/app.rs | 322 +++++++++++++++++++++++++++++++++++++++++-------- src/batch.rs | 3 + src/history.rs | 33 ++++- src/main.rs | 14 ++- src/view.rs | 1 + 5 files changed, 314 insertions(+), 59 deletions(-) create mode 100644 src/batch.rs diff --git a/src/app.rs b/src/app.rs index c938f85..3b56409 100644 --- a/src/app.rs +++ b/src/app.rs @@ -2,8 +2,7 @@ // Use of this source code is governed by an MIT license // that can be found in the LICENSE file. -// TODO: batch modeを処理するのはmain.rsではなく、こっち側でやらせるのが良さそう? - +use clap::Command; use crossbeam_channel::{Receiver, Sender}; // module use crossterm::{ @@ -127,9 +126,18 @@ pub struct App<'a> { /// is_only_diffline: bool, - /// + /// result at output. + /// Use the same value as the key usize for results, results_stdout, and results_stderr, and use it as the key when switching outputs. results: HashMap, + /// result at output only stdout. + /// Use the same value as the key usize for results, results_stdout, and results_stderr, and use it as the key when switching outputs. + results_stdout: HashMap, + + /// result at output only stderr. + /// Use the same value as the key usize for results, results_stdout, and results_stderr, and use it as the key when switching outputs. + results_stderr: HashMap, + /// interval: Interval, @@ -193,6 +201,9 @@ impl<'a> App<'a> { is_only_diffline: false, results: HashMap::new(), + results_stdout: HashMap::new(), + results_stderr: HashMap::new(), + interval: interval.clone(), tab_size: DEFAULT_TAB_SIZE, @@ -359,11 +370,20 @@ impl<'a> App<'a> { /// Set the history to be output to WatchArea. fn set_output_data(&mut self, num: usize) { - // let results = self.results; + // @TODO: setするresultを、output modeのoutput/stdout/stderrで切り替える実装の追加 + // ※ 一応、usizeをkeyにして前後のresult+他のoutput mode時のresultを取得できるようにしたつもり… + + + // Switch the result depending on the output mode. + let results = match self.output_mode { + OutputMode::Output => self.results.clone(), + OutputMode::Stdout => self.results_stdout.clone(), + OutputMode::Stderr => self.results_stderr.clone(), + }; // check result size. // If the size of result is not 0 or more, return and not process. - if self.results.is_empty() { + if results.is_empty() { return; } @@ -375,25 +395,23 @@ impl<'a> App<'a> { // check results over target... if target_dst == 0 { - target_dst = self.results.len() - 1; + target_dst = get_results_latest_index(&results); } - - // set target number at old history. - let target_src = target_dst - 1; + let previous_dst = get_results_previous_index(&results, target_dst); // set new text(text_dst) let text_dst = match self.output_mode { - OutputMode::Output => &self.results[&target_dst].output, - OutputMode::Stdout => &self.results[&target_dst].stdout, - OutputMode::Stderr => &self.results[&target_dst].stderr, + OutputMode::Output => &results[&target_dst].output, + OutputMode::Stdout => &results[&target_dst].stdout, + OutputMode::Stderr => &results[&target_dst].stderr, }; // set old text(text_src) - if self.results.len() > target_src { + if previous_dst > 0 { match self.output_mode { - OutputMode::Output => text_src = &self.results[&target_src].output, - OutputMode::Stdout => text_src = &self.results[&target_src].stdout, - OutputMode::Stderr => text_src = &self.results[&target_src].stderr, + OutputMode::Output => text_src = &results[&previous_dst].output, + OutputMode::Stdout => text_src = &results[&previous_dst].stdout, + OutputMode::Stderr => text_src = &results[&previous_dst].stderr, } } else { text_src = ""; @@ -449,12 +467,24 @@ impl<'a> App<'a> { /// pub fn set_output_mode(&mut self, mode: OutputMode) { + // header update self.output_mode = mode; self.header_area.set_output_mode(mode); self.header_area.update(); - let selected = self.history_area.get_state_select(); - self.set_output_data(selected); + // Switch the result depending on the output mode. + let results = match self.output_mode { + OutputMode::Output => self.results.clone(), + OutputMode::Stdout => self.results_stdout.clone(), + OutputMode::Stderr => self.results_stderr.clone(), + }; + + let selected: usize = self.history_area.get_state_select(); + let new_selected = get_near_index(&results, selected); + self.reset_history(new_selected, self.is_regex_filter); + + // selected = self.history_area.get_state_select(); + self.set_output_data(new_selected); } /// @@ -484,6 +514,7 @@ impl<'a> App<'a> { self.set_output_data(selected); } + /// pub fn set_tab_size(&mut self, tab_size: u16) { self.tab_size = tab_size; } @@ -546,39 +577,78 @@ impl<'a> App<'a> { } /// - fn reset_history(&mut self, is_regex: bool) { + fn reset_history(&mut self, selected: usize,is_regex: bool) { + // @TODO: output modeでの切り替えに使うのかも??(多分使う?) + // @NOTE: まだ作成中(output modeでの切り替えにhistoryを追随させる機能) + + // Switch the result depending on the output mode. + let results = match self.output_mode { + OutputMode::Output => self.results.clone(), + OutputMode::Stdout => self.results_stdout.clone(), + OutputMode::Stderr => self.results_stderr.clone(), + }; + // unlock self.results - // let results = self.results; - let counter = self.results.len(); + // let counter = results.len(); let mut tmp_history = vec![]; // append result. - let latest_num: usize = if counter > 1 { counter - 1 } else { 0 }; - + let latest_num: usize = get_results_latest_index(&results); tmp_history.push(History { timestamp: "latest ".to_string(), - status: self.results[&latest_num].status, + status: results[&latest_num].status, num: 0, }); - for result in self.results.clone().into_iter() { + let mut new_select: Option = None; + for result in results.clone().into_iter() { if result.0 == 0 { continue; } let mut is_push = true; if self.is_filtered { - let result_text = &result.1.output.clone(); + // @TODO: filterがうまく動いてないかも(ouput mode切り替えのやつ) + // @TODO: 重複しているからあとでリファクタしたほうが良さそう + // let result_text = &result.1.output.clone(); + + // if is_regex { + // let re = Regex::new(&self.filtered_text.clone()).unwrap(); + // let regex_match = re.is_match(result_text); + // if !regex_match { + // is_push = false; + // } + // } else if !result_text.contains(&self.filtered_text) { + // is_push = false; + // } + + let result_text = match self.output_mode { + OutputMode::Output => result.1.output.clone(), + OutputMode::Stdout => result.1.stdout.clone(), + OutputMode::Stderr => result.1.stderr.clone(), + }; + + match self.is_regex_filter { + true => { + let re = Regex::new(&self.filtered_text.clone()).unwrap(); + let regex_match = re.is_match(&result_text); + if !regex_match { + is_push = false; + } + } - if is_regex { - let re = Regex::new(&self.filtered_text.clone()).unwrap(); - let regex_match = re.is_match(result_text); - if !regex_match { - is_push = false; + false => { + if !result_text.contains(&self.filtered_text) { + is_push = false; + } } - } else if !result_text.contains(&self.filtered_text) { - is_push = false; } + + + } + + if selected == result.0 { + new_select = Some(selected); } if is_push { @@ -590,6 +660,11 @@ impl<'a> App<'a> { } } + if new_select.is_none() { + new_select = Some(get_near_index(&results, selected)); + } + + // sort tmp_history, to push history let mut history = vec![]; tmp_history.sort_by(|a, b| b.num.cmp(&a.num)); @@ -602,8 +677,12 @@ impl<'a> App<'a> { } } + // @TODO: selectedをうまいことやる + // reset data. self.history_area.reset_history_data(history); + self.history_area.set_state_select(new_select.unwrap()); + } /// @@ -614,6 +693,8 @@ impl<'a> App<'a> { if self.results.is_empty() { // diff output data. self.results.insert(0, latest_result.clone()); + self.results_stdout.insert(0, latest_result.clone()); + self.results_stderr.insert(0, latest_result.clone()); } else { let latest_num = self.results.len() - 1; latest_result = self.results[&latest_num].clone(); @@ -624,6 +705,7 @@ impl<'a> App<'a> { self.header_area.update(); // check result diff + // NOTE: ここで実行結果の差分を比較している // 0.3.12リリースしたら消す if latest_result == _result { return false; } @@ -649,9 +731,12 @@ impl<'a> App<'a> { } } + // NOTE: resultをoutput/stdout/stderrで分けて登録させる? // append results - let result_index = self.results.len(); - self.results.insert(result_index, _result); + let insert_result = self.insert_result(_result.clone()); + let result_index = insert_result.0; + let is_update_stdout = insert_result.1; + let is_update_stderr = insert_result.2; // logging result. if !self.logfile.is_empty() { @@ -659,15 +744,26 @@ impl<'a> App<'a> { } // update HistoryArea - let mut selected = self.history_area.get_state_select(); let mut is_push = true; if self.is_filtered { - let result_text = &self.results[&result_index].output.clone(); + // Switch the result depending on the output mode. + // let results = match self.output_mode { + // OutputMode::Output => self.results.clone(), + // OutputMode::Stdout => self.results_stdout.clone(), + // OutputMode::Stderr => self.results_stderr.clone(), + // }; + + // let result_text = &results[&result_index].output.clone(); + let result_text = match self.output_mode { + OutputMode::Output => self.results[&result_index].output.clone(), + OutputMode::Stdout => self.results_stdout[&result_index].stdout.clone(), + OutputMode::Stderr => self.results_stderr[&result_index].stderr.clone(), + }; match self.is_regex_filter { true => { let re = Regex::new(&self.filtered_text.clone()).unwrap(); - let regex_match = re.is_match(result_text); + let regex_match = re.is_match(&result_text); if !regex_match { is_push = false; } @@ -680,15 +776,23 @@ impl<'a> App<'a> { } } } + + let mut selected = self.history_area.get_state_select(); if is_push { - let _timestamp = &self.results[&result_index].timestamp; - let _status = &self.results[&result_index].status; - self.history_area - .update(_timestamp.to_string(), *_status, result_index as u16); - - // update selected - if selected != 0 { - self.history_area.previous(1); + match self.output_mode { + OutputMode::Output => { + self.add_history(result_index, selected) + }, + OutputMode::Stdout => { + if is_update_stdout { + self.add_history(result_index, selected) + } + }, + OutputMode::Stderr => { + if is_update_stderr { + self.add_history(result_index, selected) + } + }, } } selected = self.history_area.get_state_select(); @@ -699,6 +803,40 @@ impl<'a> App<'a> { true } + /// Insert CommandResult into the results of each output mode. + /// The return value is `result_index` and a bool indicating whether stdout/stderr has changed. + /// Returns true if there is a change in stdout/stderr. + fn insert_result(&mut self, result: CommandResult) -> (usize, bool, bool) { + let result_index = self.results.len(); + self.results.insert(result_index, result.clone()); + + // create result_stdout + let stdout_latest_index = get_results_latest_index(&self.results_stdout); + let before_result_stdout = self.results_stdout[&stdout_latest_index].stdout.clone(); + let result_stdout = result.stdout.clone(); + + // create result_stderr + let stderr_latest_index = get_results_latest_index(&self.results_stderr); + let before_result_stderr = self.results_stderr[&stderr_latest_index].stderr.clone(); + let result_stderr = result.stderr.clone(); + + // append results_stdout + let mut is_stdout_update = false; + if before_result_stdout != result_stdout { + is_stdout_update = true; + self.results_stdout.insert(result_index, result.clone()); + } + + // append results_stderr + let mut is_stderr_update = false; + if before_result_stderr != result_stderr { + is_stderr_update = true; + self.results_stderr.insert(result_index, result.clone()); + } + + return (result_index, is_stdout_update, is_stderr_update); + } + /// fn get_normal_input_key(&mut self, terminal_event: crossterm::event::Event) { match self.window { @@ -938,9 +1076,9 @@ impl<'a> App<'a> { self.filtered_text = "".to_string(); self.header_area.input_text = self.filtered_text.clone(); self.set_input_mode(InputMode::None); - self.reset_history(false); let selected = self.history_area.get_state_select(); + self.reset_history(selected, false); // update WatchArea self.set_output_data(selected); @@ -1092,9 +1230,9 @@ impl<'a> App<'a> { self.is_regex_filter = is_regex; self.filtered_text = self.header_area.input_text.clone(); self.set_input_mode(InputMode::None); - self.reset_history(is_regex); let selected = self.history_area.get_state_select(); + self.reset_history(selected, is_regex); // update WatchArea self.set_output_data(selected); @@ -1103,9 +1241,9 @@ impl<'a> App<'a> { KeyCode::Esc => { self.header_area.input_text = self.filtered_text.clone(); self.set_input_mode(InputMode::None); - self.reset_history(is_regex); let selected = self.history_area.get_state_select(); + self.reset_history(selected, is_regex); // update WatchArea self.set_output_data(selected); @@ -1116,6 +1254,7 @@ impl<'a> App<'a> { } } + /// fn set_area(&mut self, target: ActiveArea) { self.area = target; // set active window to header. @@ -1169,6 +1308,27 @@ impl<'a> App<'a> { let _ = self.tx.send(AppEvent::Redraw); } + /// + fn add_history(&mut self, result_index: usize, selected: usize) { + // Switch the result depending on the output mode. + let results = match self.output_mode { + OutputMode::Output => self.results.clone(), + OutputMode::Stdout => self.results_stdout.clone(), + OutputMode::Stderr => self.results_stderr.clone(), + }; + + let _timestamp = &results[&result_index].timestamp; + let _status = &results[&result_index].status; + self.history_area + .update(_timestamp.to_string(), *_status, result_index as u16); + + // update selected + if selected != 0 { + self.history_area.previous(1); + } + } + + /// pub fn toggle_mouse_events(&mut self) { let _ = self.tx.send(AppEvent::ToggleMouseEvents); } @@ -1305,6 +1465,7 @@ impl<'a> App<'a> { } } + /// fn input_key_home(&mut self) { if self.window == ActiveWindow::Normal { match self.area { @@ -1321,6 +1482,7 @@ impl<'a> App<'a> { } } + /// fn input_key_end(&mut self) { if self.window == ActiveWindow::Normal { match self.area { @@ -1368,6 +1530,7 @@ impl<'a> App<'a> { } } + /// 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); @@ -1381,7 +1544,7 @@ impl<'a> App<'a> { } } - // Mouse wheel always scrolls the main area + /// Mouse wheel always scroll up 2 lines. fn mouse_scroll_up(&mut self) { match self.window { ActiveWindow::Normal => match self.area { @@ -1401,6 +1564,7 @@ impl<'a> App<'a> { } } + /// Mouse wheel always scroll down 2 lines. fn mouse_scroll_down(&mut self) { match self.window { ActiveWindow::Normal => match self.area { @@ -1422,6 +1586,7 @@ impl<'a> App<'a> { } +/// Checks whether the area where the mouse cursor is currently located is within the specified area. fn check_in_area(area: Rect, column: u16, row: u16) -> bool { let mut result = true; @@ -1443,3 +1608,60 @@ fn check_in_area(area: Rect, column: u16, row: u16) -> bool { } result } + +fn get_near_index(results: &HashMap, index: usize) -> usize { + let keys = results.keys().cloned().collect::>(); + + if keys.contains(&index) { + return index; + } else { + // return get_results_next_index(results, index) + return get_results_previous_index(results, index) + } +} + +fn get_results_latest_index(results: &HashMap) -> usize { + let keys = results.keys().cloned().collect::>(); + + // return keys.iter().max().unwrap(); + let max: usize = match keys.iter().max() { + Some(n) => *n, + None => 0, + }; + + return max; +} + +fn get_results_previous_index(results: &HashMap, index: usize) -> usize { + // get keys + let mut keys: Vec<_> = results.keys().cloned().collect(); + keys.sort(); + + let mut previous_index: usize = 0; + for &k in &keys { + if index == k { + break; + } + + previous_index = k; + } + + return previous_index; + +} + +fn get_results_next_index(results: &HashMap, index: usize) -> usize { + // get keys + let mut keys: Vec<_> = results.keys().cloned().collect(); + keys.sort(); + + let mut next_index: usize = 0; + for &k in &keys { + if index < k { + next_index = k; + break; + } + } + + return next_index; +} diff --git a/src/batch.rs b/src/batch.rs new file mode 100644 index 0000000..602bc01 --- /dev/null +++ b/src/batch.rs @@ -0,0 +1,3 @@ +// Copyright (c) 2024 Blacknon. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. diff --git a/src/history.rs b/src/history.rs index 3e8705a..70d4728 100644 --- a/src/history.rs +++ b/src/history.rs @@ -2,8 +2,6 @@ // Use of this source code is governed by an MIT license // that can be found in the LICENSE file. -use std::borrow::BorrowMut; - use tui::{ layout::Constraint, style::{Color, Modifier, Style}, @@ -35,6 +33,7 @@ pub struct HistoryArea { /// History Area Object Trait impl HistoryArea { + /// pub fn new() -> Self { //! new Self Self { @@ -49,18 +48,22 @@ impl HistoryArea { } } + /// pub fn set_area(&mut self, area: tui::layout::Rect) { self.area = area; } + /// pub fn set_active(&mut self, active: bool) { self.active = active; } + /// pub fn set_latest_status(&mut self, latest_status: bool) { self.data[0][0].status = latest_status; } + /// pub fn update(&mut self, timestamp: String, status: bool, num: u16) { self.set_latest_status(status); @@ -77,6 +80,8 @@ impl HistoryArea { /// pub fn reset_history_data(&mut self, data: Vec>) { + // @TODO: output mode切り替えでも使えるようにするため、indexを受け取るようにする + // update data self.data = data; @@ -84,6 +89,7 @@ impl HistoryArea { self.state.select(Some(0)); } + /// pub fn draw(&mut self, frame: &mut Frame) { // insert latest timestamp const LATEST_COLOR: Color = Color::Blue; @@ -129,17 +135,37 @@ impl HistoryArea { frame.render_stateful_widget(table, self.area, &mut self.state); } + /// pub fn get_history_size(&self) -> usize { self.data.len() } + /// pub fn get_state_select(&self) -> usize { let i = match self.state.selected() { Some(i) => i, None => self.data.len() - 1, }; - self.data[i][0].num as usize + if self.data.len() > i { + return self.data[i][0].num as usize; + } else { + return 0; + } + } + + /// + pub fn set_state_select(&mut self, index: usize) { + // find index + let mut i = 0; + for d in self.data.iter() { + if d[0].num == index as u16 { + break; + } + i += 1; + } + + self.state.select(Some(i)); } /// @@ -172,6 +198,7 @@ impl HistoryArea { self.state.select(Some(i)); } + /// pub fn click_row(&mut self, row: u16) { let first_row = self.state.offset(); let select_num = row as usize; diff --git a/src/main.rs b/src/main.rs index d908d6f..f09b13c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,11 +3,11 @@ // that can be found in the LICENSE file. // v0.3.12 -// TODO(blacknon): batch mode及びoutput/stdout/stderrごとにhistoryを分ける実装の前準備 -// TODO(blacknon): issueの中でマイルストーンに突っ込んでるやつ -// - https://github.com/blacknon/hwatch/issues/82 -// - https://github.com/blacknon/hwatch/issues/103 -// TODO(blakcnon): batch modeの実装. +// TODO(blacknon): batch mode及びoutput/stdout/stderrごとにhistoryを分ける実装の前準備(https://github.com/blacknon/hwatch/issues/82) +// TODO(blacknon): diffオプション指定時に引数でパターン分けの機能を付与させる +// - https://github.com/blacknon/hwatch/issues/98 +// - これの前に、clapのバージョンを上げてやらないとだめかも?? +// TODO(blakcnon): batch modeの実装.(clapのバージョンをあげてから、diff等のオプションを指定できるようにしたほうがいいのかも??) // v0.3.13 // TODO(blacknon): 任意時点間のdiffが行えるようにする. @@ -62,7 +62,6 @@ 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; use std::time::Duration; @@ -351,10 +350,13 @@ fn main() { .set_tab_size(tab_size) .set_beep(matcher.is_present("beep")) .set_mouse_events(matcher.is_present("mouse")) + // Set color in view .set_color(matcher.is_present("color")) + // Set line number in view .set_line_number(matcher.is_present("line_number")) + // Set diff(watch diff) in view .set_watch_diff(matcher.is_present("differences")) .set_show_ui(!matcher.is_present("no_title")) diff --git a/src/view.rs b/src/view.rs index dcbafa9..f866e71 100644 --- a/src/view.rs +++ b/src/view.rs @@ -122,6 +122,7 @@ impl View { ctrlc::set_handler(|| { // Runs on SIGINT, SIGTERM (kill), SIGHUP restore_terminal(); + // Exit code for SIGTERM (signal 15), not quite right if another signal is the cause. std::process::exit(128 + 15) })?; From e48c8ac3514ee54944ccaad7f28feb27914190bc Mon Sep 17 00:00:00 2001 From: blacknon Date: Tue, 26 Mar 2024 23:09:09 +0900 Subject: [PATCH 08/16] before refactoring --- src/app.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/app.rs b/src/app.rs index 3b56409..0d4509a 100644 --- a/src/app.rs +++ b/src/app.rs @@ -2,7 +2,8 @@ // Use of this source code is governed by an MIT license // that can be found in the LICENSE file. -use clap::Command; +// TODO: そろそろリファクタしたほうが良さそう(2024/03/26) + use crossbeam_channel::{Receiver, Sender}; // module use crossterm::{ From 1d56f25541c24a8dd2012e336db8fc9327a1f026 Mon Sep 17 00:00:00 2001 From: blacknon Date: Wed, 27 Mar 2024 01:05:27 +0900 Subject: [PATCH 09/16] =?UTF-8?q?=E3=81=A8=E3=82=8A=E3=81=82=E3=81=88?= =?UTF-8?q?=E3=81=9Aclap=E3=81=AE=E3=83=90=E3=83=BC=E3=82=B8=E3=83=A7?= =?UTF-8?q?=E3=83=B3=E3=82=924.5.3=E3=81=AB=E3=81=99=E3=82=8B(=E3=83=AA?= =?UTF-8?q?=E3=83=95=E3=82=A1=E3=82=AF=E3=82=BF=E3=83=AA=E3=83=B3=E3=82=B0?= =?UTF-8?q?=E3=82=82=E3=81=97=E3=81=A6=E3=81=84=E3=81=AA=E3=81=84=E3=81=AE?= =?UTF-8?q?=E3=81=A7=E3=82=B3=E3=83=A1=E3=83=B3=E3=83=88=E3=82=A2=E3=82=A6?= =?UTF-8?q?=E3=83=88=E5=A4=9A=E6=95=B0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 160 +++++++++++++++++++++++++--------------------------- Cargo.toml | 3 +- src/main.rs | 80 +++++++++++++++----------- 3 files changed, 128 insertions(+), 115 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 521663a..ab600fd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -44,6 +44,54 @@ dependencies = [ "libc", ] +[[package]] +name = "anstream" +version = "0.6.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" + +[[package]] +name = "anstyle-parse" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", +] + [[package]] name = "anyhow" version = "1.0.80" @@ -174,17 +222,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "065374052e7df7ee4047b1160cca5e1467a12351a40b3da123c870ba0b8eda2a" -[[package]] -name = "atty" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi", - "libc", - "winapi", -] - [[package]] name = "autocfg" version = "1.1.0" @@ -308,29 +345,37 @@ dependencies = [ [[package]] name = "clap" -version = "3.2.25" +version = "4.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" +checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" dependencies = [ - "atty", - "bitflags 1.3.2", - "clap_lex", - "indexmap", - "once_cell", - "strsim", - "termcolor", - "textwrap", + "clap_builder", ] [[package]] -name = "clap_lex" -version = "0.2.4" +name = "clap_builder" +version = "4.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" +checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" dependencies = [ - "os_str_bytes", + "anstream", + "anstyle", + "clap_lex", + "strsim 0.11.0", ] +[[package]] +name = "clap_lex" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + [[package]] name = "compact_str" version = "0.7.1" @@ -739,12 +784,6 @@ dependencies = [ "byteorder", ] -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - [[package]] name = "hashbrown" version = "0.14.3" @@ -773,15 +812,6 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" -[[package]] -name = "hermit-abi" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] - [[package]] name = "hex" version = "0.4.3" @@ -845,16 +875,6 @@ dependencies = [ "cc", ] -[[package]] -name = "indexmap" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" -dependencies = [ - "autocfg", - "hashbrown 0.12.3", -] - [[package]] name = "indoc" version = "2.0.4" @@ -964,7 +984,7 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3262e75e648fce39813cb56ac41f3c3e3f65217ebf3844d818d1f9398cfb0dc" dependencies = [ - "hashbrown 0.14.3", + "hashbrown", ] [[package]] @@ -1117,12 +1137,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "os_str_bytes" -version = "6.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1" - [[package]] name = "parking" version = "2.0.0" @@ -1580,6 +1594,12 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "strsim" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" + [[package]] name = "strum" version = "0.26.1" @@ -1636,15 +1656,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "termcolor" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" -dependencies = [ - "winapi-util", -] - [[package]] name = "terminfo" version = "0.8.0" @@ -1711,12 +1722,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "textwrap" -version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" - [[package]] name = "thiserror" version = "1.0.57" @@ -1955,7 +1960,7 @@ checksum = "dfb128bacfa86734e07681fb6068e34c144698e84ee022d6e009145d1abb77b5" dependencies = [ "log", "ordered-float", - "strsim", + "strsim 0.10.0", "thiserror", "wezterm-dynamic-derive", ] @@ -1999,15 +2004,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" -[[package]] -name = "winapi-util" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" -dependencies = [ - "winapi", -] - [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" diff --git a/Cargo.toml b/Cargo.toml index c28b6ec..a192535 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,8 @@ hwatch-ansi-parser = "0.9.0" async-std = {version = "1.12"} chrono = "0.4.34" -clap = {version = "3.2.25", features = ["cargo"]} +# clap = {version = "3.2.25", features = ["cargo"]} +clap = {version = "4.5.3", features = ["cargo"]} crossbeam-channel = "0.5.12" crossterm = "0.27.0" ctrlc = {version = "3.4.2", features = ["termination"]} diff --git a/src/main.rs b/src/main.rs index f09b13c..21ff8db 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,7 +3,6 @@ // that can be found in the LICENSE file. // v0.3.12 -// TODO(blacknon): batch mode及びoutput/stdout/stderrごとにhistoryを分ける実装の前準備(https://github.com/blacknon/hwatch/issues/82) // TODO(blacknon): diffオプション指定時に引数でパターン分けの機能を付与させる // - https://github.com/blacknon/hwatch/issues/98 // - これの前に、clapのバージョンを上げてやらないとだめかも?? @@ -57,7 +56,8 @@ extern crate serde_derive; extern crate serde_json; // modules -use clap::{AppSettings, Arg, Command}; +use clap::{Arg, ArgAction, Command, builder::ArgPredicate}; + use question::{Answer, Question}; use std::env::args; use std::path::Path; @@ -99,7 +99,7 @@ const LINE_ENDING: &str = "\n"; const SHELL_COMMAND: &str = "sh -c"; /// Parse args and options function. -fn build_app() -> clap::Command<'static> { +fn build_app() -> clap::Command { // get own name let _program = args() .next() @@ -112,19 +112,18 @@ fn build_app() -> clap::Command<'static> { Command::new(crate_name!()) .about(crate_description!()) - .allow_hyphen_values(true) .version(crate_version!()) .trailing_var_arg(true) .author(crate_authors!()) - .setting(AppSettings::DeriveDisplayOrder) // -- command -- .arg( Arg::new("command") // .allow_hyphen_values(true) - .takes_value(true) - .allow_invalid_utf8(true) - .multiple_values(true) + .action(ArgAction::Append) + .allow_hyphen_values(true) + // .allow_invalid_utf8(true) + .num_args(0..) .required(true), ) @@ -143,6 +142,7 @@ fn build_app() -> clap::Command<'static> { Arg::new("beep") .help("beep if command has a change result") .short('B') + .action(ArgAction::SetTrue) .long("beep"), ) // mouse option @@ -150,13 +150,15 @@ fn build_app() -> clap::Command<'static> { .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.") + .action(ArgAction::SetTrue) .long("mouse"), ) .arg( Arg::new("tab_size") .help("Specifying tab display size") .long("tab_size") - .takes_value(true) + .value_parser(clap::value_parser!(u16)) + .action(ArgAction::Append) .default_value("4"), ) // Option to specify the command to be executed when the output fluctuates. @@ -166,7 +168,8 @@ fn build_app() -> clap::Command<'static> { .help("Executes the specified command if the output changes. Information about changes is stored in json format in environment variable ${HWATCH_DATA}.") .short('A') .long("aftercommand") - .takes_value(true) + // .takes_value(true) + .action(ArgAction::Append) ) // Enable ANSI color option // [-c,--color] @@ -174,6 +177,7 @@ fn build_app() -> clap::Command<'static> { Arg::new("color") .help("interpret ANSI color and style sequences") .short('c') + .action(ArgAction::SetTrue) .long("color"), ) // Enable diff mode option @@ -182,13 +186,15 @@ fn build_app() -> clap::Command<'static> { Arg::new("differences") .help("highlight changes between updates") .long("differences") + .action(ArgAction::SetTrue) .short('d'), ) .arg( Arg::new("no_title") - .help("hide the UI on start. Use `t` to toggle it.") - .long("no-title") - .short('t'), + .help("hide the UI on start. Use `t` to toggle it.") + .long("no-title") + .action(ArgAction::SetTrue) + .short('t'), ) // Enable line number mode option // [--line-number,-N] @@ -196,12 +202,14 @@ fn build_app() -> clap::Command<'static> { Arg::new("line_number") .help("show line number") .short('N') + .action(ArgAction::SetTrue) .long("line-number"), ) .arg( Arg::new("no_help_banner") - .help("hide the \"Display help with h key\" message") - .long("no-help-banner") + .help("hide the \"Display help with h key\" message") + .long("no-help-banner") + .action(ArgAction::SetTrue), ) // exec flag. @@ -210,7 +218,9 @@ fn build_app() -> clap::Command<'static> { Arg::new("exec") .help("Run the command directly, not through the shell. Much like the `-x` option of the watch command.") .short('x') + .action(ArgAction::SetTrue) .long("exec"), + ) // -- options -- // Logging option @@ -224,7 +234,8 @@ fn build_app() -> clap::Command<'static> { .help("logging file") .short('l') .long("logfile") - .takes_value(true), + // .takes_value(true), + .action(ArgAction::Append) ) // shell command .arg( @@ -232,7 +243,8 @@ fn build_app() -> clap::Command<'static> { .help("shell to use at runtime. can also insert the command to the location specified by {COMMAND}.") .short('s') .long("shell") - .takes_value(true) + // .takes_value(true) + .action(ArgAction::Append) .default_value(SHELL_COMMAND), ) // Interval option @@ -242,7 +254,8 @@ fn build_app() -> clap::Command<'static> { .help("seconds to wait between updates") .short('n') .long("interval") - .takes_value(true) + .action(ArgAction::Append) + .value_parser(clap::value_parser!(f64)) .default_value("2"), ) } @@ -272,8 +285,10 @@ fn main() { // Get options flag // let batch = matcher.is_present("batch"); - let after_command = matcher.value_of("after_command"); - let logfile = matcher.value_of("logfile"); + + let after_command = matcher.get_one::("after_command"); + let logfile = matcher.get_one::("logfile"); + // check _logfile directory // TODO(blacknon): commonに移す?(ここで直書きする必要性はなさそう) if let Some(logfile) = logfile { @@ -306,18 +321,19 @@ fn main() { // Create channel let (tx, rx) = unbounded(); - let override_interval = matcher.value_of_t("interval").unwrap_or(DEFAULT_INTERVAL); + let override_interval: f64 = *matcher.get_one::("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); + let tab_size = *matcher.get_one::("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 shell_command = m.get_one::("shell_command").unwrap().to_string(); + let command: Vec<_> = m.get_many::("command").unwrap().into_iter().map(|s| s.clone()).collect(); + let is_exec = m.get_flag("exec"); let interval = interval.clone(); let _ = thread::spawn(move || loop { // Create cmd.. @@ -348,19 +364,19 @@ fn main() { // 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_beep(matcher.get_flag("beep")) + .set_mouse_events(matcher.get_flag("mouse")) // Set color in view - .set_color(matcher.is_present("color")) + .set_color(matcher.get_flag("color")) // Set line number in view - .set_line_number(matcher.is_present("line_number")) + .set_line_number(matcher.get_flag("line_number")) // Set diff(watch diff) in view - .set_watch_diff(matcher.is_present("differences")) - .set_show_ui(!matcher.is_present("no_title")) - .set_show_help_banner(!matcher.is_present("no_help_banner")); + .set_watch_diff(matcher.get_flag("differences")) + .set_show_ui(!matcher.get_flag("no_title")) + .set_show_help_banner(!matcher.get_flag("no_help_banner")); // Set logfile if let Some(logfile) = logfile { From 6325ca6d2583aa5e69768d105a76f100c20e8113 Mon Sep 17 00:00:00 2001 From: blacknon Date: Wed, 27 Mar 2024 01:30:54 +0900 Subject: [PATCH 10/16] =?UTF-8?q?=E3=81=A8=E3=82=8A=E3=81=82=E3=81=88?= =?UTF-8?q?=E3=81=9Aclap=E3=81=AE=E3=83=90=E3=83=BC=E3=82=B8=E3=83=A7?= =?UTF-8?q?=E3=83=B3=E3=82=924.5.3=E3=81=AB=E3=81=99=E3=82=8B(=E3=83=AA?= =?UTF-8?q?=E3=83=95=E3=82=A1=E3=82=AF=E3=82=BF=E3=83=AA=E3=83=B3=E3=82=B0?= =?UTF-8?q?=E3=82=82=E3=81=97=E3=81=A6=E3=81=84=E3=81=AA=E3=81=84=E3=81=AE?= =?UTF-8?q?=E3=81=A7=E3=82=B3=E3=83=A1=E3=83=B3=E3=83=88=E3=82=A2=E3=82=A6?= =?UTF-8?q?=E3=83=88=E5=A4=9A=E6=95=B0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.toml | 4 +--- src/app.rs | 10 +++++----- src/main.rs | 15 +++++++-------- 3 files changed, 13 insertions(+), 16 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a192535..2e69e30 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,6 @@ hwatch-ansi-parser = "0.9.0" async-std = {version = "1.12"} chrono = "0.4.34" -# clap = {version = "3.2.25", features = ["cargo"]} clap = {version = "4.5.3", features = ["cargo"]} crossbeam-channel = "0.5.12" crossterm = "0.27.0" @@ -23,11 +22,10 @@ ctrlc = {version = "3.4.2", features = ["termination"]} difference = "2.0" futures = "0.3.30" question = "0.2.2" +ratatui = {version = "0.26.1", default-features = false, features = ['crossterm', 'unstable-rendered-line-info']} regex = "1.10.3" serde = "1.0.197" serde_derive = "1.0.197" serde_json = "1.0.114" shell-words = "1.1.0" termwiz = "0.22.0" - -ratatui = {version = "0.26.1", default-features = false, features = ['crossterm', 'unstable-rendered-line-info']} diff --git a/src/app.rs b/src/app.rs index 0d4509a..dd622a1 100644 --- a/src/app.rs +++ b/src/app.rs @@ -482,7 +482,7 @@ impl<'a> App<'a> { let selected: usize = self.history_area.get_state_select(); let new_selected = get_near_index(&results, selected); - self.reset_history(new_selected, self.is_regex_filter); + self.reset_history(new_selected); // selected = self.history_area.get_state_select(); self.set_output_data(new_selected); @@ -578,7 +578,7 @@ impl<'a> App<'a> { } /// - fn reset_history(&mut self, selected: usize,is_regex: bool) { + fn reset_history(&mut self, selected: usize) { // @TODO: output modeでの切り替えに使うのかも??(多分使う?) // @NOTE: まだ作成中(output modeでの切り替えにhistoryを追随させる機能) @@ -1079,7 +1079,7 @@ impl<'a> App<'a> { self.set_input_mode(InputMode::None); let selected = self.history_area.get_state_select(); - self.reset_history(selected, false); + self.reset_history(selected); // update WatchArea self.set_output_data(selected); @@ -1233,7 +1233,7 @@ impl<'a> App<'a> { self.set_input_mode(InputMode::None); let selected = self.history_area.get_state_select(); - self.reset_history(selected, is_regex); + self.reset_history(selected); // update WatchArea self.set_output_data(selected); @@ -1244,7 +1244,7 @@ impl<'a> App<'a> { self.set_input_mode(InputMode::None); let selected = self.history_area.get_state_select(); - self.reset_history(selected, is_regex); + self.reset_history(selected); // update WatchArea self.set_output_data(selected); diff --git a/src/main.rs b/src/main.rs index 21ff8db..9c09e46 100644 --- a/src/main.rs +++ b/src/main.rs @@ -119,10 +119,8 @@ fn build_app() -> clap::Command { // -- command -- .arg( Arg::new("command") - // .allow_hyphen_values(true) .action(ArgAction::Append) .allow_hyphen_values(true) - // .allow_invalid_utf8(true) .num_args(0..) .required(true), ) @@ -168,7 +166,6 @@ fn build_app() -> clap::Command { .help("Executes the specified command if the output changes. Information about changes is stored in json format in environment variable ${HWATCH_DATA}.") .short('A') .long("aftercommand") - // .takes_value(true) .action(ArgAction::Append) ) // Enable ANSI color option @@ -189,6 +186,8 @@ fn build_app() -> clap::Command { .action(ArgAction::SetTrue) .short('d'), ) + // exec flag. + // [--no-title] .arg( Arg::new("no_title") .help("hide the UI on start. Use `t` to toggle it.") @@ -205,6 +204,8 @@ fn build_app() -> clap::Command { .action(ArgAction::SetTrue) .long("line-number"), ) + // exec flag. + // [--no-help-banner] .arg( Arg::new("no_help_banner") .help("hide the \"Display help with h key\" message") @@ -213,7 +214,7 @@ fn build_app() -> clap::Command { ) // exec flag. - // + // [-x,--exec] .arg( Arg::new("exec") .help("Run the command directly, not through the shell. Much like the `-x` option of the watch command.") @@ -234,8 +235,7 @@ fn build_app() -> clap::Command { .help("logging file") .short('l') .long("logfile") - // .takes_value(true), - .action(ArgAction::Append) + .action(ArgAction::Append), ) // shell command .arg( @@ -243,7 +243,6 @@ fn build_app() -> clap::Command { .help("shell to use at runtime. can also insert the command to the location specified by {COMMAND}.") .short('s') .long("shell") - // .takes_value(true) .action(ArgAction::Append) .default_value(SHELL_COMMAND), ) @@ -284,7 +283,7 @@ fn main() { let matcher = get_clap_matcher(); // Get options flag - // let batch = matcher.is_present("batch"); + // let batch = matcher.get_one("batch"); let after_command = matcher.get_one::("after_command"); let logfile = matcher.get_one::("logfile"); From 71c2538ea3fdf81f36726a8fe4ed39c00bb42436 Mon Sep 17 00:00:00 2001 From: blacknon Date: Wed, 27 Mar 2024 11:18:03 +0900 Subject: [PATCH 11/16] =?UTF-8?q?=E3=81=A8=E3=82=8A=E3=81=82=E3=81=88?= =?UTF-8?q?=E3=81=9Aclap=E3=81=AE=E3=83=90=E3=83=BC=E3=82=B8=E3=83=A7?= =?UTF-8?q?=E3=83=B3=E3=82=924.5.3=E3=81=AB=E3=81=97=E3=81=A6=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E3=82=92=E6=95=B4=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main.rs | 42 +++++++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/src/main.rs b/src/main.rs index 9c09e46..965d569 100644 --- a/src/main.rs +++ b/src/main.rs @@ -56,8 +56,7 @@ extern crate serde_derive; extern crate serde_json; // modules -use clap::{Arg, ArgAction, Command, builder::ArgPredicate}; - +use clap::{Arg, ArgAction, Command, ValueHint, builder::ArgPredicate}; use question::{Answer, Question}; use std::env::args; use std::path::Path; @@ -65,6 +64,7 @@ use std::sync::{Arc, RwLock}; use crossbeam_channel::unbounded; use std::thread; use std::time::Duration; +use app::DiffMode; // local modules mod ansi; @@ -122,6 +122,7 @@ fn build_app() -> clap::Command { .action(ArgAction::Append) .allow_hyphen_values(true) .num_args(0..) + .value_hint(ValueHint::CommandWithArguments) .required(true), ) @@ -151,23 +152,6 @@ fn build_app() -> clap::Command { .action(ArgAction::SetTrue) .long("mouse"), ) - .arg( - Arg::new("tab_size") - .help("Specifying tab display size") - .long("tab_size") - .value_parser(clap::value_parser!(u16)) - .action(ArgAction::Append) - .default_value("4"), - ) - // Option to specify the command to be executed when the output fluctuates. - // [-C,--changed-command] - .arg( - Arg::new("after_command") - .help("Executes the specified command if the output changes. Information about changes is stored in json format in environment variable ${HWATCH_DATA}.") - .short('A') - .long("aftercommand") - .action(ArgAction::Append) - ) // Enable ANSI color option // [-c,--color] .arg( @@ -224,6 +208,16 @@ fn build_app() -> clap::Command { ) // -- options -- + // Option to specify the command to be executed when the output fluctuates. + // [-A,--aftercommand] + .arg( + Arg::new("after_command") + .help("Executes the specified command if the output changes. Information about changes is stored in json format in environment variable ${HWATCH_DATA}.") + .short('A') + .long("aftercommand") + .value_hint(ValueHint::CommandString) + .action(ArgAction::Append) + ) // Logging option // [--logfile,-l] /path/to/logfile // ex.) @@ -235,6 +229,7 @@ fn build_app() -> clap::Command { .help("logging file") .short('l') .long("logfile") + .value_hint(ValueHint::FilePath) .action(ArgAction::Append), ) // shell command @@ -244,6 +239,7 @@ fn build_app() -> clap::Command { .short('s') .long("shell") .action(ArgAction::Append) + .value_hint(ValueHint::CommandString) .default_value(SHELL_COMMAND), ) // Interval option @@ -257,6 +253,14 @@ fn build_app() -> clap::Command { .value_parser(clap::value_parser!(f64)) .default_value("2"), ) + .arg( + Arg::new("tab_size") + .help("Specifying tab display size") + .long("tab_size") + .value_parser(clap::value_parser!(u16)) + .action(ArgAction::Append) + .default_value("4"), + ) } fn get_clap_matcher() -> clap::ArgMatches { From 473cbd435102cbe7c71caa307ac3f8b8dd45e35f Mon Sep 17 00:00:00 2001 From: blacknon Date: Wed, 27 Mar 2024 11:45:19 +0900 Subject: [PATCH 12/16] update. #98 fix. --- src/main.rs | 43 ++++++++++++++++++++++++++++++++----------- src/view.rs | 15 +++++++-------- 2 files changed, 39 insertions(+), 19 deletions(-) diff --git a/src/main.rs b/src/main.rs index 965d569..b97395a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -161,15 +161,6 @@ fn build_app() -> clap::Command { .action(ArgAction::SetTrue) .long("color"), ) - // Enable diff mode option - // [--differences,-d] - .arg( - Arg::new("differences") - .help("highlight changes between updates") - .long("differences") - .action(ArgAction::SetTrue) - .short('d'), - ) // exec flag. // [--no-title] .arg( @@ -233,6 +224,7 @@ fn build_app() -> clap::Command { .action(ArgAction::Append), ) // shell command + // [--shell,-s] command .arg( Arg::new("shell_command") .help("shell to use at runtime. can also insert the command to the location specified by {COMMAND}.") @@ -253,6 +245,8 @@ fn build_app() -> clap::Command { .value_parser(clap::value_parser!(f64)) .default_value("2"), ) + // tab size set option + // [--tab_size] size(default:4) .arg( Arg::new("tab_size") .help("Specifying tab display size") @@ -261,6 +255,19 @@ fn build_app() -> clap::Command { .action(ArgAction::Append) .default_value("4"), ) + // Enable diff mode option + // [--differences,-d] [none, watch, line, word] + .arg( + Arg::new("differences") + .help("highlight changes between updates") + .long("differences") + .short('d') + .num_args(0..=1) + .value_parser(["none", "watch", "line", "word"]) + .default_missing_value("watch") + .default_value_ifs([("differences", ArgPredicate::IsPresent, None)]) + .action(ArgAction::Append), + ) } fn get_clap_matcher() -> clap::ArgMatches { @@ -324,12 +331,26 @@ fn main() { // Create channel let (tx, rx) = unbounded(); + // interval let override_interval: f64 = *matcher.get_one::("interval").unwrap_or(&DEFAULT_INTERVAL); - let interval = Interval::new(override_interval.into()); + // tab size let tab_size = *matcher.get_one::("tab_size").unwrap_or(&DEFAULT_TAB_SIZE); + // diff mode + let diff_mode = if matcher.contains_id("differences") { + match matcher.get_one::("differences").unwrap().as_str() { + "none" => DiffMode::Disable, + "watch" => DiffMode::Watch, + "line" => DiffMode::Line, + "word" => DiffMode::Word, + _ => DiffMode::Disable, + } + } else { + DiffMode::Disable + }; + // Start Command Thread { let m = matcher.clone(); @@ -377,7 +398,7 @@ fn main() { .set_line_number(matcher.get_flag("line_number")) // Set diff(watch diff) in view - .set_watch_diff(matcher.get_flag("differences")) + .set_diff_mode(diff_mode) .set_show_ui(!matcher.get_flag("no_title")) .set_show_help_banner(!matcher.get_flag("no_help_banner")); diff --git a/src/view.rs b/src/view.rs index f866e71..790720e 100644 --- a/src/view.rs +++ b/src/view.rs @@ -36,7 +36,7 @@ pub struct View { show_ui: bool, show_help_banner: bool, line_number: bool, - watch_diff: bool, + diff_mode: DiffMode, log_path: String, } @@ -53,7 +53,7 @@ impl View { show_ui: true, show_help_banner: true, line_number: false, - watch_diff: false, + diff_mode: DiffMode::Disable, log_path: "".to_string(), } } @@ -103,8 +103,8 @@ impl View { self } - pub fn set_watch_diff(mut self, watch_diff: bool) -> Self { - self.watch_diff = watch_diff; + pub fn set_diff_mode(mut self, diff_mode: DiffMode) -> Self { + self.diff_mode = diff_mode; self } @@ -167,10 +167,9 @@ impl View { // set line_number app.set_line_number(self.line_number); - // set watch diff - if self.watch_diff { - app.set_diff_mode(DiffMode::Watch); - } + // set diff mode + app.set_diff_mode(self.diff_mode); + // Run App let res = app.run(&mut terminal); From be062882a9866a4325c70300931386240c0b2f26 Mon Sep 17 00:00:00 2001 From: blacknon Date: Mon, 1 Apr 2024 00:13:59 +0900 Subject: [PATCH 13/16] update. #24 batch mode add. plane/watch line diff fix. --- Cargo.lock | 10 + Cargo.toml | 1 + src/app.rs | 142 ++++--- src/batch.rs | 314 ++++++++++++++ src/common.rs | 17 + src/header.rs | 3 +- src/main.rs | 142 ++++--- src/output.rs | 1132 ++++++++++++++++++++++++++++++++++--------------- src/view.rs | 22 +- 9 files changed, 1330 insertions(+), 453 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ab600fd..e9f6ed2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -44,6 +44,15 @@ dependencies = [ "libc", ] +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + [[package]] name = "anstream" version = "0.6.13" @@ -822,6 +831,7 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" name = "hwatch" version = "0.3.12" dependencies = [ + "ansi_term", "async-std", "chrono", "clap", diff --git a/Cargo.toml b/Cargo.toml index 2e69e30..9aa2272 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ version = "0.3.12" heapless = "0.6.1" hwatch-ansi-parser = "0.9.0" +ansi_term = "0.12.1" async-std = {version = "1.12"} chrono = "0.4.34" clap = {version = "4.5.3", features = ["cargo"]} diff --git a/src/app.rs b/src/app.rs index dd622a1..759ef08 100644 --- a/src/app.rs +++ b/src/app.rs @@ -22,12 +22,11 @@ use tui::{ layout::{Constraint, Direction, Layout, Rect}, Frame, Terminal, }; - -// use std::process::Command; use std::thread; // local module use crate::common::logging_result; +use crate::common::{DiffMode, OutputMode}; use crate::event::AppEvent; use crate::exec::{exec_after_command, CommandResult}; use crate::header::HeaderArea; @@ -55,23 +54,6 @@ pub enum ActiveWindow { Help, } -/// -#[derive(Clone, Copy, PartialEq, Eq)] -pub enum DiffMode { - Disable, - Watch, - Line, - Word, -} - -/// -#[derive(Clone, Copy, PartialEq, Eq)] -pub enum OutputMode { - Output, - Stdout, - Stderr, -} - /// #[derive(Clone, Copy, PartialEq, Eq)] pub enum InputMode { @@ -160,6 +142,9 @@ pub struct App<'a> { /// Enable mouse wheel support. mouse_events: bool, + /// + printer: output::Printer, + /// It is a flag value to confirm the done of the app. /// If `true`, exit app. pub done: bool, @@ -167,7 +152,10 @@ pub struct App<'a> { /// logfile path. logfile: String, + /// pub tx: Sender, + + /// pub rx: Receiver, } @@ -216,6 +204,8 @@ impl<'a> App<'a> { mouse_events, + printer: output::Printer::new(), + done: false, logfile: "".to_string(), tx, @@ -227,6 +217,19 @@ impl<'a> App<'a> { pub fn run(&mut self, terminal: &mut Terminal) -> io::Result<()> { self.history_area.next(1); let mut update_draw = true; + + self.printer + .set_batch(false) + .set_color(self.ansi_color) + .set_diff_mode(self.diff_mode) + .set_filter(self.is_filtered) + .set_regex_filter(self.is_regex_filter) + .set_line_number(self.line_number) + .set_output_mode(self.output_mode) + .set_tab_size(self.tab_size) + .set_filter_text(self.filtered_text.clone()) + .set_only_diffline(self.is_only_diffline); + loop { if self.done { return Ok(()); @@ -371,10 +374,6 @@ impl<'a> App<'a> { /// Set the history to be output to WatchArea. fn set_output_data(&mut self, num: usize) { - // @TODO: setするresultを、output modeのoutput/stdout/stderrで切り替える実装の追加 - // ※ 一応、usizeをkeyにして前後のresult+他のoutput mode時のresultを取得できるようにしたつもり… - - // Switch the result depending on the output mode. let results = match self.output_mode { OutputMode::Output => self.results.clone(), @@ -406,9 +405,12 @@ impl<'a> App<'a> { OutputMode::Stdout => &results[&target_dst].stdout, OutputMode::Stderr => &results[&target_dst].stderr, }; + let dest = results[&target_dst].clone(); // set old text(text_src) + let mut src = CommandResult::default(); if previous_dst > 0 { + src = results[&previous_dst].clone(); match self.output_mode { OutputMode::Output => text_src = &results[&previous_dst].output, OutputMode::Stdout => text_src = &results[&previous_dst].stdout, @@ -419,32 +421,18 @@ impl<'a> App<'a> { } let output_data = match self.diff_mode { - DiffMode::Disable => output::get_plane_output( - self.ansi_color, - self.line_number, - text_dst, - 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, - self.tab_size, - ), - - DiffMode::Line => output::get_line_diff( - self.ansi_color, - self.line_number, - self.is_only_diffline, - text_src, - text_dst, - self.tab_size, - ), + DiffMode::Disable => self.printer.get_watch_text(dest, src), + DiffMode::Watch => self.printer.get_watch_text(dest, src), + DiffMode::Line => self.printer.get_watch_text(dest, src), + + // DiffMode::Line => output::get_line_diff( + // self.ansi_color, + // self.line_number, + // self.is_only_diffline, + // text_src, + // text_dst, + // self.tab_size, + // ), DiffMode::Word => output::get_word_diff( self.ansi_color, @@ -473,19 +461,22 @@ impl<'a> App<'a> { self.header_area.set_output_mode(mode); self.header_area.update(); - // Switch the result depending on the output mode. - let results = match self.output_mode { - OutputMode::Output => self.results.clone(), - OutputMode::Stdout => self.results_stdout.clone(), - OutputMode::Stderr => self.results_stderr.clone(), - }; + self.printer.set_output_mode(mode); - let selected: usize = self.history_area.get_state_select(); - let new_selected = get_near_index(&results, selected); - self.reset_history(new_selected); + // + if self.results.len() > 0 { + // Switch the result depending on the output mode. + let results = match self.output_mode { + OutputMode::Output => self.results.clone(), + OutputMode::Stdout => self.results_stdout.clone(), + OutputMode::Stderr => self.results_stderr.clone(), + }; - // selected = self.history_area.get_state_select(); - self.set_output_data(new_selected); + let selected: usize = self.history_area.get_state_select(); + let new_selected = get_near_index(&results, selected); + self.reset_history(new_selected); + self.set_output_data(new_selected); + } } /// @@ -495,6 +486,8 @@ impl<'a> App<'a> { self.header_area.set_ansi_color(ansi_color); self.header_area.update(); + self.printer.set_color(ansi_color); + let selected = self.history_area.get_state_select(); self.set_output_data(selected); } @@ -511,6 +504,8 @@ impl<'a> App<'a> { self.header_area.set_line_number(line_number); self.header_area.update(); + self.printer.set_line_number(line_number); + let selected = self.history_area.get_state_select(); self.set_output_data(selected); } @@ -518,6 +513,7 @@ impl<'a> App<'a> { /// pub fn set_tab_size(&mut self, tab_size: u16) { self.tab_size = tab_size; + self.printer.set_tab_size(tab_size); } /// @@ -549,17 +545,21 @@ impl<'a> App<'a> { self.header_area.set_diff_mode(diff_mode); self.header_area.update(); + self.printer.set_diff_mode(diff_mode); + let selected = self.history_area.get_state_select(); self.set_output_data(selected); } /// - fn set_is_only_diffline(&mut self, is_only_diffline: bool) { + pub fn set_is_only_diffline(&mut self, is_only_diffline: bool) { self.is_only_diffline = is_only_diffline; self.header_area.set_is_only_diffline(is_only_diffline); self.header_area.update(); + self.printer.set_only_diffline(is_only_diffline); + let selected = self.history_area.get_state_select(); self.set_output_data(selected); } @@ -747,14 +747,6 @@ impl<'a> App<'a> { // update HistoryArea let mut is_push = true; if self.is_filtered { - // Switch the result depending on the output mode. - // let results = match self.output_mode { - // OutputMode::Output => self.results.clone(), - // OutputMode::Stdout => self.results_stdout.clone(), - // OutputMode::Stderr => self.results_stderr.clone(), - // }; - - // let result_text = &results[&result_index].output.clone(); let result_text = match self.output_mode { OutputMode::Output => self.results[&result_index].output.clone(), OutputMode::Stdout => self.results_stdout[&result_index].stdout.clone(), @@ -1078,6 +1070,10 @@ impl<'a> App<'a> { self.header_area.input_text = self.filtered_text.clone(); self.set_input_mode(InputMode::None); + self.printer.set_filter(self.is_filtered); + self.printer.set_regex_filter(self.is_regex_filter); + self.printer.set_filter_text("".to_string()); + let selected = self.history_area.get_state_select(); self.reset_history(selected); @@ -1232,6 +1228,10 @@ impl<'a> App<'a> { self.filtered_text = self.header_area.input_text.clone(); self.set_input_mode(InputMode::None); + self.printer.set_filter(self.is_filtered); + self.printer.set_regex_filter(self.is_regex_filter); + self.printer.set_filter_text(self.filtered_text.clone()); + let selected = self.history_area.get_state_select(); self.reset_history(selected); @@ -1242,6 +1242,10 @@ impl<'a> App<'a> { KeyCode::Esc => { self.header_area.input_text = self.filtered_text.clone(); self.set_input_mode(InputMode::None); + self.is_filtered = false; + + self.printer.set_filter(self.is_filtered); + self.printer.set_regex_filter(self.is_regex_filter); let selected = self.history_area.get_state_select(); self.reset_history(selected); diff --git a/src/batch.rs b/src/batch.rs index 602bc01..90a8757 100644 --- a/src/batch.rs +++ b/src/batch.rs @@ -1,3 +1,317 @@ // Copyright (c) 2024 Blacknon. All rights reserved. // Use of this source code is governed by an MIT license // that can be found in the LICENSE file. + +use crossbeam_channel::{Receiver, Sender}; +use difference::{Changeset, Difference}; +use std::{io, collections::HashMap}; +use std::thread; +use ansi_term::Colour; + +use crate::common::{DiffMode, OutputMode}; +use crate::event::AppEvent; +use crate::exec::{exec_after_command, CommandResult}; +use crate::{output, LINE_ENDING}; + +/// Struct at watch view window. +pub struct Batch { + /// + after_command: String, + + /// + line_number: bool, + + /// + is_color: bool, + + /// + is_beep: bool, + + /// + results: HashMap, + + /// + output_mode: OutputMode, + + /// + diff_mode: DiffMode, + + /// + is_only_diffline: bool, + + /// + printer: output::Printer, + + /// + pub tx: Sender, + + /// + pub rx: Receiver, +} + +impl Batch { + /// + pub fn new(tx: Sender, rx: Receiver) -> Self { + Self { + after_command: "".to_string(), + line_number: false, + is_color: true, + // is_color: false, + is_beep: false, + results: HashMap::new(), + output_mode: OutputMode::Output, + diff_mode: DiffMode::Disable, + is_only_diffline: false, + printer: output::Printer::new(), + tx, + rx, + } + } + + pub fn run(&mut self) -> io::Result<()> { + self.printer + .set_batch(true) + .set_color(self.is_color) + .set_diff_mode(self.diff_mode) + .set_line_number(self.line_number) + .set_only_diffline(self.is_only_diffline) + .set_output_mode(self.output_mode); + + loop { + match self.rx.recv() { + // Get command result. + Ok(AppEvent::OutputUpdate(exec_result)) => { + let _exec_return = self.update_result(exec_result); + + // beep + if _exec_return && self.is_beep { + println!("\x07") + } + }, + + // Other event + Ok(_) => {}, + + // Error + Err(_) => {} + } + } + } + + /// + fn update_result(&mut self, _result: CommandResult) -> bool { + // check results size. + let mut latest_result = CommandResult::default(); + + if self.results.is_empty() { + // diff output data. + self.results.insert(0, latest_result.clone()); + } else { + let latest_num = self.results.len() - 1; + latest_result = self.results[&latest_num].clone(); + } + + // check result diff + // NOTE: ここで実行結果の差分を比較している // 0.3.12リリースしたら消す + if latest_result == _result { + return false; + } + + if !self.after_command.is_empty() { + let after_command = self.after_command.clone(); + + let results = self.results.clone(); + let latest_num = results.len() - 1; + + let before_result = results[&latest_num].clone(); + let after_result = _result.clone(); + + { + thread::spawn(move || { + exec_after_command( + "sh -c".to_string(), + after_command.clone(), + before_result, + after_result, + ); + }); + } + } + + // add result + self.results.insert(self.results.len(), _result.clone()); + + // output result + self.printout_result(); + + true + } + + /// + fn printout_result(&mut self) { + // output result + let latest = self.results.len() - 1; + + // Switch the result depending on the output mode. + let dest = self.results[&latest].clone(); + let timestamp_dst = dest.timestamp.clone(); + + let previous = latest - 1; + let src = self.results[&previous].clone(); + + // print split line + if self.is_color { + println!("\x1b[38;5;240m=====[{:}]=========================\x1b[0m", timestamp_dst); + } else { + println!("=====[{:}]=========================", timestamp_dst); + } + + let printout_data = self.printer.get_batch_text(dest, src); + + println!("{:}", printout_data.join("\n")); + } + + /// + pub fn set_after_command(mut self, after_command: String) -> Self { + self.after_command = after_command; + self + } + + /// + pub fn set_line_number(mut self, line_number: bool) -> Self { + self.line_number = line_number; + self + } + + /// + pub fn set_beep(mut self, is_beep: bool) -> Self { + self.is_beep = is_beep; + self + } + + /// + pub fn set_output_mode(mut self, output_mode: OutputMode) -> Self { + self.output_mode = output_mode; + self + } + + /// + pub fn set_diff_mode(mut self, diff_mode: DiffMode) -> Self { + self.diff_mode = diff_mode; + self + } + + /// + pub fn set_only_diffline(mut self, is_only_diffline: bool) -> Self { + self.is_only_diffline = is_only_diffline; + self + } +} + + +// fn generate_watch_diff_printout_data() -> Vec {} + + +fn generate_line_diff_printout_data( + dst: CommandResult, + src: CommandResult, + line_number: bool, + is_color: bool, + output_mode: OutputMode, + only_diff_line: bool, +) -> Vec { + let mut result: Vec = Vec::new(); + + // Switch the result depending on the output mode. + let text_dst = match output_mode { + OutputMode::Output => dst.output.clone(), + OutputMode::Stdout => dst.stdout.clone(), + OutputMode::Stderr => dst.stderr.clone(), + }; + + // Switch the result depending on the output mode. + let text_src = match output_mode { + OutputMode::Output => src.output.clone(), + OutputMode::Stdout => src.stdout.clone(), + OutputMode::Stderr => src.stderr.clone(), + }; + + // get diff + let Changeset { diffs, .. } = Changeset::new(&text_src, &text_dst, LINE_ENDING); + + let mut src_counter = 1; + let mut dst_counter = 1; + + for i in 0..diffs.len() { + match diffs[i] { + Difference::Same(ref x) => { + for line in x.split("\n") { + let line_number = if line_number { + if is_color { + let style = Colour::Fixed(240); + let format = style.paint(format!("{:5} | ", src_counter)); + format!("{}", format) + } else { + format!("{:4} | ", src_counter) + } + } else { + "".to_string() + }; + + result.push(format!("{:}{:}", line_number, line)); + src_counter += 1; + dst_counter += 1; + } + } + + Difference::Add(ref x) => { + for line in x.split("\n") { + let line_number = if line_number { + if is_color { + let style = Colour::Green; + let format = style.paint(format!("{:5} | ", dst_counter)); + format!("{}", format) + } else { + format!("{:4} | ", dst_counter) + } + } else { + "".to_string() + }; + + result.push(format!("{:}{:}", line_number, line)); + dst_counter += 1; + } + } + + Difference::Rem(ref x) => { + for line in x.split("\n") { + let line_number = if line_number { + if is_color { + let style = Colour::Red; + let format = style.paint(format!("{:5} | ", src_counter)); + format!("{}", format) + } else { + format!("{:4} | ", src_counter) + } + } else { + "".to_string() + }; + + if only_diff_line { + result.push(format!("{:}{:}", line_number, line)); + } + + src_counter += 1; + } + } + } + } + + return result; +} + +// fn generate_word_diff_printout_data( +// +// ) -> Vec { +// +// } diff --git a/src/common.rs b/src/common.rs index ce3e099..5dfb024 100644 --- a/src/common.rs +++ b/src/common.rs @@ -11,6 +11,23 @@ use std::io::prelude::*; // local module use crate::exec::CommandResult; +/// +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum DiffMode { + Disable, + Watch, + Line, + Word, +} + +/// +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum OutputMode { + Output, + Stdout, + Stderr, +} + /// pub fn now_str() -> String { let date = Local::now(); diff --git a/src/header.rs b/src/header.rs index 0614670..3aea75f 100644 --- a/src/header.rs +++ b/src/header.rs @@ -16,7 +16,8 @@ use tui::{ }; // local module -use crate::app::{ActiveArea, DiffMode, InputMode, OutputMode}; +use crate::app::{ActiveArea, InputMode}; +use crate::common::{DiffMode, OutputMode}; use crate::exec::CommandResult; //const diff --git a/src/main.rs b/src/main.rs index b97395a..092cde9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,9 +3,6 @@ // that can be found in the LICENSE file. // v0.3.12 -// TODO(blacknon): diffオプション指定時に引数でパターン分けの機能を付与させる -// - https://github.com/blacknon/hwatch/issues/98 -// - これの前に、clapのバージョンを上げてやらないとだめかも?? // TODO(blakcnon): batch modeの実装.(clapのバージョンをあげてから、diff等のオプションを指定できるようにしたほうがいいのかも??) // v0.3.13 @@ -32,6 +29,7 @@ // crate // extern crate ansi_parser; extern crate hwatch_ansi_parser as ansi_parser; +extern crate ansi_term; extern crate async_std; extern crate chrono; extern crate crossbeam_channel; @@ -64,11 +62,12 @@ use std::sync::{Arc, RwLock}; use crossbeam_channel::unbounded; use std::thread; use std::time::Duration; -use app::DiffMode; +use common::DiffMode; // local modules mod ansi; mod app; +mod batch; mod common; mod event; mod exec; @@ -129,12 +128,13 @@ fn build_app() -> clap::Command { // -- flags -- // Enable batch mode option // [-b,--batch] - // .arg( - // Arg::with_name("batch") - // .help("output exection results to stdout") - // .short('b') - // .long("batch"), - // ) + .arg( + Arg::new("batch") + .help("output exection results to stdout") + .short('b') + .action(ArgAction::SetTrue) + .long("batch"), + ) // Beep option // [-B,--beep] .arg( @@ -187,7 +187,6 @@ fn build_app() -> clap::Command { .long("no-help-banner") .action(ArgAction::SetTrue), ) - // exec flag. // [-x,--exec] .arg( @@ -198,6 +197,18 @@ fn build_app() -> clap::Command { .long("exec"), ) + // output only flag. + // [-O,--diff-output-only] + .arg( + Arg::new("diff_output_only") + .help("Display only the lines with differences during line diff and word diff.") + .short('O') + .long("diff-output-only") + .requires("differences") + .action(ArgAction::SetTrue), + + ) + // -- options -- // Option to specify the command to be executed when the output fluctuates. // [-A,--aftercommand] @@ -250,7 +261,7 @@ fn build_app() -> clap::Command { .arg( Arg::new("tab_size") .help("Specifying tab display size") - .long("tab_size") + .long("tab-size") .value_parser(clap::value_parser!(u16)) .action(ArgAction::Append) .default_value("4"), @@ -268,6 +279,16 @@ fn build_app() -> clap::Command { .default_value_ifs([("differences", ArgPredicate::IsPresent, None)]) .action(ArgAction::Append), ) + .arg( + Arg::new("output") + .help("Select command output.") + .short('o') + .long("output") + .num_args(0..=1) + .value_parser(["output", "stdout", "stderr"]) + .default_value("output") + .action(ArgAction::Append), + ) } fn get_clap_matcher() -> clap::ArgMatches { @@ -294,7 +315,7 @@ fn main() { let matcher = get_clap_matcher(); // Get options flag - // let batch = matcher.get_one("batch"); + let batch = matcher.get_flag("batch"); let after_command = matcher.get_one::("after_command"); let logfile = matcher.get_one::("logfile"); @@ -338,6 +359,13 @@ fn main() { // tab size let tab_size = *matcher.get_one::("tab_size").unwrap_or(&DEFAULT_TAB_SIZE); + let output_mode = match matcher.get_one::("output").unwrap().as_str() { + "output" => common::OutputMode::Output, + "stdout" => common::OutputMode::Stdout, + "stderr" => common::OutputMode::Stderr, + _ => common::OutputMode::Output, + }; + // diff mode let diff_mode = if matcher.contains_id("differences") { match matcher.get_one::("differences").unwrap().as_str() { @@ -381,41 +409,59 @@ fn main() { } // check batch mode - // if !batch { - // is watch mode - // Create view - let mut view = view::View::new(interval.clone()) - // Set interval on view.header - .set_interval(interval) - .set_tab_size(tab_size) - .set_beep(matcher.get_flag("beep")) - .set_mouse_events(matcher.get_flag("mouse")) - - // Set color in view - .set_color(matcher.get_flag("color")) - - // Set line number in view - .set_line_number(matcher.get_flag("line_number")) - - // Set diff(watch diff) in view - .set_diff_mode(diff_mode) - .set_show_ui(!matcher.get_flag("no_title")) - .set_show_help_banner(!matcher.get_flag("no_help_banner")); - - // Set logfile - if let Some(logfile) = logfile { - view = view.set_logfile(logfile.to_string()); - } + if !batch { + // is watch mode + // Create view + let mut view = view::View::new(interval.clone()) + // Set interval on view.header + .set_interval(interval) + .set_tab_size(tab_size) + .set_beep(matcher.get_flag("beep")) + .set_mouse_events(matcher.get_flag("mouse")) + + // Set color in view + .set_color(matcher.get_flag("color")) + + // Set line number in view + .set_line_number(matcher.get_flag("line_number")) + + // Set output in view + .set_output_mode(output_mode) + + // Set diff(watch diff) in view + .set_diff_mode(diff_mode) + .set_only_diffline(matcher.get_flag("diff_output_only")) + + .set_show_ui(!matcher.get_flag("no_title")) + .set_show_help_banner(!matcher.get_flag("no_help_banner")); + + // Set logfile + if let Some(logfile) = logfile { + view = view.set_logfile(logfile.to_string()); + } - // Set after_command - if let Some(after_command) = after_command { - view = view.set_after_command(after_command.to_string()); - } + // Set after_command + if let Some(after_command) = after_command { + view = view.set_after_command(after_command.to_string()); + } + + // start app. + let _res = view.start(tx, rx); + } else { + // is batch mode + let mut batch = batch::Batch::new(tx, rx) + .set_beep(matcher.get_flag("beep")) + .set_output_mode(output_mode) + .set_diff_mode(diff_mode) + .set_line_number(matcher.get_flag("line_number")) + .set_only_diffline(matcher.get_flag("diff_output_only")); + + // Set after_command + if let Some(after_command) = after_command { + batch = batch.set_after_command(after_command.to_string()); + } - // start app. - let _res = view.start(tx, rx); - // } else { - // // is batch mode - // println!("is batch (developing now)"); - // } + // start batch. + let _res = batch.run(); + } } diff --git a/src/output.rs b/src/output.rs index 628d498..790b20d 100644 --- a/src/output.rs +++ b/src/output.rs @@ -7,7 +7,7 @@ use ansi_parser::{AnsiParser, AnsiSequence, Output}; use difference::{Changeset, Difference}; use heapless::consts::*; use regex::Regex; -use std::borrow::Cow; +use std::{borrow::Cow, vec}; use std::cmp; use std::fmt::Write; use tui::{ @@ -15,10 +15,33 @@ use tui::{ text::Span, prelude::Line, }; +use ansi_term::Colour; // local const use crate::ansi; use crate::LINE_ENDING; +use crate::DEFAULT_TAB_SIZE; + +// local module +use crate::common::{DiffMode, OutputMode}; +use crate::exec::CommandResult; + +// output return type +enum PrintData<'a> { + Lines(Vec>), + Strings(Vec), +} + +enum PrintElementData<'a> { + Line(Line<'a>), + String(String), +} + +enum DifferenceType { + Same, + Add, + Rem, +} pub trait StringExt { fn expand_tabs(&self, tab_size: u16) -> Cow; @@ -61,48 +84,258 @@ where } } -/// -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); - } +pub struct Printer { + // diff mode. + diff_mode: DiffMode, - result_vec.join("\n") -} + // output mode. + output_mode: OutputMode, -// plane output -// ========== -/// -pub fn get_plane_output<'a>( - color: bool, - line_number: bool, - base_text: &str, + // batch mode. + is_batch: bool, + + // color mode. + is_color: bool, + + // line number. + is_line_number: bool, + + // is filtered text. is_filter: bool, + + // is regex filter text. is_regex_filter: bool, - filtered_text: &str, + + // filter text. + filter_text: String, + + // is only print different line. + is_only_diffline: bool, + + // tab size. tab_size: u16, -) -> Vec> { - let text = &expand_line_tab(base_text, tab_size); +} + +impl Printer { + pub fn new() -> Self { + Self { + diff_mode: DiffMode::Disable, + output_mode: OutputMode::Output, + is_batch: false, + is_color: false, + is_line_number: false, + is_filter: false, + is_regex_filter: false, + filter_text: "".to_string(), + is_only_diffline: false, + tab_size: DEFAULT_TAB_SIZE, + } + } + + /// + pub fn get_watch_text<'a>(&mut self, dest: CommandResult, src: CommandResult) -> Vec> { + // set new text(text_dst) + let text_dest = match self.output_mode { + OutputMode::Output => dest.output, + OutputMode::Stdout => dest.stdout, + OutputMode::Stderr => dest.stderr, + }; + + // set old text(text_src) + let text_src = match self.output_mode { + OutputMode::Output => src.output, + OutputMode::Stdout => src.stdout, + OutputMode::Stderr => src.stderr, + }; + + let data = match self.diff_mode { + DiffMode::Disable => self.gen_plane_output(&text_dest), + DiffMode::Watch => self.gen_watch_diff_output(&text_dest, &text_src), + DiffMode::Line => self.gen_line_diff_output(&text_dest, &text_src), + DiffMode::Word => self.gen_plane_output(&text_dest), + }; + + if let PrintData::Lines(result) = data { + return result; + } else { + return vec![]; + } + } + + /// + pub fn get_batch_text(&mut self, dest: CommandResult, src: CommandResult) -> Vec { + // set new text(text_dst) + let mut text_dest = match self.output_mode { + OutputMode::Output => dest.output, + OutputMode::Stdout => dest.stdout, + OutputMode::Stderr => dest.stderr, + }; + + // set old text(text_src) + let mut text_src = match self.output_mode { + OutputMode::Output => src.output, + OutputMode::Stdout => src.stdout, + OutputMode::Stderr => src.stderr, + }; + + if !self.is_color { + text_dest = get_ansi_strip_str(&text_dest); + text_src = get_ansi_strip_str(&text_src); + } + + let data = match self.diff_mode { + DiffMode::Disable => self.gen_plane_output(&text_dest), + DiffMode::Watch => self.gen_watch_diff_output(&text_dest, &text_src), + DiffMode::Line => self.gen_line_diff_output(&text_dest, &text_src), + DiffMode::Word => self.gen_plane_output(&text_dest), + }; + + if let PrintData::Strings(result) = data { + return result; + } else { + return vec![]; + } + } + + // Plane Output + // ==================== + /// generate output at DiffMOde::Disable + fn gen_plane_output<'a>(&mut self, dest: &str) -> PrintData<'a> { + // tab expand + let mut text = dest.to_string(); + if !self.is_batch { + text = expand_line_tab(dest, self.tab_size); + } + + if self.is_batch { + if self.is_filter && !self.is_color { + return self.create_plane_output_batch_with_filter(&text); + } else { + return self.create_plane_output_batch(&text); + } + } else { + if self.is_filter && !self.is_color { + return self.create_plane_output_watch_with_filter(&text); + } else { + return self.create_plane_output_watch(&text); + } + } + } + + /// create filter pattern + fn create_filter_pattern(&mut self) -> Regex { + // set filter regex. + let mut pattern: Regex = Regex::new(&self.filter_text).unwrap(); + if !self.is_color && self.is_filter { + pattern = Regex::new(®ex::escape(&self.filter_text)).unwrap(); + + if self.is_regex_filter { + pattern = Regex::new(&self.filter_text).unwrap(); + } + } + + return pattern; + } + + /// + fn create_plane_output_batch<'a>(&mut self, text: &str) -> PrintData<'a> { + // + let mut result = Vec::new(); + + // + let header_width = text.split('\n').count().to_string().chars().count(); + let mut counter = 1; + + // split line + for l in text.split('\n') { + let mut line = String::new(); + if self.is_line_number { + line.push_str( + &gen_counter_str(self.is_color, counter, header_width, DifferenceType::Same) + ); + } + + line.push_str(&l); + result.push(line); + + counter += 1; + } + + return PrintData::Strings(result); + } + + /// + fn create_plane_output_batch_with_filter<'a>(&mut self, text: &str) -> PrintData<'a> { + // @TODO: filterを適用するオプションをそのうち作る + // + let mut result = Vec::new(); + + // + let header_width = text.split('\n').count().to_string().chars().count(); + let mut counter = 1; + + // split line + for l in text.split('\n') { + let mut line = String::new(); + if self.is_line_number { + line.push_str(&gen_counter_str(self.is_color, counter, header_width, DifferenceType::Same)); + } + + line.push_str(&l); + result.push(line); + + counter += 1; + } + + return PrintData::Strings(result); + } + + /// + fn create_plane_output_watch<'a>(&mut self, text: &str) -> PrintData<'a> { + // + let mut result = Vec::new(); + + // + let header_width = text.split('\n').count().to_string().chars().count(); + let mut counter = 1; + + // split line + for l in text.split('\n') { + let mut line = vec![]; + + if self.is_line_number { + line.push(Span::styled( + format!("{counter:>header_width$} | "), + Style::default().fg(Color::DarkGray), + )); + } - // set result `output_data`. - let mut output_data = vec![]; + if self.is_color { + let data = ansi::bytes_to_text(format!("{l}\n").as_bytes()); - // set filter regex. - let mut pattern: Regex = Regex::new(filtered_text).unwrap(); - if !color && is_filter { - pattern = Regex::new(®ex::escape(filtered_text)).unwrap(); + for d in data.lines { + line.extend(d.spans); + } + } else { + line.push(Span::from(String::from(l))); + } - if is_regex_filter { - pattern = Regex::new(filtered_text).unwrap(); + result.push(Line::from(line)); + counter += 1; } + + return PrintData::Lines(result); } - if is_filter && !color { + /// + fn create_plane_output_watch_with_filter<'a>(&mut self, text: &str) -> PrintData<'a> { + let mut result = Vec::new(); + // line_span is vec for span on a line-by-line basis let mut line_span = vec![]; + let pattern = self.create_filter_pattern(); + // line number let mut counter = 1; let header_width = &text.split('\n').clone().count().to_string().chars().count(); @@ -124,12 +357,12 @@ pub fn get_plane_output<'a>( { if before_range_count > 0 { let line_data = line_span.clone(); - output_data.push(Line::from(line_data)); + result.push(Line::from(line_data)); line_span = vec![]; counter += 1; } - if line_number && line_span.is_empty() { + if self.is_line_number && line_span.is_empty() { line_span.push(Span::styled( format!("{counter:>header_width$} | "), Style::default().fg(Color::DarkGray), @@ -143,7 +376,7 @@ pub fn get_plane_output<'a>( // split newline to Spans, at range_text for (range_count, text_line) in range_text.split('\n').enumerate() { if range_count > 0 { - if line_number && line_span.is_empty() { + if self.is_line_number && line_span.is_empty() { line_span.push(Span::styled( format!("{counter:>header_width$} | "), Style::default().fg(Color::DarkGray), @@ -151,7 +384,7 @@ pub fn get_plane_output<'a>( } let line_data = line_span.clone(); - output_data.push(Line::from(line_data)); + result.push(Line::from(line_data)); line_span = vec![]; counter += 1; } @@ -176,7 +409,7 @@ pub fn get_plane_output<'a>( line_span = vec![]; } - if line_number && last_str_line_span.is_empty() { + if self.is_line_number && last_str_line_span.is_empty() { last_str_line_span.push(Span::styled( format!("{counter:>header_width$} | "), Style::default().fg(Color::DarkGray), @@ -184,380 +417,613 @@ pub fn get_plane_output<'a>( } last_str_line_span.push(Span::from(String::from(last_str_line))); - output_data.push(Line::from(last_str_line_span)); + result.push(Line::from(last_str_line_span)); counter += 1; } - } else { - let lines = text.split('\n'); - let mut counter = 1; - let header_width = lines.clone().count().to_string().chars().count(); + return PrintData::Lines(result); + } - for l in lines { - let mut line_span = vec![]; + // Watch Diff Output + // ==================== + /// generate output at DiffMOde::Watch + fn gen_watch_diff_output<'a>(&mut self, dest: &str, src: &str) -> PrintData<'a> { + // tab expand dest + let mut text_dest = dest.to_string(); + if !self.is_batch { + text_dest = expand_line_tab(dest, self.tab_size); + } - if line_number { - line_span.push(Span::styled( - format!("{counter:>header_width$} | "), - Style::default().fg(Color::DarkGray), - )); + // tab expand src + let mut text_src = src.to_string(); + if !self.is_batch { + text_src = expand_line_tab(src, self.tab_size); + } + + // create text vector + let mut vec_src: Vec<&str> = text_src.lines().collect(); + let mut vec_dest: Vec<&str> = text_dest.lines().collect(); + + // get max line + let max_line = cmp::max(vec_src.len(), vec_dest.len()); + + let mut counter = 1; + let header_width = max_line.to_string().chars().count(); + + // for diff lines + let mut result = vec![]; + for i in 0..max_line { + // push empty line + if vec_src.len() <= i { + vec_src.push(""); + } + if vec_dest.len() <= i { + vec_dest.push(""); } - if color { - let data = ansi::bytes_to_text(format!("{l}\n").as_bytes()); + let src_line = vec_src[i]; + let dest_line = vec_dest[i]; - for d in data.lines { - line_span.extend(d.spans); + let mut line_data = match self.is_color { + false => self.create_watch_diff_output_line(&src_line, &dest_line), + true => { + if self.is_batch { + self.create_watch_diff_output_line(&src_line, &dest_line) + } else { + self.create_watch_diff_output_line_with_ansi_for_watch(&src_line, &dest_line) + } + }, + }; + + if self.is_line_number { + match line_data { + PrintElementData::Line(ref mut line) => { + line.spans.insert( + 0, + Span::styled( + format!("{counter:>header_width$} | "), + Style::default().fg(Color::DarkGray), + ), + ); + } + PrintElementData::String(ref mut line) => { + line.insert_str( + 0, + &gen_counter_str(self.is_color, counter, header_width, DifferenceType::Same) + ); + } } - } else { - line_span.push(Span::from(String::from(l))); - } + }; - output_data.push(Line::from(line_span)); + result.push(line_data); counter += 1; } + + return expand_print_element_data(self.is_batch, result); + + } - output_data -} + /// + fn create_watch_diff_output_line<'a>(&mut self, src_line: &str, dest_line: &str) -> PrintElementData<'a> { + if src_line == dest_line { + if self.is_batch { + return PrintElementData::String(dest_line.to_string()); + } else { + let line = Line::from(String::from(dest_line)); + return PrintElementData::Line(line); + } + } -// watch diff -// ========== + // Decompose lines by character. + let mut src_chars: Vec = src_line.chars().collect(); + let mut dest_chars: Vec = dest_line.chars().collect(); -/// -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); + // 00a0 ... non-breaking space. watch mode only. + // NOTE: Used because tui-rs skips regular space characters. + let space: char = '\u{00a0}'; - let mut result = vec![]; + // max char + let max_char = cmp::max(src_chars.len(), dest_chars.len()); + + let mut result_spans = vec![]; + let mut result_chars = vec![]; + + let mut is_escape = false; + let mut escape_code = "".to_string(); + for x in 0..max_char { + if src_chars.len() <= x { + src_chars.push(space); + } - // output to vector - let mut old_vec: Vec<&str> = old_text.lines().collect(); - let mut new_vec: Vec<&str> = new_text.lines().collect(); + if dest_chars.len() <= x { + dest_chars.push(space); + } - // get max line - let max_line = cmp::max(old_vec.len(), new_vec.len()); + let src_char = src_chars[x]; + let dest_char = dest_chars[x]; - let mut counter = 1; - let header_width = max_line.to_string().chars().count(); + if src_char != dest_char { + // create spans + let span_data = if dest_char == space { + Span::from(' '.to_string()) + } else { + Span::styled( + dest_chars[x].to_string(), + Style::default().add_modifier(Modifier::REVERSED), + ) + }; + result_spans.push(span_data); + + // create chars + let ansi_escape_sequence = '\x1b'; + let char_data = if dest_char == space { + ' ' + } else { + dest_chars[x] + }; + + if char_data == ansi_escape_sequence { + escape_code = "".to_string(); + escape_code.push(char_data); + is_escape = true; + } else if is_escape { + escape_code.push(char_data); + if char_data == 'm' { + is_escape = false; + } + for c in escape_code.chars() { + result_chars.push(c); + } + } else { + // let ansi_revese_style = ansi_term::Style::new().reverse(); + let ansi_reverse = format!("\x1b[7m{char_data}\x1b[7m"); + for c in ansi_reverse.chars() { + result_chars.push(c); + } + } + } else { + // create spans + result_spans.push(Span::styled(dest_chars[x].to_string(), Style::default())); + + // create chars + result_chars.push(dest_chars[x]); + } - // for diff lines - for i in 0..max_line { - // push empty line - if old_vec.len() <= i { - old_vec.push(""); } - if new_vec.len() <= i { - new_vec.push(""); + if self.is_batch { + let mut data_str: String = result_chars.iter().collect(); + data_str.push_str("\x1b[0m"); + return PrintElementData::String(data_str); + } else { + return PrintElementData::Line(Line::from(result_spans)) } + } - let old_line = old_vec[i]; - let new_line = new_vec[i]; - - let mut line_data = match color { - false => get_watch_diff_line(&old_line, &new_line), - true => get_watch_diff_line_with_ansi(&old_line, &new_line), - }; + /// + fn create_watch_diff_output_line_with_ansi_for_watch<'a>(&mut self, src_line: &str, dest_line: &str) -> PrintElementData<'a> { + // If the contents are the same line. + if src_line == dest_line { + let new_spans = ansi::bytes_to_text(format!("{dest_line}\n").as_bytes()); + if let Some(spans) = new_spans.into_iter().next() { + return PrintElementData::Line(spans); + } + } - if line_number { - line_data.spans.insert( - 0, - Span::styled( - format!("{counter:>header_width$} | "), - Style::default().fg(Color::DarkGray), - ), - ); + let src_colored_spans = gen_ansi_all_set_str(src_line); + let dest_colored_spans = gen_ansi_all_set_str(dest_line); + let mut src_spans = vec![]; + for mut src_span in src_colored_spans { + src_spans.append(&mut src_span); + } + let mut dest_spans = vec![]; + for mut dest_span in dest_colored_spans { + dest_spans.append(&mut dest_span); } - result.push(line_data); + // 00a0 ... non-breaking space. + // NOTE: Used because tui-rs skips regular space characters. + let space = '\u{00a0}'.to_string(); + let max_span = cmp::max(src_spans.len(), dest_spans.len()); + // + let mut result = vec![]; + for x in 0..max_span { + // + if src_spans.len() <= x { + src_spans.push(Span::from(space.to_string())); + } - counter += 1; - } + // + if dest_spans.len() <= x { + dest_spans.push(Span::from(space.to_string())); + } - result -} + // + if src_spans[x].content != dest_spans[x].content || src_spans[x].style != dest_spans[x].style + { + if dest_spans[x].content == space { + let mut data = Span::from(' '.to_string()); + data.style = Style::default().add_modifier(Modifier::REVERSED); + dest_spans[x] = data; + } else { + // add span + dest_spans[x].style = dest_spans[x] + .style + .patch(Style::default().add_modifier(Modifier::REVERSED)); + } + } -/// -fn get_watch_diff_line<'a>(old_line: &str, new_line: &str) -> Line<'a> { - // If the contents are the same line. - if old_line == new_line { - return Line::from(String::from(new_line)); - } + result.push(dest_spans[x].clone()); + } - // Decompose lines by character. - let mut old_line_chars: Vec = old_line.chars().collect(); - let mut new_line_chars: Vec = new_line.chars().collect(); + result.push(Span::styled(space, Style::default())); - // 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()); + return PrintElementData::Line(Line::from(result)); - let mut _result = vec![]; - for x in 0..max_char { - if old_line_chars.len() <= x { - old_line_chars.push(space); + } + + // Line Diff Output + // ==================== + /// + fn gen_line_diff_output<'a>(&mut self, dest: &str, src: &str) -> PrintData<'a> { + // tab expand dest + let mut text_dest = dest.to_string(); + if !self.is_batch { + text_dest = expand_line_tab(dest, self.tab_size); } - if new_line_chars.len() <= x { - new_line_chars.push(space); + // tab expand src + let mut text_src = src.to_string(); + if !self.is_batch { + text_src = expand_line_tab(src, self.tab_size); } - let old_char = old_line_chars[x]; - let new_char = new_line_chars[x]; + // Create changeset + let Changeset { diffs, .. } = Changeset::new(&text_src, &text_dest, LINE_ENDING); - if old_char != new_char { - let mut data: Span; - if new_char == space { - data = Span::from(' '.to_string()); - data.style = Style::default().add_modifier(Modifier::REVERSED); - } else { - data = Span::styled( - new_line_chars[x].to_string(), - Style::default().add_modifier(Modifier::REVERSED), - ); + // src and dest text's line count. + let src_len = &text_src.lines().count(); + let dest_len = &text_dest.lines().count(); + + // get line_number width + let header_width = cmp::max(src_len, dest_len).to_string().chars().count(); + + // line_number counter + let mut src_counter = 1; + let mut dest_counter = 1; + + // create result + let mut result_line = vec![]; + let mut result_str = vec![]; + + (0..diffs.len()).for_each(|i| { + match diffs[i] { + // Same line. + Difference::Same(ref diff_data) => { + for l in diff_data.lines() { + let data = self.gen_line_diff_linedata_from_diffs_str( + l, + DifferenceType::Same, + dest_counter, + header_width, + ); + + if !self.is_only_diffline { + match data { + PrintElementData::String(data_str) => result_str.push(data_str), + PrintElementData::Line(data_line) => result_line.push(data_line), + } + } + + // add counter + src_counter += 1; + dest_counter += 1; + } + } + + // Add line. + Difference::Add(ref diff_data) => { + for l in diff_data.lines() { + let data = self.gen_line_diff_linedata_from_diffs_str( + l, + DifferenceType::Add, + dest_counter, + header_width, + ); + + match data { + PrintElementData::String(data_str) => result_str.push(data_str), + PrintElementData::Line(data_line) => result_line.push(data_line), + } + + // add counter + dest_counter += 1; + } + } + + // Remove line. + Difference::Rem(ref diff_data) => { + for l in diff_data.lines() { + let data = self.gen_line_diff_linedata_from_diffs_str( + l, + DifferenceType::Rem, + src_counter, + header_width, + ); + + match data { + PrintElementData::String(data_str) => result_str.push(data_str.trim_end().to_string()), + PrintElementData::Line(data_line) => result_line.push(data_line), + } + + // add counter + src_counter += 1; + } + } } - // add span - _result.push(data); + }); + + if self.is_batch { + return PrintData::Strings(result_str); } else { - // add span - _result.push(Span::styled( - new_line_chars[x].to_string(), - Style::default(), - )); + return PrintData::Lines(result_line); } } - // last char - // NOTE: NBSP used as tui-rs trims regular spaces. - _result.push(Span::styled(space.to_string(), Style::default())); + /// + fn gen_line_diff_linedata_from_diffs_str<'a>( + &mut self, + diff_line: &str, + diff_type: DifferenceType, + line_number: i32, + header_width: usize, + ) -> PrintElementData<'a> { + // + let line_header: &str; + let tui_line_style: Style; + let tui_line_header_style: Style; + let str_line_style: ansi_term::Style; + // let str_line_header_style: ansi_term::Style; + + match diff_type { + DifferenceType::Same => { + line_header = " "; + tui_line_style = Style::default(); + tui_line_header_style = Style::default().fg(Color::DarkGray); + str_line_style = ansi_term::Style::new(); + // str_line_header_style = ansi_term::Style::new().fg(Colour::Fixed(240)); + }, + + DifferenceType::Add => { + line_header = "+ "; + tui_line_style = Style::default().fg(Color::Green); + tui_line_header_style = Style::default().fg(Color::Rgb(56, 119, 120)); + str_line_style = ansi_term::Style::new().fg(Colour::Green); + // str_line_header_style = ansi_term::Style::new().fg(Colour::RGB(56, 119, 120)); + }, + + DifferenceType::Rem => { + line_header = "- "; + tui_line_style = Style::default().fg(Color::Red); + tui_line_header_style = Style::default().fg(Color::Rgb(118, 0, 0)); + str_line_style = ansi_term::Style::new().fg(Colour::Red); + // str_line_header_style = ansi_term::Style::new().fg(Colour::RGB(118, 0, 0)); + }, + }; - Line::from(_result) -} + // create result_line + let mut result_line = match diff_type { + DifferenceType::Same => { + if self.is_color { + let mut colored_span = vec![Span::from(line_header)]; + let colored_data = ansi::bytes_to_text(format!("{diff_line}\n").as_bytes()); + for d in colored_data.lines { + for x in d.spans { + colored_span.push(x); + } + } + Line::from(colored_span) + } else { + Line::from(format!("{line_header}{diff_line}\n")) + } -/// -fn get_watch_diff_line_with_ansi<'a>(old_line: &str, new_line: &str) -> Line<'a> { - // If the contents are the same line. - if old_line == new_line { - let new_spans = ansi::bytes_to_text(format!("{new_line}\n").as_bytes()); - if let Some(spans) = new_spans.into_iter().next() { - return spans; - } - } + }, - let old_colored_spans = gen_ansi_all_set_str(old_line); - let new_colored_spans = gen_ansi_all_set_str(new_line); - let mut old_spans = vec![]; - for mut old_span in old_colored_spans { - old_spans.append(&mut old_span); - // break; - } - let mut new_spans = vec![]; - for mut new_span in new_colored_spans { - new_spans.append(&mut new_span); - // break; - } + _ => { + let mut line_data = diff_line.to_string(); + if self.is_color { + line_data = get_ansi_strip_str(&diff_line); + } - // 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![]; - for x in 0..max_span { - // - if old_spans.len() <= x { - old_spans.push(Span::from(space.to_string())); - } + Line::from( + Span::styled(format!("{line_header}{line_data}\n"), tui_line_style) + ) + }, + }; - // - if new_spans.len() <= x { - new_spans.push(Span::from(space.to_string())); - } + // create result_str + let mut result_str = match diff_type { + DifferenceType::Same => { + let mut line_data = format!("{line_header}{diff_line}"); + if !self.is_color { + line_data = get_ansi_strip_str(&line_data); + } + line_data + }, + + _ => { + let mut line_data = format!("{line_header}{diff_line}"); + if self.is_color { + line_data = str_line_style.paint( + get_ansi_strip_str(&format!("{line_header}{diff_line}")) + ).to_string(); + } + line_data + }, + }; - // - if old_spans[x].content != new_spans[x].content || old_spans[x].style != new_spans[x].style - { - if new_spans[x].content == space { - let mut data = Span::from(' '.to_string()); - data.style = Style::default().add_modifier(Modifier::REVERSED); - new_spans[x] = data; - } else { - // add span - new_spans[x].style = new_spans[x] - .style - .patch(Style::default().add_modifier(Modifier::REVERSED)); - } + // add line number + if self.is_line_number { + // result_line update + result_line.spans.insert( + 0, + Span::styled( + format!("{line_number:>header_width$} | "), + tui_line_header_style, + ), + ); + + result_str.insert_str( + 0, + &gen_counter_str(self.is_color, line_number as usize, header_width, diff_type) + ); } - // - _result.push(new_spans[x].clone()); + + if self.is_batch { + return PrintElementData::String(result_str.to_string().trim_end().to_string()); + } else { + return PrintElementData::Line(result_line); + } } - // last char - // NOTE: Added hidden characters as tui-rs forces trimming of end-of-line spaces. - _result.push(Span::styled(space, Style::default())); + // Word Diff Output + // ==================== + /// + /// - // - Line::from(_result) -} -// line diff -// ========== -/// -pub fn get_line_diff<'a>( - color: bool, - line_number: bool, - 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_text, &new_text, LINE_ENDING); - // old and new text's line 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(); - // line_number counter - let mut old_counter = 1; - let mut new_counter = 1; - // create result - let mut result = vec![]; - (0..diffs.len()).for_each(|i| { - match diffs[i] { - // Same line. - Difference::Same(ref diff_data) => { - 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(" ")]; - let colored_data = ansi::bytes_to_text(format!("{line}\n").as_bytes()); - for d in colored_data.lines { - for x in d.spans { - colored_span.push(x); - } - } - Line::from(colored_span) - } else { - // to string => rs-tui span. - Line::from(format!(" {line}\n")) - }; - if line_number { - data.spans.insert( - 0, - Span::styled( - format!("{new_counter:>header_width$} | "), - Style::default().fg(Color::DarkGray), - ), - ); - } - if !is_only_diffline { - result.push(data); - } - // add counter - old_counter += 1; - new_counter += 1; - } - } - // Add line. - Difference::Add(ref diff_data) => { - 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); - Line::from(Span::styled( - format!("+ {strip_str}\n"), - Style::default().fg(Color::Green), - )) - } else { - // to string => rs-tui span. - Line::from(Span::styled( - format!("+ {line}\n"), - Style::default().fg(Color::Green), - )) - }; - if line_number { - data.spans.insert( - 0, - Span::styled( - format!("{new_counter:>header_width$} | "), - Style::default().fg(Color::Rgb(56, 119, 120)), - ), - ); - } - result.push(data); + /// set diff mode. + pub fn set_diff_mode(&mut self, diff_mode: DiffMode) -> &mut Self { + self.diff_mode = diff_mode; + self + } - // add new_counter - new_counter += 1; - } - } + /// set output mode. + pub fn set_output_mode(&mut self, output_mode: OutputMode) -> &mut Self { + self.output_mode = output_mode; + self + } - // Remove line. - Difference::Rem(ref diff_data) => { - 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); - Line::from(Span::styled( - format!("- {strip_str}\n"), - Style::default().fg(Color::Red), - )) - } else { - // to string => rs-tui span. - Line::from(Span::styled( - format!("- {line}\n"), - Style::default().fg(Color::Red), - )) - }; + /// set batch mode. + pub fn set_batch(&mut self, is_batch: bool) -> &mut Self { + self.is_batch = is_batch; + self + } - if line_number { - data.spans.insert( - 0, - Span::styled( - format!("{old_counter:>header_width$} | "), - Style::default().fg(Color::Rgb(118, 0, 0)), - ), - ); - } + /// set color mode. + pub fn set_color(&mut self, is_color: bool) -> &mut Self { + self.is_color = is_color; + self + } - result.push(data); + /// set line number. + pub fn set_line_number(&mut self, is_line_number: bool) -> &mut Self { + self.is_line_number = is_line_number; + self + } - // add old_counter - old_counter += 1; - } + /// set is_filter. + pub fn set_filter(&mut self, is_filter: bool) -> &mut Self { + self.is_filter = is_filter; + self + } + + /// set diff mode. + pub fn set_regex_filter(&mut self, is_regex_filter: bool) -> &mut Self { + self.is_regex_filter = is_regex_filter; + self + } + + /// set filter text. + pub fn set_filter_text(&mut self, filter_text: String) -> &mut Self { + self.filter_text = filter_text; + self + } + + /// set diff mode. + pub fn set_only_diffline(&mut self, is_only_diffline: bool) -> &mut Self { + self.is_only_diffline = is_only_diffline; + self + } + + /// set tab size. + pub fn set_tab_size(&mut self, tab_size: u16) -> &mut Self { + self.tab_size = tab_size; + self + } +} + +/// +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); + } + + result_vec.join("\n") +} + +/// +fn gen_counter_str(is_color: bool,counter: usize, header_width: usize, diff_type: DifferenceType) -> String { + let mut counter_str = counter.to_string(); + let mut seprator = " | ".to_string(); + let mut prefix_width = 0; + let mut suffix_width = 0; + + if is_color { + let style: ansi_term::Style = match diff_type { + DifferenceType::Same => ansi_term::Style::default().fg(Colour::Fixed(240)), + DifferenceType::Add => ansi_term::Style::default().fg(Colour::RGB(56, 119, 120)), + DifferenceType::Rem => ansi_term::Style::default().fg(Colour::RGB(118, 0, 0)), + }; + counter_str = style.paint(counter_str).to_string(); + seprator = style.paint(seprator).to_string(); + prefix_width = style.prefix().to_string().len(); + suffix_width = style.suffix().to_string().len(); + + } + + let width = header_width + prefix_width + suffix_width; + format!("{counter_str:>width$}{seprator}") +} + +/// +fn expand_print_element_data(is_batch: bool, data: Vec) -> PrintData { + let mut lines = Vec::new(); + let mut strings = Vec::new(); + + for element in data { + match element { + PrintElementData::Line(line) => { + lines.push(line); + } + PrintElementData::String(string) => { + strings.push(string); } } - }); + } - result + if is_batch { + return PrintData::Strings(strings) + } else { + return PrintData::Lines(lines) + }; } // word diff @@ -1038,8 +1504,8 @@ fn gen_ansi_all_set_str<'b>(text: &str) -> Vec>> { fn get_ansi_strip_str(text: &str) -> String { let mut line_str = "".to_string(); for block in text.ansi_parse() { - if let Output::TextBlock(text) = block { - line_str.push_str(text); + if let Output::TextBlock(t) = block { + line_str.push_str(t); } } diff --git a/src/view.rs b/src/view.rs index 790720e..20c9b86 100644 --- a/src/view.rs +++ b/src/view.rs @@ -17,7 +17,8 @@ use std::{ use tui::{backend::CrosstermBackend, Terminal}; // local module -use crate::app::{App, DiffMode}; +use crate::app::App; +use crate::common::{DiffMode, OutputMode}; use crate::event::AppEvent; // local const @@ -36,7 +37,9 @@ pub struct View { show_ui: bool, show_help_banner: bool, line_number: bool, + output_mode: OutputMode, diff_mode: DiffMode, + is_only_diffline: bool, log_path: String, } @@ -53,7 +56,9 @@ impl View { show_ui: true, show_help_banner: true, line_number: false, + output_mode: OutputMode::Output, diff_mode: DiffMode::Disable, + is_only_diffline: false, log_path: "".to_string(), } } @@ -103,11 +108,21 @@ impl View { self } + pub fn set_output_mode(mut self, output_mode: OutputMode) -> Self { + self.output_mode = output_mode; + self + } + pub fn set_diff_mode(mut self, diff_mode: DiffMode) -> Self { self.diff_mode = diff_mode; self } + pub fn set_only_diffline(mut self, only_diffline: bool) -> Self { + self.is_only_diffline = only_diffline; + self + } + pub fn set_logfile(mut self, log_path: String) -> Self { self.log_path = log_path; self @@ -167,9 +182,12 @@ impl View { // set line_number app.set_line_number(self.line_number); + // set output mode + app.set_output_mode(self.output_mode); + // set diff mode app.set_diff_mode(self.diff_mode); - + app.set_is_only_diffline(self.is_only_diffline); // Run App let res = app.run(&mut terminal); From 0cc66122f7ba5f59f26d83143df328b0087a2102 Mon Sep 17 00:00:00 2001 From: blacknon Date: Sat, 6 Apr 2024 01:25:07 +0900 Subject: [PATCH 14/16] update. #24 batch mode add. word diff fix. --- src/ansi.rs | 67 ++++ src/app.rs | 39 +- src/batch.rs | 112 +----- src/output.rs | 1055 ++++++++++++++++++++++++++----------------------- 4 files changed, 629 insertions(+), 644 deletions(-) diff --git a/src/ansi.rs b/src/ansi.rs index 2ea7560..394ef30 100644 --- a/src/ansi.rs +++ b/src/ansi.rs @@ -1,6 +1,8 @@ // Copyright (c) 2024 Blacknon. // This code from https://github.com/blacknon/ansi4tui/blob/master/src/lib.rs +use ansi_parser::{AnsiParser, AnsiSequence, Output}; +use heapless::consts::*; use termwiz::cell::{Blink, Intensity, Underline}; use termwiz::color::ColorSpec; use termwiz::escape::{ @@ -140,3 +142,68 @@ pub fn bytes_to_text<'a, B: AsRef<[u8]>>(bytes: B) -> Text<'a> { spans.into() } + +// Ansi Color Code parse +// ========== + +/// Apply ANSI color code character by character. +pub fn gen_ansi_all_set_str<'b>(text: &str) -> Vec>> { + // set Result + let mut result = vec![]; + + // ansi reset code heapless_vec + let mut ansi_reset_vec = heapless::Vec::::new(); + let _ = ansi_reset_vec.push(0); + + // get ansi reset code string + let ansi_reset_seq = AnsiSequence::SetGraphicsMode(ansi_reset_vec); + let ansi_reset_seq_str = ansi_reset_seq.to_string(); + + // init sequence. + let mut sequence: AnsiSequence; + let mut sequence_str = "".to_string(); + + // text processing + let mut processed_text = vec![]; + for block in text.ansi_parse() { + match block { + Output::TextBlock(text) => { + for char in text.chars() { + let append_text = if !sequence_str.is_empty() { + format!("{sequence_str}{char}{ansi_reset_seq_str}") + } else { + format!("{char}") + }; + + // parse ansi text to tui text. + let data = bytes_to_text(format!("{append_text}\n").as_bytes()); + if let Some(d) = data.into_iter().next() { + for x in d.spans { + processed_text.push(x); + } + } + } + } + Output::Escape(seq) => { + sequence = seq; + sequence_str = sequence.to_string(); + } + } + } + + result.push(processed_text); + + result +} + +/// +pub fn get_ansi_strip_str(text: &str) -> String { + let mut line_str = "".to_string(); + for block in text.ansi_parse() { + if let Output::TextBlock(t) = block { + line_str.push_str(t); + } + } + + line_str +} diff --git a/src/app.rs b/src/app.rs index 759ef08..4512092 100644 --- a/src/app.rs +++ b/src/app.rs @@ -387,9 +387,6 @@ impl<'a> App<'a> { return; } - // text_src ... old text. - let text_src: &str; - // set target number at new history. let mut target_dst: usize = num; @@ -400,49 +397,15 @@ impl<'a> App<'a> { let previous_dst = get_results_previous_index(&results, target_dst); // set new text(text_dst) - let text_dst = match self.output_mode { - OutputMode::Output => &results[&target_dst].output, - OutputMode::Stdout => &results[&target_dst].stdout, - OutputMode::Stderr => &results[&target_dst].stderr, - }; let dest = results[&target_dst].clone(); // set old text(text_src) let mut src = CommandResult::default(); if previous_dst > 0 { src = results[&previous_dst].clone(); - match self.output_mode { - OutputMode::Output => text_src = &results[&previous_dst].output, - OutputMode::Stdout => text_src = &results[&previous_dst].stdout, - OutputMode::Stderr => text_src = &results[&previous_dst].stderr, - } - } else { - text_src = ""; } - let output_data = match self.diff_mode { - DiffMode::Disable => self.printer.get_watch_text(dest, src), - DiffMode::Watch => self.printer.get_watch_text(dest, src), - DiffMode::Line => self.printer.get_watch_text(dest, src), - - // DiffMode::Line => output::get_line_diff( - // self.ansi_color, - // self.line_number, - // self.is_only_diffline, - // text_src, - // text_dst, - // self.tab_size, - // ), - - DiffMode::Word => output::get_word_diff( - self.ansi_color, - self.line_number, - self.is_only_diffline, - text_src, - text_dst, - self.tab_size, - ), - }; + let output_data = self.printer.get_watch_text(dest, src); // TODO: output_dataのtabをスペース展開する処理を追加 diff --git a/src/batch.rs b/src/batch.rs index 90a8757..28e11cd 100644 --- a/src/batch.rs +++ b/src/batch.rs @@ -3,15 +3,13 @@ // that can be found in the LICENSE file. use crossbeam_channel::{Receiver, Sender}; -use difference::{Changeset, Difference}; use std::{io, collections::HashMap}; use std::thread; -use ansi_term::Colour; use crate::common::{DiffMode, OutputMode}; use crate::event::AppEvent; use crate::exec::{exec_after_command, CommandResult}; -use crate::{output, LINE_ENDING}; +use crate::output; /// Struct at watch view window. pub struct Batch { @@ -207,111 +205,3 @@ impl Batch { self } } - - -// fn generate_watch_diff_printout_data() -> Vec {} - - -fn generate_line_diff_printout_data( - dst: CommandResult, - src: CommandResult, - line_number: bool, - is_color: bool, - output_mode: OutputMode, - only_diff_line: bool, -) -> Vec { - let mut result: Vec = Vec::new(); - - // Switch the result depending on the output mode. - let text_dst = match output_mode { - OutputMode::Output => dst.output.clone(), - OutputMode::Stdout => dst.stdout.clone(), - OutputMode::Stderr => dst.stderr.clone(), - }; - - // Switch the result depending on the output mode. - let text_src = match output_mode { - OutputMode::Output => src.output.clone(), - OutputMode::Stdout => src.stdout.clone(), - OutputMode::Stderr => src.stderr.clone(), - }; - - // get diff - let Changeset { diffs, .. } = Changeset::new(&text_src, &text_dst, LINE_ENDING); - - let mut src_counter = 1; - let mut dst_counter = 1; - - for i in 0..diffs.len() { - match diffs[i] { - Difference::Same(ref x) => { - for line in x.split("\n") { - let line_number = if line_number { - if is_color { - let style = Colour::Fixed(240); - let format = style.paint(format!("{:5} | ", src_counter)); - format!("{}", format) - } else { - format!("{:4} | ", src_counter) - } - } else { - "".to_string() - }; - - result.push(format!("{:}{:}", line_number, line)); - src_counter += 1; - dst_counter += 1; - } - } - - Difference::Add(ref x) => { - for line in x.split("\n") { - let line_number = if line_number { - if is_color { - let style = Colour::Green; - let format = style.paint(format!("{:5} | ", dst_counter)); - format!("{}", format) - } else { - format!("{:4} | ", dst_counter) - } - } else { - "".to_string() - }; - - result.push(format!("{:}{:}", line_number, line)); - dst_counter += 1; - } - } - - Difference::Rem(ref x) => { - for line in x.split("\n") { - let line_number = if line_number { - if is_color { - let style = Colour::Red; - let format = style.paint(format!("{:5} | ", src_counter)); - format!("{}", format) - } else { - format!("{:4} | ", src_counter) - } - } else { - "".to_string() - }; - - if only_diff_line { - result.push(format!("{:}{:}", line_number, line)); - } - - src_counter += 1; - } - } - } - } - - return result; -} - -// fn generate_word_diff_printout_data( -// -// ) -> Vec { -// -// } diff --git a/src/output.rs b/src/output.rs index 790b20d..dc50952 100644 --- a/src/output.rs +++ b/src/output.rs @@ -3,9 +3,7 @@ // that can be found in the LICENSE file. // modules -use ansi_parser::{AnsiParser, AnsiSequence, Output}; use difference::{Changeset, Difference}; -use heapless::consts::*; use regex::Regex; use std::{borrow::Cow, vec}; use std::cmp; @@ -17,12 +15,27 @@ use tui::{ }; use ansi_term::Colour; +// const +const COLOR_BATCH_LINE_NUMBER_DEFAULT: Colour = Colour::Fixed(240); +const COLOR_BATCH_LINE_NUMBER_ADD: Colour = Colour::RGB(56, 119, 120); +const COLOR_BATCH_LINE_NUMBER_REM: Colour = Colour::RGB(118, 0, 0); +const COLOR_BATCH_LINE_ADD: Colour = Colour::Green; +const COLOR_BATCH_LINE_REM: Colour = Colour::Red; +const COLOR_WATCH_LINE_NUMBER_DEFAULT: Color = Color::DarkGray; +const COLOR_WATCH_LINE_NUMBER_ADD: Color = Color::Rgb(56, 119, 120); +const COLOR_WATCH_LINE_NUMBER_REM: Color = Color::Rgb(118, 0, 0); +const COLOR_WATCH_LINE_ADD: Color = Color::Green; +const COLOR_WATCH_LINE_REM: Color = Color::Red; +const COLOR_WATCH_LINE_REVERSE_FG: Color = Color::White; + // local const use crate::ansi; use crate::LINE_ENDING; use crate::DEFAULT_TAB_SIZE; // local module +use crate::ansi::gen_ansi_all_set_str; +use crate::ansi::get_ansi_strip_str; use crate::common::{DiffMode, OutputMode}; use crate::exec::CommandResult; @@ -37,6 +50,11 @@ enum PrintElementData<'a> { String(String), } +enum LineElementData<'a> { + Spans(Vec>>), + String(Vec), +} + enum DifferenceType { Same, Add, @@ -152,7 +170,7 @@ impl Printer { DiffMode::Disable => self.gen_plane_output(&text_dest), DiffMode::Watch => self.gen_watch_diff_output(&text_dest, &text_src), DiffMode::Line => self.gen_line_diff_output(&text_dest, &text_src), - DiffMode::Word => self.gen_plane_output(&text_dest), + DiffMode::Word => self.gen_word_diff_output(&text_dest, &text_src), }; if let PrintData::Lines(result) = data { @@ -187,7 +205,7 @@ impl Printer { DiffMode::Disable => self.gen_plane_output(&text_dest), DiffMode::Watch => self.gen_watch_diff_output(&text_dest, &text_src), DiffMode::Line => self.gen_line_diff_output(&text_dest, &text_src), - DiffMode::Word => self.gen_plane_output(&text_dest), + DiffMode::Word => self.gen_word_diff_output(&text_dest, &text_src), }; if let PrintData::Strings(result) = data { @@ -753,13 +771,13 @@ impl Printer { ); match data { - PrintElementData::String(data_str) => result_str.push(data_str.trim_end().to_string()), + PrintElementData::String(data_str) => result_str.push(data_str), PrintElementData::Line(data_line) => result_line.push(data_line), } // add counter src_counter += 1; - } + } } } }); @@ -784,31 +802,27 @@ impl Printer { let tui_line_style: Style; let tui_line_header_style: Style; let str_line_style: ansi_term::Style; - // let str_line_header_style: ansi_term::Style; match diff_type { DifferenceType::Same => { line_header = " "; tui_line_style = Style::default(); - tui_line_header_style = Style::default().fg(Color::DarkGray); + tui_line_header_style = Style::default().fg(COLOR_WATCH_LINE_NUMBER_DEFAULT); str_line_style = ansi_term::Style::new(); - // str_line_header_style = ansi_term::Style::new().fg(Colour::Fixed(240)); }, DifferenceType::Add => { line_header = "+ "; - tui_line_style = Style::default().fg(Color::Green); - tui_line_header_style = Style::default().fg(Color::Rgb(56, 119, 120)); - str_line_style = ansi_term::Style::new().fg(Colour::Green); - // str_line_header_style = ansi_term::Style::new().fg(Colour::RGB(56, 119, 120)); + tui_line_style = Style::default().fg(COLOR_WATCH_LINE_ADD); + tui_line_header_style = Style::default().fg(COLOR_WATCH_LINE_NUMBER_ADD); + str_line_style = ansi_term::Style::new().fg(COLOR_BATCH_LINE_ADD); }, DifferenceType::Rem => { line_header = "- "; - tui_line_style = Style::default().fg(Color::Red); - tui_line_header_style = Style::default().fg(Color::Rgb(118, 0, 0)); - str_line_style = ansi_term::Style::new().fg(Colour::Red); - // str_line_header_style = ansi_term::Style::new().fg(Colour::RGB(118, 0, 0)); + tui_line_style = Style::default().fg(COLOR_WATCH_LINE_REM); + tui_line_header_style = Style::default().fg(COLOR_WATCH_LINE_NUMBER_REM); + str_line_style = ansi_term::Style::new().fg(COLOR_BATCH_LINE_REM); }, }; @@ -890,22 +904,532 @@ impl Printer { // Word Diff Output // ==================== - /// - /// + fn gen_word_diff_output<'a>(&mut self, dest: &str, src: &str) -> PrintData<'a> { + // tab expand dest + let mut text_dest = dest.to_string(); + if !self.is_batch { + text_dest = expand_line_tab(dest, self.tab_size); + } + + // tab expand src + let mut text_src = src.to_string(); + if !self.is_batch { + text_src = expand_line_tab(src, self.tab_size); + } + + // Create changeset + let Changeset { diffs, .. } = Changeset::new(&text_src, &text_dest, LINE_ENDING); + + // src and dest text's line count. + let src_len = &text_src.lines().count(); + let dest_len = &text_dest.lines().count(); + + // get line_number width + let header_width = cmp::max(src_len, dest_len).to_string().chars().count(); + + // line_number counter + let mut src_counter = 1; + let mut dest_counter = 1; + + // create result + let mut result_line = vec![]; + let mut result_str = vec![]; + + (0..diffs.len()).for_each(|i| { + match diffs[i] { + // Same line. + Difference::Same(ref diff_data) => { + // For lines with the same data output, it is no different from line diff, so use to that function. + for l in diff_data.lines() { + let data = self.gen_line_diff_linedata_from_diffs_str( + l, + DifferenceType::Same, + dest_counter, + header_width, + ); + + if !self.is_only_diffline { + match data { + PrintElementData::String(data_str) => result_str.push(data_str), + PrintElementData::Line(data_line) => result_line.push(data_line), + } + } + + // add counter + src_counter += 1; + dest_counter += 1; + } + } + + // Add line. + Difference::Add(ref diff_data) => { + // + // + // + let data: LineElementData; + + if i > 0 { + let before_diffs = &diffs[i - 1]; + data = self.get_word_diff_linedata_from_diffs_str_add(before_diffs, diff_data.to_string()); + } else { + let mut elements_line = vec![]; + let mut elements_str = vec![]; + let str_line_style = ansi_term::Style::new().fg(COLOR_BATCH_LINE_ADD); + + for l in diff_data.lines() { + let line = l.expand_tabs(self.tab_size); + let data = if self.is_color { + get_ansi_strip_str(&line) + } else { + line.to_string() + }; + + // append elements_line + elements_line.push(vec![Span::styled( + data.to_string(), + Style::default().fg(Color::Green), + )]); + + // append elements_str + elements_str.push( + str_line_style.paint(data).to_string() + ); + } + + if self.is_batch { + data = LineElementData::String(elements_str); + } else { + data = LineElementData::Spans(elements_line); + } + } + + // batch or watch + match data { + // batch + LineElementData::String(data_str) => { + // is batch + for l in data_str { + let style = ansi_term::Style::new().fg(COLOR_BATCH_LINE_ADD); + let mut line = style.paint("+ ").to_string(); + line.push_str(&l); + + if self.is_line_number { + line.insert_str( + 0, + &gen_counter_str(self.is_color, dest_counter as usize, header_width, DifferenceType::Add) + ); + } + + result_str.push(line); + dest_counter += 1; + } + }, + + // watch + LineElementData::Spans(data_line) => { + // is watch + for l in data_line { + let mut line = vec![Span::styled("+ ", Style::default().fg(COLOR_WATCH_LINE_ADD))]; + + for d in l { + line.push(d); + } + + if self.is_line_number { + line.insert( + 0, + Span::styled( + format!("{dest_counter:>header_width$} | "), + Style::default().fg(Color::DarkGray), + ), + ); + } + + result_line.push(Line::from(line)); + dest_counter += 1; + } + }, + } + } + + // Remove line. + Difference::Rem(ref diff_data) => { + // + // + // + let data: LineElementData; + + if i > 0 { + let before_diffs = &diffs[i - 1]; + data = self.get_word_diff_linedata_from_diffs_str_rem(before_diffs, diff_data.to_string()); + } else { + let mut elements_line = vec![]; + let mut elements_str = vec![]; + let str_line_style = ansi_term::Style::new().fg(COLOR_BATCH_LINE_REM); + + for l in diff_data.lines() { + let line = l.expand_tabs(self.tab_size); + let data = if self.is_color { + get_ansi_strip_str(&line) + } else { + line.to_string() + }; + + // append elements_line + elements_line.push(vec![Span::styled( + data.to_string(), + Style::default().fg(Color::Green), + )]); + + // append elements_str + elements_str.push( + str_line_style.paint(data).to_string() + ); + } + + if self.is_batch { + data = LineElementData::String(elements_str); + } else { + data = LineElementData::Spans(elements_line); + } + } + + // batch or watch + match data { + // batch + LineElementData::String(data_str) => { + // is batch + for l in data_str { + let style = ansi_term::Style::new().fg(COLOR_BATCH_LINE_REM); + let mut line = style.paint("- ").to_string(); + line.push_str(&l); + if self.is_line_number { + line.insert_str( + 0, + &gen_counter_str(self.is_color, dest_counter as usize, header_width, DifferenceType::Add) + ); + } + + result_str.push(line); + src_counter += 1; + } + }, + + // watch + LineElementData::Spans(data_line) => { + // is watch + for l in data_line { + let mut line = vec![Span::styled("+ ", Style::default().fg(COLOR_WATCH_LINE_REM))]; + + for d in l { + line.push(d); + } + + if self.is_line_number { + line.insert( + 0, + Span::styled( + format!("{src_counter:>header_width$} | "), + Style::default().fg(Color::DarkGray), + ), + ); + } + + result_line.push(Line::from(line)); + src_counter += 1; + } + }, + } + } + } + }); + if self.is_batch { + return PrintData::Strings(result_str); + } else { + return PrintData::Lines(result_line); + } + } + + /// + fn get_word_diff_linedata_from_diffs_str_add<'a>( + &mut self, + before_diffs: &difference::Difference, + diff_data: String, + ) -> LineElementData<'a> { + // result is Vec> + // ex) + // [ // 1st line... + // [Sapn, Span, Span, ...], + // // 2nd line... + // [Sapn, Span, Span, ...], + // // 3rd line... + // [Sapn, Span, Span, ...], + // ] + // result + let mut result_data_spans: Vec> = vec![]; + let mut result_data_strs: Vec = vec![]; + + // line + let mut line_data_spans = vec![]; + let mut line_data_strs = vec![]; + + match before_diffs { + // Change Line. + Difference::Rem(before_diff_data) => { + // Craete Changeset at `Addlind` and `Before Diff Data`. + let Changeset { diffs, .. } = Changeset::new( + before_diff_data, + &diff_data, + " ", + ); + // + for c in diffs { + match c { + // Same + Difference::Same(ref char) => { + if self.is_batch { + // batch data + let str_line_style = ansi_term::Style::new() + .fg(COLOR_BATCH_LINE_ADD); + line_data_strs.push( + str_line_style.paint(char).to_string() + ); + } else { + // watch data + let same_element = get_word_diff_line_to_spans( + self.is_color, + Style::default().fg(COLOR_WATCH_LINE_ADD), + char, + ); + + for (counter, lines) in same_element.into_iter().enumerate() { + if counter > 0 { + result_data_spans.push(line_data_spans); + line_data_spans = vec![]; + } + + for l in lines { + line_data_spans.push(l.clone()); + } + } + } + } + // Add + Difference::Add(ref char) => { + if self.is_batch { + // batch data + let str_line_style = ansi_term::Style::new() + .reverse() + .fg(COLOR_BATCH_LINE_ADD); + line_data_strs.push( + str_line_style.paint(char).to_string() + ); + } else { + // watch data + let add_element = get_word_diff_line_to_spans( + self.is_color, + Style::default().fg(COLOR_WATCH_LINE_REVERSE_FG).bg(COLOR_WATCH_LINE_ADD), + char, + ); + + for (counter, lines) in add_element.into_iter().enumerate() { + if counter > 0 { + result_data_spans.push(line_data_spans); + line_data_spans = vec![]; + } + + for l in lines { + line_data_spans.push(l.clone()); + } + } + } + } + // No data. + _ => {} + } + } + } + // Add line + _ => { + for line in diff_data.lines() { + let data = if self.is_color { + get_ansi_strip_str(line) + } else { + line.to_string() + }; + if self.is_batch { + // batch data + let str_line_style = ansi_term::Style::new() + .fg(COLOR_BATCH_LINE_ADD); + result_data_strs.push(str_line_style.paint(data).to_string()); + } else { + // watch data + let line_data = vec![Span::styled( + data.to_string(), + Style::default().fg(COLOR_WATCH_LINE_ADD)), + ]; + result_data_spans.push(line_data); + } + } + } + } + if !line_data_spans.is_empty() { + result_data_spans.push(line_data_spans); + } + if self.is_batch { + result_data_strs.push(line_data_strs.join(" ")); + return LineElementData::String(result_data_strs); + } else { + return LineElementData::Spans(result_data_spans); + } + } + /// + fn get_word_diff_linedata_from_diffs_str_rem<'a>( + &mut self, + before_diffs: &difference::Difference, + diff_data: String, + ) -> LineElementData<'a> { + // result is Vec> + // ex) + // [ // 1st line... + // [Sapn, Span, Span, ...], + // // 2nd line... + // [Sapn, Span, Span, ...], + // // 3rd line... + // [Sapn, Span, Span, ...], + // ] + // result + let mut result_data_spans: Vec> = vec![]; + let mut result_data_strs: Vec = vec![]; + + // line + let mut line_data_spans = vec![]; + let mut line_data_strs = vec![]; + + match before_diffs { + // Change Line. + Difference::Add(before_diff_data) => { + // Craete Changeset at `Addlind` and `Before Diff Data`. + let Changeset { diffs, .. } = Changeset::new( + before_diff_data, + &diff_data, + " ", + ); + // + for c in diffs { + match c { + // Same + Difference::Same(ref char) => { + if self.is_batch { + // batch data + let str_line_style = ansi_term::Style::new() + .fg(COLOR_BATCH_LINE_REM); + line_data_strs.push( + str_line_style.paint(char).to_string() + ); + } else { + // watch data + let same_element = get_word_diff_line_to_spans( + self.is_color, + Style::default().fg(COLOR_WATCH_LINE_REM), + char, + ); + + for (counter, lines) in same_element.into_iter().enumerate() { + if counter > 0 { + result_data_spans.push(line_data_spans); + line_data_spans = vec![]; + } + + for l in lines { + line_data_spans.push(l.clone()); + } + } + } + } + // Add + Difference::Rem(ref char) => { + if self.is_batch { + // batch data + let str_line_style = ansi_term::Style::new() + .reverse() + .fg(COLOR_BATCH_LINE_REM); + line_data_strs.push( + str_line_style.paint(char).to_string() + ); + } else { + // watch data + let add_element = get_word_diff_line_to_spans( + self.is_color, + Style::default().fg(COLOR_WATCH_LINE_REVERSE_FG).bg(COLOR_WATCH_LINE_REM), + char, + ); + + for (counter, lines) in add_element.into_iter().enumerate() { + if counter > 0 { + result_data_spans.push(line_data_spans); + line_data_spans = vec![]; + } + + for l in lines { + line_data_spans.push(l.clone()); + } + } + } + } + // No data. + _ => {} + } + } + } + // Add line + _ => { + for line in diff_data.lines() { + let data = if self.is_color { + get_ansi_strip_str(line) + } else { + line.to_string() + }; + if self.is_batch { + // batch data + let str_line_style = ansi_term::Style::new() + .fg(COLOR_BATCH_LINE_REM); + line_data_strs.push(str_line_style.paint(data).to_string()); + } else { + // watch data + let line_data = vec![Span::styled( + data.to_string(), + Style::default().fg(COLOR_WATCH_LINE_REM)), + ]; + result_data_spans.push(line_data); + } + } + } + } + if !line_data_spans.is_empty() { + result_data_spans.push(line_data_spans); + } + if self.is_batch { + result_data_strs.push(line_data_strs.join(" ")); + return LineElementData::String(result_data_strs); + } else { + return LineElementData::Spans(result_data_spans); + } + } /// set diff mode. pub fn set_diff_mode(&mut self, diff_mode: DiffMode) -> &mut Self { @@ -979,30 +1503,6 @@ fn expand_line_tab(data: &str, tab_size: u16) -> String { result_vec.join("\n") } -/// -fn gen_counter_str(is_color: bool,counter: usize, header_width: usize, diff_type: DifferenceType) -> String { - let mut counter_str = counter.to_string(); - let mut seprator = " | ".to_string(); - let mut prefix_width = 0; - let mut suffix_width = 0; - - if is_color { - let style: ansi_term::Style = match diff_type { - DifferenceType::Same => ansi_term::Style::default().fg(Colour::Fixed(240)), - DifferenceType::Add => ansi_term::Style::default().fg(Colour::RGB(56, 119, 120)), - DifferenceType::Rem => ansi_term::Style::default().fg(Colour::RGB(118, 0, 0)), - }; - counter_str = style.paint(counter_str).to_string(); - seprator = style.paint(seprator).to_string(); - prefix_width = style.prefix().to_string().len(); - suffix_width = style.suffix().to_string().len(); - - } - - let width = header_width + prefix_width + suffix_width; - format!("{counter_str:>width$}{seprator}") -} - /// fn expand_print_element_data(is_batch: bool, data: Vec) -> PrintData { let mut lines = Vec::new(); @@ -1026,398 +1526,28 @@ fn expand_print_element_data(is_batch: bool, data: Vec) -> Pri }; } -// word diff -// ========== - -/// -pub fn get_word_diff<'a>( - color: bool, - line_number: bool, - 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_text, &new_text, LINE_ENDING); - - // old and new text's line 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(); - - // line_number counter - let mut old_counter = 1; - let mut new_counter = 1; - - // create result - let mut result = vec![]; - - for i in 0..diffs.len() { - match diffs[i] { - // Same line. - Difference::Same(ref diff_data) => { - 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(" ")]; - let colored_data = ansi::bytes_to_text(format!("{line}\n").as_bytes()); - for d in colored_data.lines { - for x in d.spans { - colored_span.push(x); - } - } - Line::from(colored_span) - } else { - // to string => rs-tui span. - Line::from(format!(" {line}\n")) - }; - - if line_number { - data.spans.insert( - 0, - Span::styled( - format!("{new_counter:>header_width$} | "), - Style::default().fg(Color::DarkGray), - ), - ); - } - - if !is_only_diffline { - result.push(data); - } - - // add counter - old_counter += 1; - new_counter += 1; - } - } - - // Add line. - Difference::Add(ref diff_data) => { - // line Spans. - // it is lines data >>> - // ex) - // [ // 1st line... - // [Sapn, Span, Span, ...], - // // 2nd line... - // [Sapn, Span, Span, ...], - // // 3rd line... - // [Sapn, Span, Span, ...], - // ] - let mut lines_data = vec![]; - - // check lines. - if i > 0 { - let before_diffs = &diffs[i - 1]; - - lines_data = get_word_diff_addline(color, before_diffs, diff_data.to_string()) - } else { - for l in diff_data.lines() { - let line = l.expand_tabs(tab_size); - let data = if color { - get_ansi_strip_str(&line) - } else { - line.to_string() - }; - lines_data.push(vec![Span::styled( - data.to_string(), - Style::default().fg(Color::Green), - )]); - } - } - - for line_data in lines_data { - let mut data = vec![Span::styled("+ ", Style::default().fg(Color::Green))]; - for line in line_data { - data.push(line); - } - - if line_number { - data.insert( - 0, - Span::styled( - format!("{new_counter:>header_width$} | "), - Style::default().fg(Color::Rgb(56, 119, 120)), - ), - ); - } - - result.push(Line::from(data.clone())); - - // add new_counter - new_counter += 1; - } - } - - // Remove line. - Difference::Rem(ref diff_data) => { - // line Spans. - // it is lines data >>> - // ex) - // [ // 1st line... - // [Sapn, Span, Span, ...], - // // 2nd line... - // [Sapn, Span, Span, ...], - // // 3rd line... - // [Sapn, Span, Span, ...], - // ] - let mut lines_data = vec![]; - - // check lines. - if diffs.len() > i + 1 { - let after_diffs: &Difference = &diffs[i + 1]; - - lines_data = get_word_diff_remline(color, after_diffs, diff_data.to_string()) - } else { - for line in diff_data.lines() { - let data = if color { - get_ansi_strip_str(line) - } else { - line.to_string() - }; - lines_data.push(vec![Span::styled( - data.to_string(), - Style::default().fg(Color::Red), - )]); - } - } - - for line_data in lines_data { - let mut data = vec![Span::styled("- ", Style::default().fg(Color::Red))]; - for line in line_data { - data.push(line); - } - - if line_number { - data.insert( - 0, - Span::styled( - format!("{old_counter:>header_width$} | "), - Style::default().fg(Color::Rgb(118, 0, 0)), - ), - ); - } - - result.push(Line::from(data.clone())); - - // add old_counter - old_counter += 1; - } - } - } - } - - result -} - -/// This Function when there is an additional line in word_diff and there is a previous diff. -/// -fn get_word_diff_addline<'a>( - color: bool, - before_diffs: &difference::Difference, - diff_data: String, -) -> Vec>> { - // result is Vec> - // ex) - // [ // 1st line... - // [Sapn, Span, Span, ...], - // // 2nd line... - // [Sapn, Span, Span, ...], - // // 3rd line... - // [Sapn, Span, Span, ...], - // ] - let mut result = vec![]; - - // line_data is Vec - // ex) [Span, Span, Span, ...] - let mut line_data = vec![]; - - match before_diffs { - // Change Line. - Difference::Rem(before_diff_data) => { - // Craete Changeset at `Addlind` and `Before Diff Data`. - let Changeset { diffs, .. } = Changeset::new(before_diff_data, &diff_data, " "); - - // - for c in diffs { - match c { - // Same - Difference::Same(ref char) => { - let same_line = get_word_diff_line_to_spans( - color, - Style::default().fg(Color::Green), - char, - ); - - for (counter, lines) in same_line.into_iter().enumerate() { - if counter > 0 { - result.push(line_data); - line_data = vec![]; - } - - for l in lines { - line_data.push(l.clone()); - } - } - } - - // Add - Difference::Add(ref char) => { - let add_line = get_word_diff_line_to_spans( - color, - Style::default().fg(Color::White).bg(Color::Green), - char, - ); - - for (counter, lines) in add_line.into_iter().enumerate() { - if counter > 0 { - result.push(line_data); - line_data = vec![]; - } - - for l in lines { - line_data.push(l.clone()); - } - } - } - - // No data. - _ => {} - } - } - } - - // Add line - _ => { - for line in diff_data.lines() { - let data = if color { - get_ansi_strip_str(line) - } else { - line.to_string() - }; - let line_data = vec![Span::styled( - data.to_string(), - Style::default().fg(Color::Green), - )]; - result.push(line_data); - } - } - } - - if !line_data.is_empty() { - result.push(line_data); - } - - result -} - /// -fn get_word_diff_remline<'a>( - color: bool, - after_diffs: &difference::Difference, - diff_data: String, -) -> Vec>> { - // result is Vec> - // ex) - // [ // 1st line... - // [Sapn, Span, Span, ...], - // // 2nd line... - // [Sapn, Span, Span, ...], - // // 3rd line... - // [Sapn, Span, Span, ...], - // ] - let mut result = vec![]; - - // line_data is Vec - // ex) [Span, Span, Span, ...] - let mut line_data = vec![]; - - match after_diffs { - // Change Line. - Difference::Add(after_diffs_data) => { - // Craete Changeset at `Addlind` and `Before Diff Data`. - let Changeset { diffs, .. } = Changeset::new(&diff_data, after_diffs_data, " "); - - // - for c in diffs { - match c { - // Same - Difference::Same(ref char) => { - let same_line = get_word_diff_line_to_spans( - color, - Style::default().fg(Color::Red), - char, - ); - - for (counter, lines) in same_line.into_iter().enumerate() { - if counter > 0 { - result.push(line_data); - line_data = vec![]; - } - - for l in lines { - line_data.push(l.clone()); - } - } - } - - // Add - Difference::Rem(ref char) => { - let add_line = get_word_diff_line_to_spans( - color, - Style::default().fg(Color::White).bg(Color::Red), - char, - ); - - for (counter, lines) in add_line.into_iter().enumerate() { - if counter > 0 { - result.push(line_data); - line_data = vec![]; - } - - for l in lines { - line_data.push(l.clone()); - } - } - } - - // No data. - _ => {} - } - } - } - - // Rem line - _ => { - for line in diff_data.lines() { - let data = if color { - get_ansi_strip_str(line) - } else { - line.to_string() - }; - - let line_data = vec![Span::styled( - data.to_string(), - Style::default().fg(Color::Red), - )]; +fn gen_counter_str(is_color: bool,counter: usize, header_width: usize, diff_type: DifferenceType) -> String { + let mut counter_str = counter.to_string(); + let mut seprator = " | ".to_string(); + let mut prefix_width = 0; + let mut suffix_width = 0; - result.push(line_data); - } - } - } + if is_color { + let style: ansi_term::Style = match diff_type { + DifferenceType::Same => ansi_term::Style::default().fg(COLOR_BATCH_LINE_NUMBER_DEFAULT), + DifferenceType::Add => ansi_term::Style::default().fg(COLOR_BATCH_LINE_NUMBER_ADD), + DifferenceType::Rem => ansi_term::Style::default().fg(COLOR_BATCH_LINE_NUMBER_REM), + }; + counter_str = style.paint(counter_str).to_string(); + seprator = style.paint(seprator).to_string(); + prefix_width = style.prefix().to_string().len(); + suffix_width = style.suffix().to_string().len(); - if !line_data.is_empty() { - result.push(line_data); } - result + let width = header_width + prefix_width + suffix_width; + format!("{counter_str:>width$}{seprator}") } /// @@ -1446,68 +1576,3 @@ fn get_word_diff_line_to_spans<'a>( result } - -// Ansi Color Code parse -// ========== - -/// Apply ANSI color code character by character. -fn gen_ansi_all_set_str<'b>(text: &str) -> Vec>> { - // set Result - let mut result = vec![]; - - // ansi reset code heapless_vec - let mut ansi_reset_vec = heapless::Vec::::new(); - let _ = ansi_reset_vec.push(0); - - // get ansi reset code string - let ansi_reset_seq = AnsiSequence::SetGraphicsMode(ansi_reset_vec); - let ansi_reset_seq_str = ansi_reset_seq.to_string(); - - // init sequence. - let mut sequence: AnsiSequence; - let mut sequence_str = "".to_string(); - - // text processing - let mut processed_text = vec![]; - for block in text.ansi_parse() { - match block { - Output::TextBlock(text) => { - for char in text.chars() { - let append_text = if !sequence_str.is_empty() { - format!("{sequence_str}{char}{ansi_reset_seq_str}") - } else { - format!("{char}") - }; - - // parse ansi text to tui text. - let data = ansi::bytes_to_text(format!("{append_text}\n").as_bytes()); - if let Some(d) = data.into_iter().next() { - for x in d.spans { - processed_text.push(x); - } - } - } - } - Output::Escape(seq) => { - sequence = seq; - sequence_str = sequence.to_string(); - } - } - } - - result.push(processed_text); - - result -} - -/// -fn get_ansi_strip_str(text: &str) -> String { - let mut line_str = "".to_string(); - for block in text.ansi_parse() { - if let Output::TextBlock(t) = block { - line_str.push_str(t); - } - } - - line_str -} From 994787a269b7b49b1caa50d1149cde807e59aba1 Mon Sep 17 00:00:00 2001 From: blacknon Date: Sun, 7 Apr 2024 01:05:31 +0900 Subject: [PATCH 15/16] update. #24 batch mode buffix. --- src/help.rs | 4 +- src/main.rs | 1 + src/output.rs | 127 +++++++++++++++++++++++++++++++++++++++++--------- 3 files changed, 107 insertions(+), 25 deletions(-) diff --git a/src/help.rs b/src/help.rs index f762c7e..b70eaff 100644 --- a/src/help.rs +++ b/src/help.rs @@ -28,10 +28,10 @@ impl<'a> HelpWindow<'a> { /// pub fn draw(&mut self, f: &mut Frame) { - let title = "help"; + let title = " [help] "; let size = f.size(); - let area = centered_rect(60, 50, size); + let area = centered_rect(80, 70, size); // create block. let block = Paragraph::new(self.data.clone()) diff --git a/src/main.rs b/src/main.rs index 092cde9..242f338 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,6 +4,7 @@ // v0.3.12 // TODO(blakcnon): batch modeの実装.(clapのバージョンをあげてから、diff等のオプションを指定できるようにしたほうがいいのかも??) +// TODO(blacknon): word diffでremove行のワードがハイライト表示されないので、原因を調べる. // v0.3.13 // TODO(blacknon): 任意時点間のdiffが行えるようにする. diff --git a/src/output.rs b/src/output.rs index dc50952..8929602 100644 --- a/src/output.rs +++ b/src/output.rs @@ -987,7 +987,7 @@ impl Printer { // append elements_line elements_line.push(vec![Span::styled( data.to_string(), - Style::default().fg(Color::Green), + Style::default().fg(COLOR_WATCH_LINE_ADD), )]); // append elements_str @@ -1016,7 +1016,7 @@ impl Printer { if self.is_line_number { line.insert_str( 0, - &gen_counter_str(self.is_color, dest_counter as usize, header_width, DifferenceType::Add) + &gen_counter_str(self.is_color, dest_counter as usize, header_width, DifferenceType::Add) ); } @@ -1078,7 +1078,7 @@ impl Printer { // append elements_line elements_line.push(vec![Span::styled( data.to_string(), - Style::default().fg(Color::Green), + Style::default().fg(COLOR_WATCH_LINE_REM), )]); // append elements_str @@ -1106,7 +1106,7 @@ impl Printer { if self.is_line_number { line.insert_str( 0, - &gen_counter_str(self.is_color, dest_counter as usize, header_width, DifferenceType::Add) + &gen_counter_str(self.is_color, src_counter as usize, header_width, DifferenceType::Rem) ); } @@ -1119,7 +1119,7 @@ impl Printer { LineElementData::Spans(data_line) => { // is watch for l in data_line { - let mut line = vec![Span::styled("+ ", Style::default().fg(COLOR_WATCH_LINE_REM))]; + let mut line = vec![Span::styled("- ", Style::default().fg(COLOR_WATCH_LINE_REM))]; for d in l { line.push(d); @@ -1130,7 +1130,7 @@ impl Printer { 0, Span::styled( format!("{src_counter:>header_width$} | "), - Style::default().fg(Color::DarkGray), + Style::default().fg(COLOR_WATCH_LINE_NUMBER_REM), ), ); } @@ -1172,7 +1172,7 @@ impl Printer { // line let mut line_data_spans = vec![]; - let mut line_data_strs = vec![]; + let mut line_data_strs = "".to_string(); match before_diffs { // Change Line. @@ -1193,9 +1193,22 @@ impl Printer { // batch data let str_line_style = ansi_term::Style::new() .fg(COLOR_BATCH_LINE_ADD); - line_data_strs.push( - str_line_style.paint(char).to_string() + + let same_element = get_word_diff_line_to_strs( + str_line_style, + char, ); + + for (counter, lines) in same_element.into_iter().enumerate() { + if counter > 0 { + result_data_strs.push(line_data_strs); + line_data_strs = "".to_string(); + } + + for l in lines { + line_data_strs.push_str(&l); + } + } } else { // watch data let same_element = get_word_diff_line_to_spans( @@ -1222,11 +1235,24 @@ impl Printer { if self.is_batch { // batch data let str_line_style = ansi_term::Style::new() - .reverse() - .fg(COLOR_BATCH_LINE_ADD); - line_data_strs.push( - str_line_style.paint(char).to_string() + .fg(COLOR_BATCH_LINE_ADD) + .reverse(); + + let add_element = get_word_diff_line_to_strs( + str_line_style, + char, ); + + for (counter, lines) in add_element.into_iter().enumerate() { + if counter > 0 { + result_data_strs.push(line_data_strs); + line_data_strs = "".to_string(); + } + + for l in lines { + line_data_strs.push_str(&l); + } + } } else { // watch data let add_element = get_word_diff_line_to_spans( @@ -1283,8 +1309,11 @@ impl Printer { result_data_spans.push(line_data_spans); } + if line_data_strs.len() > 0{ + result_data_strs.push(line_data_strs); + } + if self.is_batch { - result_data_strs.push(line_data_strs.join(" ")); return LineElementData::String(result_data_strs); } else { return LineElementData::Spans(result_data_spans); @@ -1312,7 +1341,7 @@ impl Printer { // line let mut line_data_spans = vec![]; - let mut line_data_strs = vec![]; + let mut line_data_strs = "".to_string(); match before_diffs { // Change Line. @@ -1333,9 +1362,23 @@ impl Printer { // batch data let str_line_style = ansi_term::Style::new() .fg(COLOR_BATCH_LINE_REM); - line_data_strs.push( - str_line_style.paint(char).to_string() + + let same_element = get_word_diff_line_to_strs( + str_line_style, + char, ); + + + for (counter, lines) in same_element.into_iter().enumerate() { + if counter > 0 { + result_data_strs.push(line_data_strs); + line_data_strs = "".to_string(); + } + + for l in lines { + line_data_strs.push_str(&l); + } + } } else { // watch data let same_element = get_word_diff_line_to_spans( @@ -1362,11 +1405,25 @@ impl Printer { if self.is_batch { // batch data let str_line_style = ansi_term::Style::new() - .reverse() - .fg(COLOR_BATCH_LINE_REM); - line_data_strs.push( - str_line_style.paint(char).to_string() + .fg(COLOR_BATCH_LINE_REM) + .reverse(); + + let same_element = get_word_diff_line_to_strs( + str_line_style, + char, ); + + + for (counter, lines) in same_element.into_iter().enumerate() { + if counter > 0 { + result_data_strs.push(line_data_strs); + line_data_strs = "".to_string(); + } + + for l in lines { + line_data_strs.push_str(&l); + } + } } else { // watch data let add_element = get_word_diff_line_to_spans( @@ -1406,7 +1463,7 @@ impl Printer { // batch data let str_line_style = ansi_term::Style::new() .fg(COLOR_BATCH_LINE_REM); - line_data_strs.push(str_line_style.paint(data).to_string()); + result_data_strs.push(str_line_style.paint(data).to_string()); } else { // watch data let line_data = vec![Span::styled( @@ -1423,8 +1480,11 @@ impl Printer { result_data_spans.push(line_data_spans); } + if line_data_strs.len() > 0{ + result_data_strs.push(line_data_strs); + } + if self.is_batch { - result_data_strs.push(line_data_strs.join(" ")); return LineElementData::String(result_data_strs); } else { return LineElementData::Spans(result_data_spans); @@ -1576,3 +1636,24 @@ fn get_word_diff_line_to_spans<'a>( result } + +/// +fn get_word_diff_line_to_strs( + style: ansi_term::Style, + diff_str: &str, +) -> Vec> { + // result + let mut result = vec![]; + + for l in diff_str.split('\n') { + let text = get_ansi_strip_str(l); + result.push( + vec![ + style.paint(text).to_string(), + ansi_term::Style::new().paint(" ").to_string(), + ] + ); + } + + result +} From a8549488fb2ae5b3ba6b26080aa9f238f02aa479 Mon Sep 17 00:00:00 2001 From: blacknon Date: Sun, 7 Apr 2024 16:36:29 +0900 Subject: [PATCH 16/16] update. help message update --- src/help.rs | 122 ++++++++++++++++++++++++++++++++++++++-------------- src/keys.rs | 8 ++++ src/main.rs | 1 + 3 files changed, 99 insertions(+), 32 deletions(-) create mode 100644 src/keys.rs diff --git a/src/help.rs b/src/help.rs index b70eaff..fb7cf98 100644 --- a/src/help.rs +++ b/src/help.rs @@ -2,17 +2,28 @@ // Use of this source code is governed by an MIT license // that can be found in the LICENSE file. +// TODO(blacknon): keyのhelpをテキストからテーブルにする? +// TODO(blacknon): keyの内容をカスタムし、それをhelpで出せるようにする +// TODO(blacknon): keyの内容をvecで渡してやるようにする +// TODO(blacknon): keyの内容を折り返して表示させるようにする + +use ratatui::text::Span; use tui::{ layout::{Constraint, Direction, Layout, Rect}, style::{Color, Style}, prelude::Line, - widgets::{Block, Borders, Clear, Paragraph}, + widgets::{Block, Borders, Clear, Paragraph, Wrap}, Frame, }; +use crate::keys::{self, KeyData}; + pub struct HelpWindow<'a> { /// - data: Vec>, + text: Vec>, + + /// + area: Rect, /// position: i16, @@ -21,9 +32,13 @@ pub struct HelpWindow<'a> { /// History Area Object Trait impl<'a> HelpWindow<'a> { pub fn new() -> Self { - let data = gen_help_text(); + let text = gen_help_text(); - Self { data, position: 0 } + Self { + text, + area: Rect::new(0, 0, 0, 0), + position: 0 + } } /// @@ -34,7 +49,7 @@ impl<'a> HelpWindow<'a> { let area = centered_rect(80, 70, size); // create block. - let block = Paragraph::new(self.data.clone()) + let block = Paragraph::new(self.text.clone()) .style(Style::default().fg(Color::LightGreen)) .block( Block::default() @@ -42,6 +57,7 @@ impl<'a> HelpWindow<'a> { .borders(Borders::ALL) .border_style(Style::default().fg(Color::Gray).bg(Color::Reset)), ) + .wrap(Wrap { trim: false }) .scroll((self.position as u16, 0)); f.render_widget(Clear, area); @@ -50,56 +66,98 @@ impl<'a> HelpWindow<'a> { /// pub fn scroll_up(&mut self, num: i16) { - if 0 <= self.position - num { - self.position -= num - } + self.position = std::cmp::max(0, self.position - num); } /// pub fn scroll_down(&mut self, num: i16) { // get area data size - let data_size = self.data.len() as i16; + let data_size = self.text.len() as i16; if data_size > self.position + num { self.position += num } + + if self.text.len() as i16 > self.area.height as i16 { + self.position = std::cmp::min(self.position + num, self.text.len() as i16 - self.area.height as i16); + } + } +} + +fn gen_help_text_from_key_data<'a>(data: Vec) -> Vec> { + let mut text = vec![]; + + for key_data in data { + let line1 = Line::from(vec![ + Span::styled( + " - [", + Style::default().fg(Color::LightGreen), + ), + Span::styled( + key_data.key, + Style::default().fg(Color::Yellow), + ), + Span::styled( + "] key :", + Style::default().fg(Color::LightGreen), + ) + ] + ); + + let line2 = Line::from(vec![ + Span::styled( + " ", + Style::default(), + ), + Span::styled( + key_data.description, + Style::default().fg(Color::White), + ), + ] + ); + + text.push(line1); + text.push(line2); } + + text } /// fn gen_help_text<'a>() -> Vec> { - // set help messages. - let text = vec![ - Line::from(" - [h] key ... show this help message."), + let keydata_list = vec![ + KeyData { key: "h".to_string(), description: "show this help message.".to_string() }, // toggle - Line::from(" - [c] key ... toggle color mode."), - Line::from(" - [n] key ... toggle line number."), - Line::from(" - [d] key ... switch diff mode at None, Watch, Line, and Word mode. "), - Line::from(" - [t] key ... toggle ui (history pane & header both on/off). "), - Line::from(" - [Bkspace] ... toggle history pane. "), - Line::from(" - [m] key ... toggle mouse wheel support. With this option, copying text with your terminal may be harder. Try holding the Shift key."), + KeyData { key: "c".to_string(), description: "toggle color mode.".to_string() }, + KeyData { key: "n".to_string(), description: "toggle line number.".to_string() }, + KeyData { key: "d".to_string(), description: "switch diff mode at None, Watch, Line, and Word mode.".to_string() }, + KeyData { key: "t".to_string(), description: "toggle ui (history pane & header both on/off).".to_string() }, + KeyData { key: "Bkspace".to_string(), description: "toggle history pane.".to_string() }, + KeyData { key: "m".to_string(), description: "toggle mouse wheel support. With this option, copying text with your terminal may be harder. Try holding the Shift key.".to_string() }, // exit hwatch - Line::from(" - [q] key ... exit hwatch."), + KeyData { key: "q".to_string(), description: "exit hwatch.".to_string() }, // change diff - Line::from(" - [0] key ... disable diff."), - Line::from(" - [1] key ... switch Watch type diff."), - Line::from(" - [2] key ... switch Line type diff."), - Line::from(" - [3] key ... switch Word type diff."), + KeyData { key: "0".to_string(), description: "disable diff.".to_string() }, + KeyData { key: "1".to_string(), description: "switch Watch type diff.".to_string() }, + KeyData { key: "2".to_string(), description: "switch Line type diff.".to_string() }, + KeyData { key: "3".to_string(), description: "switch Word type diff.".to_string() }, // change output - Line::from(" - [F1] key ... change output mode as stdout."), - Line::from(" - [F2] key ... change output mode as stderr."), - Line::from(" - [F3] key ... change output mode as output(stdout/stderr set.)"), + KeyData { key: "F1".to_string(), description: "change output mode as stdout.".to_string() }, + KeyData { key: "F2".to_string(), description: "change output mode as stderr.".to_string() }, + KeyData { key: "F3".to_string(), description: "change output mode as output(stdout/stderr set.).".to_string() }, // change interval - Line::from(" - [+] key ... Increase interval by .5 seconds."), - Line::from(" - [-] key ... Decrease interval by .5 seconds."), + KeyData { key: "+".to_string(), description: "Increase interval by .5 seconds.".to_string() }, + KeyData { key: "-".to_string(), description: "Decrease interval by .5 seconds.".to_string() }, // change use area - Line::from(" - [Tab] key ... toggle current area at history or watch."), + KeyData { key: "Tab".to_string(), description: "toggle current area at history or watch.".to_string() }, // filter text input - Line::from(" - [/] key ... filter history by string."), - Line::from(" - [*] key ... filter history by regex."), - Line::from(" - [ESC] key ... unfiltering."), + KeyData { key: "/".to_string(), description: "filter history by string.".to_string() }, + KeyData { key: "*".to_string(), description: "filter history by regex.".to_string() }, + KeyData { key: "ESC".to_string(), description: "unfiltering.".to_string() }, ]; + let text = gen_help_text_from_key_data(keydata_list); + text } diff --git a/src/keys.rs b/src/keys.rs new file mode 100644 index 0000000..9989eb0 --- /dev/null +++ b/src/keys.rs @@ -0,0 +1,8 @@ +// Copyright (c) 2024 Blacknon. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. + +pub struct KeyData { + pub key: String, + pub description: String, +} diff --git a/src/main.rs b/src/main.rs index 242f338..14b9de0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -75,6 +75,7 @@ mod exec; mod header; mod help; mod history; +mod keys; mod output; mod view; mod watch;