crateの追加
では端末を制御してrawモードにするためにcrateを使うようにしましょう。このような機能を提供するcrateは複数ありますが今回はcrosstermを使用します。
下記のcargo add
コマンド1を実行してみましょう。
このコマンドでCargo.tomlの[dependencies]セクションがアップデートされてcrosstermの情報が追加されます。
$ cargo add crossterm
Updating crates.io index
Adding crossterm v0.27.0 to dependencies.
Features:
+ bracketed-paste
+ events
+ windows
- event-stream
- filedescriptor
- serde
- use-dev-tty
Updating crates.io index
crateの探し方
crates.ioというサイトでcrateを収集しています。こちらでキーワードを使って検索し目的のcrateを見つけることができると思います。
類似サイトにLib.rsやAwesome Rustなどがあります。
cargo search
コマンドでcrate名が検索結果に表示されれば使用可能と思われます。
その他、cargoには様々な機能があります。こちらやThe Cargo Bookを参照してください。
$ cargo search crossterm
crossterm = "0.27.0" # A crossplatform terminal library for manipulating terminals.
crossterm_utils = "0.4.0" # Common logic used by the crossterm crates.
crossterm_style = "0.5.2" # A cross-platform library styling the terminal output.
crossterm_cursor = "0.4.0" # A cross-platform library for moving the terminal cursor.
crossterm_input = "0.5.0" # A cross-platform library for reading userinput.
crossterm_screen = "0.3.2" # A cross-platform library for raw and alternate screen.
crossterm_terminal = "0.3.2" # A cross-platform library for doing terminal related actions.
ibox-crossterm = "0.23.2" # A fork of crossterm made for ibox to get the stderr position of the cursor. NOT FOR GENERAL USE…
crosstermion = "0.14.0" # utilities for `crossterm`, without ties to `termion`
bevy_crossterm = "0.4.0" # Develop terminal games with crossterm and Bevy
... and 193 crates more (use --limit N to see more)
crosstermについて
crosstermの機能2についてざっくりと図に表すと下記にようになるかと思います。
使い方としては現在表示されているメイン画面とは別の画面に切り替えることができます。
別画面に切り替えてアプリケーションの画面描画をバッファを通じて行います。
rawモードの制御、キー入力などのイベント処理、カーソル処理、スタイリングしたテキストの表示などでアプリケーションの画面を作っていくことができそうです。

rawモードへGo!
これで準備ができました。
crosstermを使ったコードにしてみましょう。cargo build
すると新しくcrosstermもコンパイルされている様子が表示されると思います。
cargo run
して、これを実行すると今度はキー入力がすぐに出力されるようになります。
// raw_mode
// 標準ライブラリの読み込み
use std::io::{self, Read};
// crosstermの読み込み
use crossterm::terminal::disable_raw_mode;
use crossterm::terminal::enable_raw_mode;
// main 関数
fn main() {
// raw モードに移行
enable_raw_mode().unwrap();
// STDINからbyte列を読み込み
for input_byte in io::stdin().bytes() {
// 読み込んだbyteをcharに変換して束縛
let output_char = input_byte.unwrap() as char;
// 読み込んだ値を出力
println!("{}", output_char);
// qが入力されたらbreak
if output_char == 'q' {
// raw モードを解除
disable_raw_mode().unwrap();
break;
}
}
}
キー入力の表示
rawモードでの入力がどのように機能するかを知るために、読み取った各バイトを出力してみよう。
各文字のバイナリ表現、数値のASCII値、そして印字可能な文字であればその文字を表示します。
これを行うには、main()を以下のように変更します
// keypresses
// 標準ライブラリの読み込み
use std::io::{self, Read};
// crosstermの読み込み
use crossterm::terminal::disable_raw_mode;
use crossterm::terminal::enable_raw_mode;
// main 関数
fn main() {
// raw モードに移行
enable_raw_mode().unwrap();
// STDINからbyte列を読み込み
for input_byte in io::stdin().bytes() {
// シャドーイング
let input_byte = input_byte.unwrap();
// 読み込んだbyteをcharに変換して束縛
let output_char = input_byte as char;
// 読み込んだ値を出力
if output_char.is_control() {
println!("Binary: {0:08b} ASCII: {0:#03} \r", input_byte);
} else {
println!(
"Binary: {0:08b} ASCII: {0:#03} Character: {1:#?}\r",
input_byte, output_char
);
}
// qが入力されたらbreak
if output_char == 'q' {
// raw モードを解除
disable_raw_mode().unwrap();
break;
}
}
}
実行例はこのようになります。
Characterが表示されていないものがありますが、これは
Binary: 00000011 ASCII: 003はCtrl+C
Binary: 00000100 ASCII: 004はCtrl+D
を押したケースになります。
Binary: 01100001 ASCII: 097 Character: 'a'
Binary: 01100010 ASCII: 098 Character: 'b'
Binary: 01100011 ASCII: 099 Character: 'c'
Binary: 00000011 ASCII: 003
Binary: 00000100 ASCII: 004
Binary: 01110001 ASCII: 113 Character: 'q'
ここまでのまとめ
さて、新しいプログラムを使っていくつかのことを学びました。
canonicalモードはタイプライターやテレタイプ端末のような動作をしていました。rawモードではこのような動きではなく即座にキーが押されると結果が出力されました。
コンピューター内部では、文字は単なる数字であり、数字はメモリ上では0と1(これを「ビット」と呼ぶ)で表現されます(2進数)。例えば、メモリのどこかに01100001があるとします(8ビットをByteと呼ぶので、これは1Byteです)。
2進数を10進数に変換するのは簡単で、01100001を10進数に変換すると97という数字になります。このような数値を文字にマッピングする規格が存在します。(ASCIIコード)
新しいプログラムにaを入力すると、a、097、01100001の3つが出力されます。
キーボードから読み取ったバイナリを文字として扱うようプログラムに簡単に教えることができるのもこのためです。
基本的に、私たちはコンピューターに「この数字を文字として扱ってください」と伝えただけなのです。
ASCIIコード0~31は制御文字と呼ばれるすべて印刷不可能な文字で(ベル、キャリッジ・リターン、改行もこの中にある)、127は制御文字です。
したがって、is_control()は単純に入力値がこの範囲かどうかを調べることになります。
再度プログラムを実行してEscapeキーやEnterキー、Homeキーなど様々なキー入力を試してみましょう。
TOC
- 準備
- ターミナル入出力を試す
- ターミナル系crateを利用する
- 編集データと表示処理
- 画面制御
- その他
GitHubにコードをアップロードしています。
コードのコメントに書かれているfirst_stepなどをcargoコマンドに渡すと実行できます。
# Example
$ cargo run --example first_step