16
14

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

文字入力のすごい基礎的なところを理解した

Posted at

経緯

Vim とか触っていると、一文字入力するたびに何かアクションが発生したりするので、どういう仕組みで動いているんだろうという技術的興味があったので、ソースコードリーディングをしてみた。

知識レベル

getc() とか gets() とかで 行単位の入力ができるのは知っているが、キー入力ごとに処理をする方法を知らない。

予想していた動作

予想というか自分の知識で簡単に考えられたのは、別スレッドで文字入力を監視してキー入力があったらメインスレッドに通知するみたいなのを想像してた。

wincons.rb を眺める

wincons.rb という Ruby製のコンソールライブラリ、一文字入力したら終了するみたいな処理をすごい昔に書いた覚えがあって、そういえば、これはどうやって実現しているんだろうと気になりました。

ソースは以下からゲットできます。
http://texcell.co.jp/ruby/Lib/winconsole.html

見てみると、win32apiのPeekConsoleInputWという関数を呼び出すことで実現しているよう。

http://texcell.co.jp/ruby/PLC/rubyc24.html
↑ここらへんの例を見ると、ループの中で常にキー入力チェックをして、qが来たら終了するようにしているみたいです。

Vim のソースコードを眺める

wincons.rb のやり方が本当に正しいのか?という疑問もあったので、Vimのソースコードも眺めてみることにしました。

ソースコードはgithubから手に入れることができます。
https://github.com/vim/vim

先ほど出てきたPeekConsoleInputWが使われている箇所を検索すると、src/os_win32.cread_console_inputという関数の中で使われていることがわかりました。

このread_console_inputというのが結構いろんなところから呼ばれているようで、どれがキーボード入力に直結するのかが私にはわからなかったので、あきらめました。

mch_inchar => tgetch あたりがあやしいかなと踏んでいます。mch_inchrui_incharから呼ばれていて、ui_incharinchar関数のforループの中で呼ばれています。forループはui_incharからキー入力がゲットできるまで回り続けるので、ここら辺がキー入力のキモなのかなと思いますが、これだと、inchar関数の中でループしてほかの処理が動かなくなってしまうので、incharを呼んでいる親元でスレッドになっているんだと思いますが、憶測です。

readline のソースコードを眺める

rubyに付属しているreadlineはTABキーを押すと補完用のProcを呼び出します。おそらくTABキーが押されたらProcを呼び出すといった処理が入っているに違いないと推測。

まずは、rubyに付属のreadlineをコードリーディング

rubyのソースは以下から見れます。
https://github.com/ruby/ruby

readlineのソースはext/readlineあたりにあります。

色々見た結果、Procを呼び出す関数はreadline_attempted_completion_functionぽいことがわかりました。その関数はrl_attempted_completion_functionにバインドされています。

rl_attempted_completion_functionはrubyのコードではなく、GNU readlineのコードの中にあるため、そちらを読み解きます。

GNU readlineのソースは以下から見れます。
https://github.com/JuliaLang/readline

正確にはGNU readlineの本当のソースではありませんが、今回の理解を進める分には問題ないと思います。

rl_attempted_completion_functioncomplete.cの中にありますが、関数をさかのぼってもそれらしいところに到達できませんでした。

readlineの使い方を調べる

ここら辺を見てみるとreadlineがルートっぽいので、ルートから順を追って調べることにしました。
http://d.hatena.ne.jp/cocoatomo/20071112/1194855728

readline()readline.cにあります。その関数の中をどんどん辿っていくと、input.crl_gather_tyi()の中にあるkbhit()がキー入力のキモとなる部分のようです。

kbhit()とは?

ここを見てみると
http://tricky-code.net/mine/c/mc07kbhit.php

kbhit関数とは、何かキーが押された場合0以外の値を返し、
何もキーが押されていない場合は0を返す関数です。

とのこと、これ自体がどのキーが押されたかを判定するものではないようです。

実際とっているのはrl_getc(stream)の中で、read()関数を呼んで、引数として渡されているIOから一文字読んでいるようです。

それをループでぐるぐる回すことで、常にキー入力をチェックして1文字単位でチェックしてるようです。

スレッドなんてなかった。

まとめ

readlineはスレッドを使わずにループでキー入力をチェックしている。

キー入力があったかどうかの判定にはkbhit()を使っている

文字取得にはread()を使って1文字ずつ取得している。

16
14
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
16
14

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?