Rubyでcursesを使う

作成日:2016/01/31
最終更新日:2020/07/05

Rubyでcursesを使う

作成日:2016/01/31
最終更新日:2020/07/05

概要

Rubyでcursesを使う練習をしました。 cursesはCLI上でUIを作成するのに便利なライブラリです。 Rubyに限ったものではなく、汎用的に使われます。

動機

Hamamatsu.rbに参加して早4年。何かアプリケーションを作成したくなってきました。 せっかく作るのですから、普段の開発に役立つツールを作りたいと考えました。

作りたいものは何なのか自問しているうちに、ふと浮かんだのは学生の頃思いつきで作ったテキストエディタを思い出しました。 機能としては、Windowsに標準添付されているメモ帳にも劣るものでしたが作っていて楽しかったです。

今は普段Emacsを使っているので、折角ならRubyで拡張できるようなテキストエディタを作ってみたいと思い始めました。

自分が使う上でEmacsを代替するにはCLI上で動くことも必要な要件になります。 GUIアプリを作るためにはGTKやQtを覚える必要があるのでまずはCLIで動かすことを目標にします。

curses

cursesはUNIX環境における端末制御ライブラリで、CLI上でTUIを作成するために用いられます。 Windows上で動く実装もあるようです。

Rubyには2.0系までは標準添付のライブラリに含まれていました。 このライブラリは、C言語のプログラム用のcursesライブラリのラッパになっているようです。 Rubyの2.1系からはgemでインストールする必要があります。

Hello World!

リファレンスマニュアルに載っていたサンプルを動かしてみます。

ソース1. サンプル
require "curses"

Curses.init_screen
begin
  s = "Hello World!"
  Curses.setpos(Curses.lines / 2, Curses.cols / 2 - (s.length / 2))
  Curses.addstr(s)
  Curses.refresh
  Curses.getch
ensure
  Curses.close_screen
end

各メソッドの挙動から意味を調べます。

  • init_screen
    • Cursesを初期化する
  • lines
    • 画面の行数を返す
  • cols
    • 画面の列数を返す require "curses"
  • setpos
    • カーソルを指定の位置に移動する
  • addstr
    • カーソルの位置から引数に渡した文字列を出力する
    • 改行文字を渡してはいけない
  • refresh
    • 画面を再描画する
  • getch
    • 文字入力を待つ
    • 文字入力があると例外が発生し、ensureに処理が落ちる
  • close_screen
    • cursesを終了する
    • cursesから抜ける場合には呼ばなければならない

Window

CursesではWindowと呼ばれる描画単位を持ちます。 init_screenの戻り値としてメインのWindowオブジェクトであるstdscrを得ることができます。 このオブジェクトは、stdscrを呼んでも得ることができます。 即ち、上記のHello World!は、stdscr上に描画された文字列です。

Curses::Windowクラスを用いるとウィンドウの中にサブウィンドウを作成することができます。 サブウィンドウを作成するサンプルを作成します。

ソース2. サンプル2
require "curses"

mwin = Curses.init_screen
mwin.box(?|, ?-, ?+)

begin
  mws = "Main Window"
  sws1 = "Sub Window 1"
  sws2 = "Sub Window 2"

  Curses.setpos(Curses.lines / 2, Curses.cols / 2 - (mws.length / 2))
  Curses.addstr(mws)

  swin1 = mwin.subwin(3, Curses.cols, 0, 0)
  swin1.box(?|, ?-, ?+)
  swin1.setpos(1, Curses.cols / 2 - (sws1.length / 2))
  swin1.addstr(sws1)
  swin1.refresh

  swin2 = mwin.subwin(3, Curses.cols, Curses.lines - 3, 0)
  swin2.box(?|, ?-, ?+)
  swin2.setpos(1, Curses.cols / 2 - (sws2.length / 2))
  swin2.addstr(sws2)
  swin2.refresh

  Curses.refresh
  Curses.getch
ensure
  Curses.close_screen
end

上記プログラムを実行すると以下の結果が得られます。

コマンド1. 実行結果
  +------------------------------------------------------------------------------+
  |                                 Sub Window 1                                 |
  +------------------------------------------------------------------------------+
  |                                                                              |
  |                                                                              |
  |                                                                              |
  |                                                                              |
  |                                                                              |
  |                                                                              |
  |                                                                              |
  |                                                                              |
  |                                                                              |
  |                                  Main Window                                 |
  |                                                                              |
  |                                                                              |
  |                                                                              |
  |                                                                              |
  |                                                                              |
  |                                                                              |
  |                                                                              |
  |                                                                              |
  +------------------------------------------------------------------------------+
  |                                 Sub Window 2                                 |
  +------------------------------------------------------------------------------+

メインウィンドウの上部と下部にサブウィンドウを作成しました。 各メソッドの挙動から意味を調べます。

  • stdscr
    • stdscrオブジェクトを得る
  • subwin
    • サブウィンドウを作成する
    • 第1引数が縦方向の長さ
    • 第2引数が横方向の長さ
    • 第3引数が縦方向の左上の位置
    • 第4引数が横方向の左上の位置
  • box
    • Windowオブジェクトの周囲に枠を描画する
    • 第1引数が縦線
    • 第2引数が横線
    • 第3引数が交点

つづく