Crosstermの概要

👉 [crossterm](https://github.com/crossterm-rs/crossterm) version = ~~“0.25.0”~~ "0.28.0"に更新
👉 [document](https://docs.rs/crossterm/latest/crossterm/)

crosstermはマルチプラットフォームで入出力系を取得したり、色を付けたりできるターミナル操作ライブラリ

基本的な特徴

- クロスプラットフォーム
- ドキュメントが豊富
- 依存が少ない
- カーソル操作
- イベントハンドリング
- 色の指定
- ターミナルの操作

この記事で扱うのはターミナルとカーソル、キー操作の一部のみ

## 基本

- 即時実行できるマクロの`execute!()`, 関数の`execute()`
- 遅延評価できるマクロの`queue!()`, 関数の`queue()`

基本はこの4つのいずれかを使って実行する

本記事では基本的にはマクロの方を利用するが関数に変えても動作する

また既存のprint!(), println!()を使っても同様のことはできる

```rust
use std::io::stdout;
use std::io::Result;

use crossterm::execute;
use crossterm::style::{Color, Print, ResetColor, SetBackgroundColor, SetForegroundColor};
use crossterm::ExecutableCommand;

fn main() -> Result<()> {
    let mut stdout = stdout();
    stdout
        .execute(SetForegroundColor(Color::Blue))?
        .execute(SetBackgroundColor(Color::Red))?
        .execute(Print("Styled text here."))?;
    // print!でも同様のコードをかける
    println!(
        "{}{} Styple text here{}",
        SetForegroundColor(Color::Red),
        SetBackgroundColor(Color::Blue),
        ResetColor
    );
    // ResetColorをしないと以降の場所でも色が適応される
    execute!(stdout, SetBackgroundColor(Color::Blue), Print(" "))?;
    println!("Hi{}There", SetForegroundColor(Color::Black));
    println!("{}", ResetColor);
    Ok(())
}
```

![スクリーンショット 2024-01-23 200410.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2630694/fff1b003-ce56-9488-fc27-ca04afedb5e9.png)

print!()とexecute!()は基本的にはどちらでも良い

だが，version="0.26.0"からはマクロを使った方がすっきりと書ける

遅延評価したい場合は`queue!()`を使う

`stdout.flush()?`で実行できる

```rust
use crossterm::style::{PrintStyledContent, Stylize};
use crossterm::{cursor, execute, queue, terminal};

fn main() -> Result<()> {
    let mut stdout = stdout();
    execute!(stdout, terminal::Clear(terminal::ClearType::All))?;
    let block = PrintStyledContent("█".magenta());
    for y in 0..20 {
        for x in 0..150 {
            if (y == 0 || y == 20 - 1) || (x == 0 || x == 150 - 1) {
                queue!(stdout, cursor::MoveTo(x, y), block)?;
            }
        }
    }
    stdout.flush()?; // ここでqueueされた順で遅延評価される
    Ok(())
}
```

たくさんの描画が必要な時には，描画するのを全てqueueしてから一気に描画した方がちらつきが少なくてすっきりと見られる

![Untitled 1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2630694/5e79f355-9225-3f3f-89a3-c3104b69c686.png)

## ターミナル操作

個人的に重要かなと思うのが以下の6つ

- EnterAlternateScreen 代替画面(バッファー)へ入る
- LeaveAlternateScreen 代替画面から出る
- enable_raw_mode rawモードの有効化
- disable_raw_mode rawモードの無効化
- Clear, ClearType 描画したのを消せる

これらの概要を説明する

### 代替画面の入出

`EnterAlternateScreen`は今開いてるターミナルとは別のバッファースクリーンを表示することができ、`LeaveAlternateScreen`をすると実行前の画面に戻ることができる

tmuxとかのターミナルエミュレータを起動し、終わった後に元の画面に戻るイメージ

なので実行したコマンドとcrosstermで行われる処理や描画を分離して、バッファーが閉じたらコマンドを実行した画面に綺麗に戻れるようになる

```diff_toml
[dependencies]
crossterm = "0.28.0"
+ rand = "0.8.5"
```

迷路を作成して元の画面に戻る

```rust
use std::io::{stdout, Result, Write};
use std::time::Duration;

use crossterm::cursor;
use crossterm::style::{PrintStyledContent, Stylize};
use crossterm::terminal::{Clear, ClearType, EnterAlternateScreen, LeaveAlternateScreen};
use crossterm::{execute, queue};
use rand::seq::SliceRandom;

fn main() -> Result<()> {
    let mut stdout = stdout();
    execute!(stdout, cursor::Hide, EnterAlternateScreen, Clear(ClearType::All))?;

    let mut maze = [[1usize; MAZE_X]; MAZE_Y];
    dig_maze(11, 11, &mut maze)?;

    execute!(stdout, cursor::Show, LeaveAlternateScreen,)?;
    Ok(())
}

enum Direction {
    Up,
    Down,
    Left,
    Right,
}
impl Direction {
    fn new_vec() -> Vec<Direction> {
        vec![Self::Up, Self::Down, Self::Left, Self::Right]
    }
}

// 穴掘り法
// ブロック(WALL)の大きさが2で掘る長さが4なのでそこだけ注意
const MAZE_Y: usize = 31;
const MAZE_X: usize = 51;
const WALL: &str = "██";
const TIME: Duration = Duration::from_millis(10); // ゆっくり見たいときにはここを大きい数字にする
fn dig_maze(mut x: usize, mut y: usize, maze: &mut [[usize; MAZE_X]; MAZE_Y]) -> Result<()> {
    maze[y][x] = 0;
    maze[y][x + 1] = 0;
    let mut direction = Direction::new_vec();
    let mut rng = rand::thread_rng();
    direction.shuffle(&mut rng);
    for d in direction.iter() {
        match d {
            Direction::Up => {
                let dy = y.checked_sub(2);
                if dy.is_none() || maze[dy.unwrap()][x] == 0 {
                    continue;
                }
                maze[y - 1][x] = 0;
                maze[y - 1][x + 1] = 0;
                maze[y - 2][x] = 0;
                maze[y - 2][x + 1] = 0;
                y -= 2;
            }
            Direction::Right => {
                if (x + 4) >= MAZE_X || maze[y][x + 4] == 0 {
                    continue;
                }
                maze[y][x + 1] = 0;
                maze[y][x + 2] = 0;
                maze[y][x + 3] = 0;
                maze[y][x + 4] = 0;
                x += 4;
            }
            Direction::Down => {
                if (y + 2) >= MAZE_Y || maze[y + 2][x] == 0 {
                    continue;
                }
                maze[y + 1][x] = 0;
                maze[y + 1][x + 1] = 0;
                maze[y + 2][x] = 0;
                maze[y + 2][x + 1] = 0;
                y += 2;
            }
            Direction::Left => {
                let dx = x.checked_sub(4);
                if dx.is_none() || maze[y][dx.unwrap()] == 0 {
                    continue;
                }
                maze[y][x - 1] = 0;
                maze[y][x - 2] = 0;
                maze[y][x - 3] = 0;
                maze[y][x - 4] = 0;
                x -= 4;
            }
        }
        draw_maze(maze).unwrap();
        std::thread::sleep(TIME);
        dig_maze(x, y, maze)?;
    }
    Ok(())
}

fn draw_maze(maze: &[[usize; MAZE_X]; MAZE_Y]) -> Result<()> {
    for y in 0..MAZE_Y {
        for x in 0..MAZE_X {
            if maze[y][x] == 0 {
                queue!(
                    stdout(),
                    cursor::MoveTo(x as u16, y as u16),
                    PrintStyledContent(WALL.blue())
                )?;
            } else {
                queue!(
                    stdout(),
                    cursor::MoveTo(x as u16, y as u16),
                    PrintStyledContent(WALL.black())
                )?;
            }
        }
    }
    stdout().flush()?;
    Ok(())
}
```

![dig_maze.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2630694/ef5d1d60-bf21-c22a-f777-e27e73b28120.gif)

代替画面で迷路が表示され戻ってくるときにはなくなっているので画面が綺麗なままになる

このように別のバッファーを開いて何かやってコマンドを実行した後に戻すことができる

大抵はループ処理で表示し続けるようにする

### ローモードの切り替え

`raw mode`はターミナルに対する入力やそれによるカーソル移動などを自動でやらずに全て手動で行えるようにするモード

`Ctrl+c`でターミナルのコマンドを強制終了したりするのをやめさせたりできる

先ほどの迷路を表示中の間に`Ctrl+c`を押すと強制終了させることができる

これを意図的に消したい場合はrawモードを設定する

ただ，入力によるカーソル移動や右端に来たら改行なども全て手動で制御する必要が出てくるので注意が必要

### 画面のクリア

ref: [Clear](https://docs.rs/crossterm/latest/crossterm/terminal/struct.Clear.html)

Clearの仕方はClearTypeを指定するだけ

- All 全部
- Purge 粛清する(履歴とセル全て)
- FromCursorDown カーソル位置から下方向のセル全て
- FromCursorUp カーソル位置から上方向のセル全て
- CurrentLine 現在の行のセル全て
- UntilNewLine 現在のカーソル位置から改行までのセルすべて

```rust
fn main() -> Result<()>{
    let mut stdout = stdout();
    execute!(stdout, EnterAlternateScreen)?;
    // terminal::size() -> Result<(u16, u16)>を利用して全画面もできる
    const WALL_Y: usize = 41;
    const WALL_X: usize = 101;

    let tiles = "██";
    for y in 0..WALL_Y {
        for x in 0..WALL_X {
            execute!(
                stdout,
                MoveTo(x as u16, y as u16),
                style::PrintStyledContent(tiles.white())
            )
            .unwrap();
        }
        println!();
    }
    for y in 0..WALL_Y {
        for x in 0..WALL_X {
            execute!(
                stdout,
                MoveTo(x as u16, y as u16),
                Clear(ClearType::FromCursorUp),
            )
            .unwrap();
        }
        println!();
    }
    execute!(stdout, LeaveAlternateScreen)
}
```

![clear_window.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2630694/83ea8276-4e8f-7f3a-c08d-d6b4a94eac95.gif)

白く塗られていき、徐々に消えていく

実行したときに`Clear(ClearType::All)`をやっておくと、実行するたびに綺麗になる

実行した場所のカーソル位置を保存し`Cler(ClearType::FromCursorDown)`をしたりすると代替画面に行かず描画したのを消したりできる

## カーソル操作

出力するときの位置先などを決めることができる

入力をするときとかにMoveRight(Left)とかBlinkを利用し、何かを特定の位置に表示させたいときにはMoveToを使ったりする

構造体の名前と機能が割と見てわかるので簡易的に説明をする

- DisableBlinking: カーソルの点滅を無効化
- EnableBlinking: カーソルの点滅を有効化
- SetCursorStyle: カーソルの形を変更(ブロック，ラインなどなど)
- Hide: カーソルを隠す
- Show: カーソルを表示
- Move(Up, Down, Left, Right)
- MoveTo: 指定した(col, row)に移動させる。左上が(0, 0)の基準になっている
- MoveTo(Column, NextLine, PreviousLine, Row, Up) 各列や行に移動
- SavePosition, RestorePosition

とりあえず指定した位置に文字を出力するのはこのようになる

```rust
use std::io::{stdout, Result};

use crossterm::cursor::MoveTo;
use crossterm::execute;
use crossterm::style::{Color, Print, ResetColor, SetBackgroundColor, SetForegroundColor};

fn main() -> Result<()> {
    execute!(
        stdout(),
        MoveTo(10, 20),
        SetForegroundColor(Color::Blue),
        SetBackgroundColor(Color::Red),
        Print("Styled text here."),
        ResetColor
    )?;
    Ok(())
}
```

![スクリーンショット 2024-10-29 094905.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2630694/e3edafaa-4844-c47a-f66a-d56fe4a03187.png)

MoveTo(10, 20)からわかる通り、10列目の20行目から`Styled text here`が表示される

現在開いているターミナルの画面を基準にして大きさが測られて上書きするように表示される

そのため、ターミナルの中央でコマンドを実行しても、そこから指定された行と列移動するわけではないので注意

MoveDownの場合は以下の通り

```rust
use std::io::{stdout, Result};

use crossterm::cursor::MoveTo;
use crossterm::execute;
use crossterm::style::{Color, Print, SetBackgroundColor};
use crossterm::terminal::{Clear, ClearType};

fn main() -> Result<()> {
    let mut stdout: std::io::Stdout = stdout();
    let cursor_styles: [(u16, Color); 16] = [
        (0, Color::Black),
        (1, Color::DarkGrey),
        (2, Color::Red),
        (3, Color::DarkRed),
        (4, Color::Green),
        (5, Color::DarkGreen),
        (6, Color::Yellow),
        (7, Color::DarkYellow),
        (8, Color::Blue),
        (9, Color::DarkBlue),
        (10, Color::Magenta),
        (11, Color::DarkMagenta),
        (12, Color::Cyan),
        (13, Color::DarkCyan),
        (14, Color::White),
        (15, Color::Grey),
    ];
    execute!(stdout, Clear(ClearType::All), MoveTo(0, 0))?;
    for (i, color) in cursor_styles.into_iter() {
        execute!(
            stdout,
            MoveTo(i, i),
            SetBackgroundColor(color),
            Print(cursor_styles[i as usize].0),
        )?;
    }

    Ok(())
}
```
![スクリーンショット 2024-10-29 094055.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2630694/41d63a0b-390e-f715-151f-6423f77b8fee.png)

これも最初に指定した位置から背景を付けて数字をプリントして下に移動するのが分かる

UpやRight、Leftも同様である

## 入力系

とりあえずはドキュメントにある入力系はざっくりと以下の通り

- EnableFocusChange
- DisableMouseCapture
- KeyEvent
- KeyModifiers
- MouseEvent

それから読み込むためのread()と、そのread()をチェックするためのpoll()の2つの関数がある

~~crosstermもそうだが、割と多くのライブラリで独自のResultを実装していることが多い~~

~~そのため`crossterm::Result`と`io::Result`, `std::result::Result`など様々な`Result`が混ざってタイプミスマッチのエラーが起きやすくなる~~

この[PR](https://github.com/crossterm-rs/crossterm/pull/765)で修正され、`io::Result`で処理可能になった

とりあえず、read()を使ってマウスとキーボードそれからターミナルのリサイジングの入力を受け取るコードを以下に示す

ここで注意が必要なのは出力される先が決められていないので中途半端な位置から出力がされるという点である

EscかCtrl+cを押すと元に戻れる

```rust
fn main() -> Result<()> {
    execute!(stdout(), EnterAlternateScreen, EnableMouseCapture)?;
    print_events().unwrap();
    execute!(stdout(), LeaveAlternateScreen, DisableMouseCapture)?;
    Ok(())
}

fn print_events() -> Result<()> {
    loop {
        // `read()` blocks until an `Event` is available
        match read()? {
            Event::Mouse(event) => println!("{:?}", event),
            Event::Key(event) => match event {
                KeyEvent {
                    code: KeyCode::Esc, ..
                } => break,
                _ => println!("{:?}", event),
            },
            Event::Resize(width, height) => println!("New size {}x{}", width, height),
            _ => {}
        }
    }
    Ok(())
}
```

ターミナルに対するすほぼ全ての入力が出力されるようになる

他にもtokioを使ったEventSteramだったり、クリップボードのサポートだったり機能が豊富にある

### マウス入力

マウスカーソルがターミナル上にあるときに自動で取得される

位置，移動中かどうか，ボタンを押しているかどうかなどが取得できる

とりあえず`here`を表示してその近くになると`hover`が表示され，クリックをし続けていると`Clicked`が表示されるようのは以下の通り ~~(Result as crsResultするのを忘れずに)~~

`esc`か`Ctrl+c`で終了できる

```rust
use std::io::{self, stdout};

use crossterm::cursor::MoveTo;
use crossterm::event::{
    self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEvent, MouseButton,
    MouseEvent, MouseEventKind,
};
use crossterm::execute;
use crossterm::style::{Color, Print, Stylize};
use crossterm::terminal::{Clear, ClearType, EnterAlternateScreen, LeaveAlternateScreen};

fn main() -> io::Result<()> {
    execute!(stdout(), EnterAlternateScreen, EnableMouseCapture)?;
    print_events().unwrap();
    execute!(stdout(), LeaveAlternateScreen, DisableMouseCapture)?;
    Ok(())
}

struct ButtonDeath {
    x: u16,
    y: u16,
    width: u16,
    height: u16,
}

impl ButtonDeath {
    fn new(x: u16, y: u16, width: u16, height: u16) -> Self {
        Self {
            x,
            y,
            width,
            height,
        }
    }

    fn is_in_button(&self, x: u16, y: u16) -> bool {
        match (
            self.x <= x,
            x <= self.x + self.width,
            self.y <= y,
            y <= self.y + self.height,
        ) {
            (true, true, true, true) => true,
            _ => false,
        }
    }

    fn get_left_top(&self) -> (u16, u16) {
        (self.x, self.y)
    }
}

fn print_events() -> io::Result<()> {
    let button = ButtonDeath::new(2, 2, 4, 0);
    let (left_x, left_y) = button.get_left_top();
    loop {
        match event::read()? {
            Event::Mouse(MouseEvent {
                kind, column, row, ..
            }) => {
                if button.is_in_button(column, row) {
                    execute!(
                        stdout(),
                        MoveTo(left_x, left_y),
                        Clear(ClearType::CurrentLine),
                        Print("hover".with(Color::Black.into()).on(Color::Blue.into()))
                    )?;
                    match kind {
                        MouseEventKind::Down(MouseButton::Left) => {
                            execute!(
                                stdout(),
                                MoveTo(left_x, left_y + 1),
                                Print("Clicked".with(Color::Green.into()))
                            )?;
                        }
                        MouseEventKind::Up(_) => {
                            execute!(
                                stdout(),
                                MoveTo(left_x, left_y + 1),
                                Clear(ClearType::CurrentLine)
                            )?;
                        }
                        _ => {}
                    }
                } else {
                    execute!(
                        stdout(),
                        MoveTo(left_x, left_y),
                        Clear(ClearType::CurrentLine),
                        Print("Here")
                    )?;
                }
            }
            Event::Key(event) => match event {
                KeyEvent {
                    code: KeyCode::Esc, ..
                } => break,
                _ => {
                    execute!(stdout(), MoveTo(0, 0))?;
                    println!("{:?}", event);
                }
            },
            _ => {}
        }
    }
    Ok(())
}
```

![スクリーンショット 2025-01-07 100400.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2630694/152ead34-cac8-ab3a-be8e-766ec3b3daf5.png)


簡単なクリックとボタンもどきみたいなのができてしまう

専用の`Button`構造体を作ってしまえばターミナル上に簡単にボタンを配置したりできるようになる

あくまでも簡易的なものでしかないので、範囲指定したり影をつけたりより細かいものを作りたいときにはTUIライブラリを使用した方が良い

他にもドラック(クリックして移動)とかの入力を受け取れるので更に細かい操作とかも可能になっている

### キーボード入力

以下のコードがとりあえずキーボード入力を取得する一番楽な方法である

```rust
Even::Key(key) = event::read()?
```

簡易的に`if let`で一文字入力、`while let`で文字列の入力ができる

```rust
fn input_events() -> Result<()> {
    loop {
        if let Event::Key(KeyEvent {
            code,
            modifiers,
            kind,
            state,
        }) = read()?
        {
            if code == KeyCode::Esc {
                break;
            }
            println!("{:?}{:?}{:?}{:?}", code, modifiers, kind, state);
        }
    }
    Ok(())
}
```

複数行にするときには`loop`と`while let`を使うとできる

`key`は`KeyEvent { code, modifiers, kind, state }`の4つのフィールドを持つ構造体になっている

- code: 押されている特殊キーを含めたキー入力
- modifiers: 押されている修飾キー入力
- kind: キーが押される、押し続けられている、離されたを判断するバリアント
- state: 変更されたキー入力を正確に読み取れるようにするフラグ

今回はここの`code`と`modifiers`に限定して説明する

codeとmodfiersはそれぞれキーのバリアントを持っており、キー入力をそれらバリアントとマッチングさせることでキーを操作する

例えばCtrl+Alt+aを押した場合は以下のようになる

```rust
KeyEvent {
    code: Char('A'),
    modifiers: CNTROL | ALT,
    kind: Pressed,
    state: None, 
}
```

なぜかShiftとCtrlは同時に押しても認識されない

codeには入力された文字が代入される

ユニークな文字の場合はそれのバリアント名となる

- Enter
- Backspace
- Tab
- Esc

それ以外はChar('a'), Char(' ')(空白), Char('#')などのcharで取得される

入力とカーソル移動を合わせたエディタもどきは以下のようになる

<details>

```rust
use std::{
    fmt::Display,
    io::{stdout, Result, Write},
};

use crossterm::{
    cursor::{MoveTo, SetCursorStyle},
    event::{self, Event, KeyCode, KeyEvent, KeyEventKind},
    execute, queue,
    style::Print,
    terminal::{
        disable_raw_mode, enable_raw_mode, Clear, ClearType, EnterAlternateScreen,
        LeaveAlternateScreen,
    },
};

fn main() -> Result<()> {
    let mut stdout = stdout();
    let mut app = App::new();
    execute!(
        stdout,
        EnterAlternateScreen,
        SetCursorStyle::BlinkingBlock,
        MoveTo(app.col as u16, app.col as u16)
    )?;
    enable_raw_mode()?;

    app.normal_mode()?;

    execute!(
        stdout,
        LeaveAlternateScreen,
        SetCursorStyle::DefaultUserShape
    )?;
    disable_raw_mode()?;
    Ok(())
}

struct App {
    lines: Vec<String>,
    line_length: usize,
    row: usize,
    col: usize,
    _width: u16,
    height: u16,
    mode: Mode,
}

enum Mode {
    Normal,
    Insert,
    Visual,
}

trait MoveTrait {
    fn cursor_style(&self) -> SetCursorStyle;
    fn set_mode(&mut self, mode: Mode);
}

impl MoveTrait for Mode {
    fn cursor_style(&self) -> SetCursorStyle {
        match self {
            Mode::Normal => SetCursorStyle::BlinkingBlock,
            Mode::Insert => SetCursorStyle::BlinkingBar,
            Mode::Visual => SetCursorStyle::SteadyBlock,
        }
    }

    fn set_mode(&mut self, mode: Mode) {
        *self = mode;
    }
}

impl Display for Mode {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Mode::Normal => write!(f, "Normal"),
            Mode::Insert => write!(f, "Insert"),
            Mode::Visual => write!(f, "Visual"),
        }
    }
}

struct VisualRange {
    line_begin: usize,
    line_end: usize,
    col_begin: usize,
    col_end: usize,
}

impl VisualRange {
    fn new(lb: usize, le: usize, cb: usize, ce: usize) -> Self {
        Self {
            line_begin: lb,
            line_end: le,
            col_begin: cb,
            col_end: ce,
        }
    }
}

impl App {
    fn new() -> Self {
        let (width, height) = crossterm::terminal::size().unwrap();
        Self {
            lines: vec![String::new()],
            line_length: 0,
            row: 0,
            col: 0,
            _width: width,
            height,
            mode: Mode::Normal,
        }
    }

    fn draw_text(&self) -> Result<()> {
        queue!(stdout(), Clear(ClearType::All))?;
        for (i, line) in self.lines.iter().enumerate() {
            queue!(stdout(), MoveTo(0, i as u16), Print(line))?;
        }
        Ok(())
    }

    fn draw_cursor(&self) -> Result<()> {
        print!(
            "{}({}:{}) {}, {}",
            MoveTo(0, self.height - 1),
            self.col,
            self.row,
            self.line_length,
            self.mode
        );
        queue!(stdout(), MoveTo(self.col as u16, self.row as u16))?;
        Ok(())
    }

    fn current_width(&mut self) {
        if let Some(line) = self.lines.get(self.row) {
            self.line_length = line.len();
            if self.line_length < self.col {
                self.col = self.line_length;
            }
        }
    }

    fn normal_mode(&mut self) -> Result<()> {
        self.draw_text()?;
        self.draw_cursor()?;
        loop {
            if let Event::Key(KeyEvent { code, kind, .. }) = event::read()? {
                if kind == KeyEventKind::Release {
                    continue;
                }
                self.mode.set_mode(Mode::Normal);
                match code {
                    KeyCode::Esc => break,

                    KeyCode::Char('i') => {
                        self.insert_mode()?;
                        self.mode.set_mode(Mode::Normal);
                        execute!(stdout(), self.mode.cursor_style())?;
                    }

                    KeyCode::Char('v') => {
                        self.visual_mode()?;
                        self.mode.set_mode(Mode::Normal);
                        execute!(stdout(), self.mode.cursor_style())?;
                    }

                    KeyCode::Char('h') => {
                        if self.col > 0 {
                            self.col -= 1;
                        }
                    }

                    KeyCode::Char('l') => {
                        if let Some(line) = self.lines.get(self.row) {
                            if self.col < line.len() {
                                self.col += 1;
                            }
                        }
                    }

                    KeyCode::Char('j') => {
                        if self.row < self.lines.len() - 1 {
                            self.row += 1;
                            self.current_width();
                        }
                    }

                    KeyCode::Char('k') => {
                        if self.row > 0 {
                            self.row -= 1;
                            self.current_width();
                        }
                    }
                    _ => {}
                }
            }

            self.draw_text()?;
            self.draw_cursor()?;
            stdout().flush()?;
        }

        Ok(())
    }

    fn insert_mode(&mut self) -> Result<()> {
        self.mode.set_mode(Mode::Insert);
        self.draw_text()?;
        self.draw_cursor()?;
        execute!(stdout(), self.mode.cursor_style())?;
        loop {
            if let Event::Key(KeyEvent { code, kind, .. }) = event::read()? {
                if kind == KeyEventKind::Release {
                    continue;
                }

                match code {
                    KeyCode::Enter => {
                        if self.col == self.line_length {
                            self.row += 1;
                            self.col = 0;
                            self.lines.push(String::new());
                        } else {
                            let line = self.lines.remove(self.row);
                            let (l, r) = line.split_at(self.col);
                            self.lines.insert(self.row, l.to_string());
                            self.lines.insert(self.row + 1, r.to_string());
                            self.row += 1;
                            self.col = 0;
                        }
                    }

                    KeyCode::Backspace => {
                        let row = self.lines.get_mut(self.row);
                        if row.is_none() {
                            continue;
                        }

                        if self.col == 0 {
                            if let Some(row) = self.row.checked_sub(1) {
                                self.row = row;
                                if let Some(width) = self.lines.get(self.row) {
                                    self.col = width.len();
                                    let text = self.lines.remove(self.row + 1);
                                    if let Some(line) = self.lines.get_mut(self.row) {
                                        line.push_str(&text);
                                    }
                                }
                            }
                            self.current_width();
                        } else if self.col > 0 {
                            row.unwrap().remove(self.col - 1);
                            self.col -= 1;
                            self.current_width();
                        }
                    }

                    KeyCode::Esc => break,

                    KeyCode::Left => {
                        self.col = self.col.saturating_sub(1);
                    }

                    KeyCode::Right => {
                        if let Some(row) = self.lines.get(self.row) {
                            if self.col < row.len() {
                                self.col += 1;
                            }
                        }
                    }

                    KeyCode::Down => {
                        if self.row + 1 < self.lines.len() {
                            self.row += 1;
                            self.current_width();
                        }
                    }

                    KeyCode::Up => {
                        self.row = self.row.saturating_sub(1);
                        self.current_width();
                    }

                    KeyCode::Char(c) => {
                        self.lines[self.row as usize].insert(self.col as usize, c);
                        self.col += 1;
                        self.current_width();
                    }

                    _ => {}
                }
                self.draw_text()?;
                self.draw_cursor()?;
                stdout().flush()?;
            }
        }
        Ok(())
    }

    fn visual_mode(&mut self) -> Result<()> {
        self.mode.set_mode(Mode::Visual);
        todo!()
    }
}
```

</details>

visualモードも作ろうと思ったが面倒になったのであきらめた

文字列をVecで保持しているのでこれを単一のStringやそれこそ`Piece Table`や`Gup Buffer`にすると本格的なテキストエディタになる

また、毎度画面を消して再描画するのを繰り返しているので編集した箇所のみを描画とか無効な時に`continue`するとか、いろいろ考慮できるところがある

もどきなので致し方ない

でも簡単な操作ならこんな感じでRustにしてはコードが少ない?と思われる量で書くことができる

## まとめ

Crosstermの使い方を少しだが紹介することができたと思う

- 色の指定
- ターミナルの画面
- カーソル位置
- キー入力

それぞれの紹介しきれていない部分や自分の簡易コードだと表現しきれていない良さもあったりするので触ってみるのをお勧めする

私も勉強がてらに触っているが、こういうライブラリを勉強するとTUIやCUIに慣れてきたり、それこそ作ってみたいなあという気が強くなってくるので割とRustの初心者にも良いかなあと思う