はじめに
こんにちは。izurinaです。最近は無事に来年配属される研究室が決まって平穏な生活を取り戻したと思ったらGo言語の勉強しなければならなくなった大学生です。
UEC Advent Calendar 2022
この記事はUEC Advent Calendar 2022の12日目のものになります。UEC Advent Calendarは毎年電通大の有志によっていつの間にか開催されています。
どんな内容?
ここではBashでの矢印キーなどでカーソルを動かすタイプの選択メニューを作ってみたので簡単にどうやったのかを紹介します。とはいっても大部分はFascodeNetworkのハヤオさんのソースコードを参考にしています。私はこれにvim風のキーバインドを追加したり、選択中のものをハイライトするなどの変更を加えています。今回はその点を中心に紹介していきます。
Bash
みなさんはシェルスクリプトを書きますか?書けるようになると結構快適なので、書けるようになりましょう!Bashは多くのLinuxディストリビューションでは標準シェルになっていますね。私はよくPythonと組み合わせていろいろなツールを作っています。
主な機能
カーソルの移動
特殊なキーの入力検知
たとえばa
やb
など、普通の文字入力であれば、read
で読み取ったものを、目的の文字との一致を判定すれば良いですが、矢印キーやTab
キーなどはそうはいきません。こうした特殊なキーの入力を検知するためにはASCIIコードを用いて判定を行う必要があります。例えばRightArrow
のASCIIコードは0x1b 0x5b 0x43
と3バイトの値で表現されるので、この値を用いて入力検知を行うことになります。
以下にソースコードの例を掲載します。
capture_keys(){
local PUSHED_KEY buff
IFS= read -n1 -s PUSHED_KEY
case "$PUSHED_KEY" in
$'\x09')
echo "Tab"
;;
$'\x1b')
read -r -n2 -s buff
PUSHED_KEY+="$buff"
case "$PUSHED_KEY" in
$'\x1b\x5b\x5a')
echo "ShiftTab"
;;
$'\x1b\x5b\x41')
echo "Up"
;;
$'\x1b\x5b\x42')
echo "Down"
;;
$'\x1b\x5b\x43')
echo "Right"
;;
$'\x1b\x5b\x44')
echo "Left"
;;
esac
;;
"")
echo "Enter"
;;
$'\x7f')
echo "Backspace"
;;
$'\x20')
echo "Space"
;;
esac
}
read
コマンドの機能の解説は参考にしたハヤオさんの記事がわかりやすいと思います。
showkeyコマンド
入力キーをCLIで確認するものとしてshowkey
があります。showkey -a
を実行した状態でキー入力すると入力キーとそれに対応するASCIIコードが出力されます。ソースコード中では矢印キーなど特殊なキーの入力をASCIIコードで受け付けている部分があります。下の画像はRightArrow
を入力した例のものです。最右列がASCIIコードを16進数で表したものです。
vim風カーソル移動
自分は普段テキストエディタはvim(neovim)を使っています。vimではh
j
k
l
の四つのキーでカーソル移動ができます。個人的にこのカーソル移動に慣れているのと、このカーソル移動があればvimmerも喜ぶだろうという勝手な想像もあり実装してみました。
Tab,Shift-Tabでのカーソル移動
上記のshowkey
を用いてTab
とShift-Tab
のASCIIコードを取得して、これらのキーによるカーソル移動を実装しました。Tab
は次の選択肢に移動、Shift-Tab
はその逆にしました。また、これらの移動はループできるようにもしました。
選択肢の表示
選択肢を縦一列に表示するのではスクロールする手間があったり、右側にスペースがあるのに…といったことが気になってしまうのと、せっかく左右の矢印キーやh
l
キーの検知も実装しているのに左右のカーソル移動が無いのは勿体ないと感じたので、選択肢を縦横に並べてみました。(選択肢の内容にはツッコまないでね…)
選択肢番号の二次元座標への変換
選択肢の番号(key)は一つの連番で管理されているので、これの二次元の座標への変換が必要になります。ここでは、行数を$H$、列数を$W$とします。
選択肢は以下のように配置しました。下の行列ではkeyのみとなっていますが、実際の画面ではその右側に選択肢の内容が表示されます。 $$ \begin{bmatrix} 0 & H & \cdots & (W-1)H \\ 1 & H+1 & \cdots & (W-1)H+1 \\ \vdots & \vdots & \ddots & \vdots \\ H-1 & 2H-1 & \cdots & WH-1 \end{bmatrix} $$
ここで、選択肢のkeyが$N$とすると、$N = (N \mod{H}) + (N/H) \times H$ (ただし、$N/H$は切り捨て除算)と表現できます。なので、$N$番目の選択肢の座標を$(x,y)$ ($x$行$y$列)とすると$x=N \mod H,y=N/H$となります。これで、二次元座標への変換は完了しました。
縦方向の移動は選択中の番号のインクリメント・デクリメントで実装しています。横方向の移動は選択中の番号を$H$だけ加算あるいは減算すると実現できます。
選択肢のハイライト
選択中のメニューを強調する方法の一つとして、それをハイライトするというものがありますね。実際何を選んでいるか一目瞭然です。これも一緒に実装しました。方法はエスケープシエンスを使って背景色と文字色をいじるだけです。以下のようにecho
を使うと色が変わります。(ソースコード中ではprintf
を使用しています。)
$ echo -e "\033[48;2;r;g;bm これは背景色を変える"
$ echo -e "\033[38;2;r;g;bm これは文字色を変える"
r
g
b
はそれぞれ0から255までの間の値を入れてください。
実行例
動作環境
Arch Linux+Alacritty、ArchWSL+WindowsTerminalでBash 5.X系で問題なく動作したことを確認しています。
動作例
今後の展望と課題
今後の展望としてこのツールを応用してほかのツールを便利にできたらなと思っています。今までに作ったツールや今後作るツールに組み込んでいきたいです。
一方で課題点として挙げられるのはESC
のASCIIコードが0x1b
なのでArrowキーのASCIIコードの1バイト目と被ってしまっているんですよね…。その関係でESC
に機能を割り当てることができなくなっているのでこれを解決できたらなあと思います。多分無理。
ソースコード
公開
ソースコードはGitHubで公開しています。
ライセンス
私が作成したソースコードはWTFPLにて再ライセンスします。プログラムの使用はご自由にどうぞ。
以下ライセンス原文
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
Version 2, December 2004
Copyright (C) 2004 Sam Hocevar <[email protected]>
Everyone is permitted to copy and distribute verbatim or modified
copies of this license document, and changing it is allowed as long
as the name is changed.
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. You just DO WHAT THE FUCK YOU WANT TO.
参考
[1] 山田ハヤオ「シェルスクリプトで特殊なキーの入力を検出する」
https://blog.fascode.net/2022/11/02/shell-special-keys/
[2] PruneMazui「ANSIエスケープシエンスチートシート」
https://qiita.com/PruneMazui/items/8a023347772620025ad6