LoginSignup
26
23

More than 5 years have passed since last update.

ターミナルのキー入力を得てカーソル移動とかする (curses無し)

Last updated at Posted at 2015-09-08

スクリーンショット 2015-09-08 22.42.25.png

やりたいこと: 方向キーでカーソルとか動かしたい (※画像はイメージです)

キー入力を得る

ターミナルでユーザーの入力を得る際、getsgetcではエンターが押されるまでキーの入力状況を得ることができません。
なので、カーソル移動等の操作には使えません。
cursesライブラリは色々とめんどいので使わない方法を調べました。

IO#getch (io/console)

require 'io/console'するとIO#getchが使えるようになります。
これでキーが押されるたびに標準入力から文字を得ることができます。
ただしControl + Cの終了もできなくなるので、Control + Cが押されたらループを抜けるようにします。

read_key.rb
require 'io/console'

i = 0
while (key = STDIN.getch) != "\C-c"
  puts " #{i += 1}: #{key.inspect} キーが押されました。"
end

a, b, c のキーを押した結果

$ ruby read_key.rb 
  1: "a" キーが押されました。
  2: "b" キーが押されました。
  3: "c" キーが押されました。

矢印キーも使ってみる

矢印キーを押すと、カーソルを移動するエスケープシーケンスが入力されます。
(MacのTerminalで動作確認してます。他も一緒かは調べてません)
3文字の文字列じゃなくて、1文字が3回。

$ ruby read_key.rb 
  1: "\e" キーが押されました。
  2: "[" キーが押されました。
  3: "A" キーが押されました。
方向 エスケープシーケンス
"\e[A"
"\e[B"
"\e[C"
"\e[D"

適当に処理を変えて矢印キーを認識するようにします。

read_arrow_key.rb
require 'io/console'

arrows = {A: "↑", B: "↓", C: "→", D: "←"}

i = 0
while (key = STDIN.getch) != "\C-c"

  if key == "\e"
    second_key = STDIN.getch

    if second_key == "["
      key = STDIN.getch
      key = arrows[key.intern] || "esc: [#{key}"
    else
      key = "esc: #{second_key}"
    end
  end

  puts " #{i += 1}: #{key.inspect} キーが押されました。"
end
$ ruby read_arrow_key.rb
  1: "↑" キーが押されました。
  2: "→" キーが押されました。
  3: "↓" キーが押されました。
  4: "←" キーが押されました

使ってみる

カーソル移動してみます。
WASDhjklにも対応したのでPCゲーマーやvim派も安心。
(追記:宗教論争を避ける為、せっかくなのでControl+f,b,n,pにも対応)

move_cursor.rb
require 'io/console'
require 'io/console/size'

#画面を消去して、真ん中に移動しておく
print "\e[2J"
print "\e[%d;%dH" % IO::console_size.map{|size| size / 2 }

while (key = STDIN.getch) != "\C-c"
  # 方向キー以外は不要なので、エスケープ文字を得る処理は簡略化した
  if key == "\e" && STDIN.getch == "["
    key = STDIN.getch
  end

  # 方向を判断
  direction = case key
  when "A", "k", "w", "\u0010"; "A" #↑
  when "B", "j", "s", "\u000E"; "B" #↓
  when "C", "l", "d", "\u0006"; "C" #→
  when "D", "h", "a", "\u0002"; "D" #←
  else nil
  end

  # カーソル移動
  print "\e[#{direction}" if direction
end

このままだと大文字のAとかを打った場合にも上下左右に移動してしまいます。
特に支障はないので対応しませんが。

require 'io/console/size'IO::console_size、今回知ったのですがほとんど使われてませんね。
今までstty sizeでやってました。GitHubでconsole_sizeで検索しても全く使われてないっぽい?

参考

26
23
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
26
23