という疑問を持ったので、少し調べてみた。
やってみた
ターミナルを分割して、ターミナルの任意の位置にカーソルの移動を行うサンプルアプリを作って見た。
動きとしてはこんな感じ
実装
こんな感じで実装してます。
ansi.rb
# frozen_string_literal: true
# ターミナル幅、高さの取得
width, height = `tput cols`.to_i, `tput lines`.to_i
# 画面を改行で埋めて、カーソルの移動ができるようにする
height.times { puts "\n" }
sleep 2
# ターミナルの中央位置を取得
center = height / 2
# ターミナルの中央部を"="で埋めて擬似的に画面分割をさせる
print "\033[#{center};1H"
puts '=' * width
# ターミナルの先頭行へ移動
print "\033[1;1H"
sleep 2
# 先頭行に文字列を出力
puts 'HELLO WORLD'
sleep 2
# 擬似分割画面下部の先頭行へカーソルを移動する
print "\033[#{center + 1};1H"
# 擬似分割画面下部先頭行へ文字列を出力
print '1234567890'
sleep 2
# 擬似分割画面上部の先頭行へカーソルを移動する
print "\033[1;1H"
# カーソルを左から7番目の位置に移動させる
print "\033[7G"
sleep 2
# カーソルの右側にある文字列を削除する
print "\033[K"
sleep 2
# 文字色を指定して文字列を出力する
puts "\e[31mworld\e[0m"
# 終了後に画面をクリアさせるために、カーソルを画面外に移動
print "\033[1;1H"
print "\033[1A"
sleep 5
解説
結論から言うと、ANSIエスケープシーケンスを使ってカーソル位置の操作を行い、擬似的に画面分割をさせてる感じ。
どんなことやってるかはコメントで全部書いてあるけど、せっかくだしざっくりと説明を。
1. 画面のクリア
- ターミナルの高さいっぱい改行を挿入して、画面をクリア。
2. 擬似画面分割
-
=
をターミナルの横幅いっぱいに出力し、擬似画面分割の境界を示す。
3. 上画面の先頭行に文字列を出力
- ANSIエスケープコード
ESC[n;mH
を使用してカーソルを上画面の先頭行に移動 -
HELLO WORLD
の文字列を出力
4. 下画面の先頭行に文字列を出力
- ANSIエスケープコード
ESC[n;mH
を使用してカーソルを下画面の先頭行に移動 -
1234567890
の文字列を出力
5. 文字列の変更
- ANSIエスケープコード
ESC[n;mH
を使用してカーソルを上画面の先頭行に移動 - ANSIエスケープコード
ESC[nG
を使用してカーソルを左から7番目の位置(WORLD
のW
の位置)に移動 - ANSIエスケープコード
ESC[nK
を使用して、カーソルより右の文字列を削除 - ANSIエスケープコード
ESC[nm
を使用して、カラーコードを指定してworld
を出力
という感じ。
まとめ
もっとちゃんと画面分割を表現したい場合は、プログラム内で擬似画面の管理を行う必要があるため、結構苦労しそう。
とはいえ、これができるとターミナルアプリの表現力がぐっと広がるので(あと個人的にとても楽しい)もう少し色々と試してみたい。