Rustでlessクローン実装の3日目です。
気分を変えて検索機能を入れてみます。
初めに考えていた戦略通りripgrepを使います。
そのまえにclapバージョン3.0.0の対応
ripgrep関連の機能を入れようとcargo update
するとclapのバージョン 3.0.0-rc.1
にアップデートされてしまいました。
各機能がfeaturesに分離されたようで、そのままではビルドできなくなりました。
というわけで、以下のようなパッチを当てました。
diff --git a/1207_less_clone/Cargo.toml b/1207_less_clone/Cargo.toml
index 142d615..b131d5f 100644
--- a/1207_less_clone/Cargo.toml
+++ b/1207_less_clone/Cargo.toml
@@ -6,6 +6,6 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
-clap = "3.0.0-beta.5"
+clap = { version = "3.0.0-rc.1", features = ["derive"] }
crossterm = "0.22.1"
ropey = "1.3.1"
これでビルドできるようになりました。
ripgrep関連の機能を追加
まずはripgrepのgrepcrateを組み込みます。
diff --git a/1207_less_clone/Cargo.toml b/1207_less_clone/Cargo.toml
index b131d5f..d95294a 100644
--- a/1207_less_clone/Cargo.toml
+++ b/1207_less_clone/Cargo.toml
@@ -9,3 +9,4 @@ edition = "2021"
clap = { version = "3.0.0-rc.1", features = ["derive"] }
crossterm = "0.22.1"
ropey = "1.3.1"
+grep = "0.2"
これでgrep-XXX
なcrateを呼び出せます。
検索機能を実装してみる。
use grep::searcher::SearcherBuilder;
use grep::regex::RegexMatcher;
use grep::searcher::sinks::UTF8;
fn search(filename: &str, search_word: &str) -> Result<Vec<(u64, String)>> {
let matcher = RegexMatcher::new(search_word).unwrap();
let mut matches: Vec<(u64, String)> = vec![];
let mut searcher = SearcherBuilder::new().build();
searcher.search_path(&matcher, filename, UTF8(|lnum, line| {
matches.push((lnum, line.to_string()));
Ok(true)
}))?;
Ok(matches)
}
ripgrep系の機能は、検索機能自体?を提供するSearcher
、文字列(regexp)マッチ機能を提供するMatcher
、マッチしたときにどのように処理するかを司るSink
の三点セットになっています。
/WORD↩︎
でWORD
文字列を取り出して、この実装した fn search()
に入力します。
パッチはこんな感じ。
diff --git a/1207_less_clone/src/main.rs b/1207_less_clone/src/main.rs
index 174aefd..b975dd5 100644
--- a/1207_less_clone/src/main.rs
+++ b/1207_less_clone/src/main.rs
@@ -52,6 +52,7 @@ fn less_loop(filename: &str) -> Result<()> {
let line_count = lines.len_lines();
let mut is_search_mode = false;
+ let mut search_word_vec: Vec<char> = [].to_vec();
let (_, window_rows) = terminal::size()?;
let mut display_lines = DisplayLines { start: 0, end: 0 };
@@ -77,6 +78,40 @@ fn less_loop(filename: &str) -> Result<()> {
}) => {
is_search_mode = false;
execute!(stdout(), RestorePosition)?;
+ search_word_vec = Vec::new();
+ },
+ Event::Key(KeyEvent {
+ code: KeyCode::Enter,
+ modifiers: _,
+ }) => {
+ let search_word = String::from_iter(search_word_vec.clone());
+ let result = search(filename, search_word.as_str())?;
+ if result.len() > 0 {
+ // jump to result line
+ let (lnum, _) = result[0];
+ execute!(stdout(), SavePosition, Clear(ClearType::All))?;
+
+ for idx in lnum..(lnum+window_rows as u64) {
+ println!("{}", lines.line(idx as usize));
+ execute!(stdout(), MoveTo(0, idx as u16))?;
+ if idx as usize >= line_count - 1 {
+ break
+ }
+ *display_lines.end_mut() = idx;
+ }
+ execute!(stdout(), RestorePosition)?;
+ }
+
+ is_search_mode = false;
+ execute!(stdout(), RestorePosition)?;
+ search_word_vec = Vec::new();
+ },
+ Event::Key(KeyEvent {
+ code: KeyCode::Char(c),
+ modifiers: _,
+ }) => {
+ search_word_vec.push(c);
+ execute!(stdout(), Print(c))?;
},
_ => (),
};
1文字ずつ入力を拾って、search_word_vec
に検索ワードを入れる。
Enter入力されたらsearch_word_vec
を文字列にしてfn search()
に渡す。
fn search()
から結果が戻ってくるので、いい感じにジャンプする。(今は結果があるかチェックして0番の結果を無条件で使う感じ。)
という流れです。
ripgrepから返される行番号の型がu64
なので、そのあたりはu64
で保持するように変更しました。
動作確認
いちおここまでで検索してジャンプっぽい動きができました。
今日は時間切れでここまで。
おわりに
検索してみるとlessより断然検索が早くていい感じです!!
モチベーションがさらに上がってきました
検索後の表示周りがバグりまくってるので、明日はそのあたりを整えたいと思います。
今日の感想としては、terminalさわる系のツールはprintデバッグ派の自分には動作確認が大変。
いい感じな方法ないのかな。。
まぁ悩みつつ、テンション上げつつ明日もやっていこうと思います。