2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

hhattoのひとりアドベントカレンダーAdvent Calendar 2021

Day 11

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

Posted at

Rustでlessクローンの続き その4

こまかいブラッシュアップとか検索ジャンプ後の表示バグを修正する予定。

やじるしキーでもカーソル移動したい

以下でイベント取れるので、hjklキーの割り当てと合わせる感じで処理させます。

  • crossterm::event::KeyCode::Up
  • crossterm::event::KeyCode::Down
  • crossterm::event::KeyCode::Right
  • crossterm::event::KeyCode::Left
diff --git a/1207_less_clone/src/main.rs b/1207_less_clone/src/main.rs
index b07fe39..7925c87 100644
--- a/1207_less_clone/src/main.rs
+++ b/1207_less_clone/src/main.rs
@@ -120,11 +120,11 @@ fn less_loop(filename: &str) -> Result<()> {

             match event {
                 Event::Key(KeyEvent {
-                    code: KeyCode::Char('h'),
+                    code: KeyCode::Char('h') | KeyCode::Left,
                     modifiers: _,
                 }) => execute!(stdout(), MoveLeft(1))?,
                 Event::Key(KeyEvent {
-                    code: KeyCode::Char('j'),
+                    code: KeyCode::Char('j') | KeyCode::Down,
                     modifiers: _,
                 }) => {
                     if window_rows-3 == row {
@@ -137,7 +137,7 @@ fn less_loop(filename: &str) -> Result<()> {
                     }
                 },
                 Event::Key(KeyEvent {
-                    code: KeyCode::Char('k'),
+                    code: KeyCode::Char('k') | KeyCode::Up,
                     modifiers: _,
                 }) => {
                     if 0 == row {
@@ -150,7 +150,7 @@ fn less_loop(filename: &str) -> Result<()> {
                     }
                 },
                 Event::Key(KeyEvent {
-                    code: KeyCode::Char('l'),
+                    code: KeyCode::Char('l') | KeyCode::Right,
                     modifiers: _,
                 }) => execute!(stdout(), MoveRight(1))?,
                 Event::Key(KeyEvent {

カーソル移動でターミナルの上端下端、ファイル先端終端を考慮する

バグってただけという。

diff --git a/1207_less_clone/src/main.rs b/1207_less_clone/src/main.rs
index 7925c87..067faba 100644
--- a/1207_less_clone/src/main.rs
+++ b/1207_less_clone/src/main.rs
@@ -35,6 +35,9 @@ impl DisplayLines {
     }
 }
 
+const STATUS_LINE_OFFSET: usize = 2;
+const DISPLAY_BOTTOM_LINE_OFFSET: usize = STATUS_LINE_OFFSET + 1;
+
 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![];
@@ -49,20 +52,21 @@ fn search(filename: &str, search_word: &str) -> Result<Vec<(u64, String)>> {
 fn less_loop(filename: &str) -> Result<()> {
     let f = File::open(filename)?;
     let lines = ropey::Rope::from_reader(f)?;
-    let line_count = lines.len_lines();
+    let line_count = lines.len_lines() - 1;
     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 };
 
-    for idx in 0..window_rows {
-        println!("{}", lines.line(idx as usize));
-        execute!(stdout(), MoveTo(0, idx as u16))?;
-        if idx as usize >= line_count - 1 {
+    for idx in 0..(window_rows - STATUS_LINE_OFFSET as u16) {
+        *display_lines.end_mut() = idx as u64;
+        // NOTE: use format, because debug print
+        let disp = format!("{}", lines.line(idx as usize));
+        execute!(stdout(), MoveTo(0, idx as u16), Print(disp))?;
+        if idx >= line_count as u16 {
             break
         }
-        *display_lines.end_mut() = idx as u64;
     }
     execute!(stdout(), MoveTo(0, 0))?;
 
@@ -127,12 +131,13 @@ fn less_loop(filename: &str) -> Result<()> {
                     code: KeyCode::Char('j') | KeyCode::Down,
                     modifiers: _,
                 }) => {
-                    if window_rows-3 == row {
+                    if (window_rows - DISPLAY_BOTTOM_LINE_OFFSET as u16) == row && line_count != (display_lines.end + 1) as usize {
                         *display_lines.start_mut() = display_lines.start + 1;
                         *display_lines.end_mut() = display_lines.end + 1;
                         let l = lines.line(display_lines.end as usize);
-                        execute!(stdout(), ScrollUp(1), Print(l), RestorePosition)?;
-                    } else {
+                        // NOTE: use format because debug print
+                        execute!(stdout(), ScrollUp(1), Print(format!("{}", l)), RestorePosition)?;
+                    } else if (window_rows - DISPLAY_BOTTOM_LINE_OFFSET as u16) != row && line_count != (row + 1) as usize {
                         execute!(stdout(), MoveDown(1))?;
                     }
                 },
@@ -140,12 +145,13 @@ fn less_loop(filename: &str) -> Result<()> {
                     code: KeyCode::Char('k') | KeyCode::Up,
                     modifiers: _,
                 }) => {
-                    if 0 == row {
+                    if 0 == row && display_lines.start > 0 {
                         *display_lines.start_mut() = display_lines.start - 1;
                         *display_lines.end_mut() = display_lines.end - 1;
-                        let l = lines.line(display_lines.end as usize);
-                        execute!(stdout(), ScrollDown(1), Print(l), RestorePosition)?;
-                    } else {
+                        let l = lines.line(display_lines.start as usize);
+                        // NOTE: use format because debug print
+                        execute!(stdout(), ScrollDown(1), Print(format!("{}", l)), RestorePosition)?;
+                    } else if 0 != row {
                         execute!(stdout(), MoveUp(1))?;
                     }
                 },

とりあえず困った時は execute!(crossterm::style::Print("hogehoge"))で出力してデバッグする感じ。
結局プリントデバッグという:sweat:

ステータス用の行を追加

Vimのステータスラインが分かりやすいので、組み込んでみた。
現在の カーソル行/全行数(xx%) みたいな感じで表示する。

diff --git a/1207_less_clone/src/main.rs b/1207_less_clone/src/main.rs
index 23c8a63..f5bbadf 100644
--- a/1207_less_clone/src/main.rs
+++ b/1207_less_clone/src/main.rs
@@ -6,7 +6,7 @@ use crossterm::{
     cursor::{position, DisableBlinking, MoveTo, MoveUp, MoveDown, MoveLeft, MoveRight, RestorePosition, SavePosition},
     event::{read, Event, KeyCode, KeyEvent, KeyModifiers},
     execute,
-    style::Print,
+    style::{Color, Print, ResetColor, SetBackgroundColor},
     terminal,
     terminal::{disable_raw_mode, enable_raw_mode, Clear, ClearType, ScrollDown, ScrollUp},
     Result,
@@ -49,6 +49,50 @@ fn search(filename: &str, search_word: &str) -> Result<Vec<(u64, String)>> {
     Ok(matches)
 }
 
+fn clear_status_line() -> Result<()> {
+    let (window_columns, window_rows) = terminal::size()?;
+
+    let mut status_line = Vec::new();
+    for _ in 0..window_columns {
+        status_line.push(" ");
+    }
+
+    execute!(
+        stdout(),
+        SavePosition,
+        MoveTo(0, window_rows - STATUS_LINE_OFFSET as u16),
+        Print(String::from_iter(status_line)),
+        RestorePosition,
+    )?;
+
+    Ok(())
+}
+
+fn render_status_line(line_count: u64, max_line_count: usize) -> Result<()> {
+    let (window_columns, window_rows) = terminal::size()?;
+
+    let mut status_line = Vec::new();
+    for _ in 0..window_columns {
+        status_line.push(" ");
+    }
+
+    let percentage = line_count as f64 / max_line_count as f64 * 100.;
+
+    execute!(
+        stdout(),
+        SavePosition,
+        MoveTo(0, window_rows - STATUS_LINE_OFFSET as u16),
+        SetBackgroundColor(Color::Blue),
+        Print(String::from_iter(status_line)),
+        MoveTo(0, window_rows - STATUS_LINE_OFFSET as u16),
+        Print(format!("{}/{}({:3.0}%)", line_count, max_line_count, percentage as usize)),
+        ResetColor,
+        RestorePosition,
+    )?;
+
+    Ok(())
+}
+
 fn less_loop(filename: &str) -> Result<()> {
     let f = File::open(filename)?;
     let lines = ropey::Rope::from_reader(f)?;
@@ -71,8 +115,12 @@ fn less_loop(filename: &str) -> Result<()> {
     execute!(stdout(), MoveTo(0, 0))?;
 
     loop {
-        let event = read()?;
         let (_, row) = position()?;
+        let _ = render_status_line(display_lines.start + 1 + row as u64, line_count);
+
+        let event = read()?;
+
+        let _ = clear_status_line();
 
         if is_search_mode {
             match event {

ちょっと効率悪そうだけど、以下のような感じで実装。

  1. イベント取得直前にステータス行をレンダリング
  2. イベント取得(read())
  3. イベント取得(read())が戻ってきたら、その直後にステータス行を一旦クリア
  4. イベント毎の処理
  5. 1に戻る(これを毎ループ繰り返し)

色は crossterm::style::SetBackgroundColor(crossterm::style::Color::Blue)) 等で背景色を設定。 Print()する。crossterm::style::ResetColor で塗る感じ。

以下参照。
https://docs.rs/crossterm/0.22.1/crossterm/style/index.html

おわりに

今の動きはこんな感じ。

ステータス行はこんな感じになった。
statusline.png

というわけで今日はここまで :wave_tone3:

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

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?