エコー領域は端末画面最下にあり、 エディタからのメッセージを表示する場所です。 ここでは、 エコー領域兼ミニバッファの表示に MiniWindow を使います。 MiniWindow のインスタンスはエディタ稼働中に 1 個存在し続け、 Screen が保持しています。 ScreenBase は 10 月に書いた Screen に手を加えて ScreenBase に改名したクラスです。
class Screen < ScreenBase attr_reader :miniwindow def initialize(tty, buffer, miniwindow_buffer) super(tty) @miniwindow = MiniWindow.new(self, miniwindow_buffer) #省略 end end
例えば、 現点のある行番号を表示する what-line コマンドが、 エコー領域を使います。 MiniWindow へ print メッセージを送ると、 次回の画面への refresh 時に表示文字列が現れます。 print の第 1 引数はプロンプト文字列で、 第 2 引数は MiniWindow のバッファの内容になります。
class WhatLine < Interactive def self.name() :what_line end def edit(arg) count = buffer.count_line(window.dot.point) screen.miniwindow.print('', 'Line %d' % [count]) end end
MiniWindow はモードラインのない Window であり、 固有のバッファをもっています。 このバッファも Window と同じ Buffer のインスタンスです。 MiniWindow は通常は端末画面最下の 1 行を占めており、 バッファの内容が 1 行を越えるときは、 自動的に内容に合わせて行数を増やしたり減らします。 バッファの内容以外に、 プロンプトを指定することが可能で、 プロンプトを指定してあるときは、 それを含めて表示します。 現点は、 プロンプトを除くバッファ内の座標を示しています。 バッファの Undo 機能は flush_undo で切ってあります。 Layout も持っており、 Window と同じやりかたで行を折り返し表示します。 ただし、 端末画面最下行の右端への書き込みによってスクロールされてしまうのを避けるために、 MiniWindow の幅は Screen より 1 桁小さく設定しています。 WindowBase の中身は後日に。
class MiniWindow < WindowBase def initialize(screen, buffer) super topleft.y = screen.size.y - 1 size.y = 1 size.x = screen.size.x - 1 @prompt = '' end def modeline_height() 0 end #@<clear@> #@<print@> #@<redisplay@> #@<activate@> #@<closest_height@> end
clear はプロンプトを空文字列にし、 現点をバッファ先頭へ、 バッファの内容をすべて消去します。 clear だけでは端末の表示内容を変更せず。 redisplay によって画面へ書き出す準備をします。
#@<clear@>= def clear() @prompt.clear dot.point = 0 buffer.delete(dot, buffer.size) self end
print メソッドでプロンプト文字列 ps と、 バッファの内容を文字列 str にセットします。 その際、 現点を str の末尾へ移動します。 これも端末の表示内容を変更しません。 print はプロンプト文字列をバッファとは別に文字列として控えておきます。
#@<print@>= def print(ps, str) @prompt = ps.dup dot.point = 0 buffer.delete(dot, buffer.size) buffer.insert(dot, str) self end
redisplay は、 一時的にバッファの先頭へ控えておいたプロンプト文字列を挿入します。 現点はプロンプト文字列の分、 後ろへ動かします。 それから、 表示に必要な行数分の高さを求めて screen の行へ書き出します。 書き出しが終わったらプロンプト文字列をバッファから削除して現点を戻します。 buffer.mode.max_y は、 MiniWindow の最大の高さでこれを越える分は表示しません。
#@<redisplay@>= def redisplay() real_point = dot.point dot.point = 0 buffer.insert(dot, @prompt) dot.point += real_point size.y = buffer.mode.max_y || 8 layout.framer() size.y = closest_height() topleft.y = screen.size.y - size.y layout.display_page() dot.point = 0 buffer.delete(dot, @prompt.size) dot.point = real_point self end
表示に必要な最小の行数を layout を使って求めます。 これを呼ぶ前に size.y に表示可能な最大高さをセットしておかなければなりません。 求める行数は 1 行以上、 size.y 以下です。
#@<closest_height@>= def closest_height() y = layout.get_column(start.point) / size.x height = layout.sizey - y while ! layout.eos? && height < size.y layout.layout(layout.grid.last) height += layout.sizey end [1, [size.y, height].min].max end end
エコー領域として使うときは、 MiniWindow にカーソルを置くことはありませんが、 ミニバッファとして使うときは別です。 そのような場合に、 activate メソッドで、 カーソルを MiniWindow のバッファの現点に対応する位置へ置きます。
#@<activate@>= def activate() screen.tty.set_cursor(cursor.y + topleft.y, cursor.x + topleft.x).flush end