テキスト・エディタのエコー領域

エコー領域は端末画面最下にあり、 エディタからのメッセージを表示する場所です。 ここでは、 エコー領域兼ミニバッファの表示に 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