Rustでlessクローンを作りたい その3


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で保持するように変更しました。

動作確認

いちおここまでで検索してジャンプっぽい動きができました。

ただジャンプして表示するとバグりまくってましたorz

今日は時間切れでここまで。

今日の成果
https://github.com/hhatto/advent-2021/compare/3e90ddd468b1b737037cacdbe272d952b29c6d15..bdfa30aa516e6ec97ef2971486bc761923f4ded6

おわりに

検索してみるとlessより断然検索が早くていい感じです!!
モチベーションがさらに上がってきました

検索後の表示周りがバグりまくってるので、明日はそのあたりを整えたいと思います。

今日の感想としては、terminalさわる系のツールはprintデバッグ派の自分には動作確認が大変。
いい感じな方法ないのかな。。

まぁ悩みつつ、テンション上げつつ明日もやっていこうと思います。