CodeZine(コードジン)

特集ページ一覧

RubyとCursesを使ったコンソールテキストエディタ

作って覚えるRuby再入門(第2回)

  • LINEで送る
  • このエントリーをはてなブックマークに追加
2008/02/13 12:00

ダウンロード ソースコード (6.1 KB)

目次

動作確認

 ここまで出来上がったら動かしてみましょう。動かすにあたってはテスト用のテキストファイルを用意してください(面倒であれば今回のソースコードでもかまいません)。

 コマンドプロンプトを立ち上げ、次のように入力してください。"testfile.txt"はテスト用のテキストファイルの名前ですので、用意したテスト用のテキストファイルに置き換えて入力してください。

実行方法
c:\>ruby fe.rb testfile.txt

 このように表示されれば完成です。なにかキーを入力すると終了します。

動作画面
動作画面

スクロールできるようにする

 次はキーの入力によってスクロールするようにしてみましょう。

 まずクラスを一つ追加します。キーの入力内容によって分岐し処理するHandlerクラスです。カーソルの操作は[w][s][a][d]キーで動かすようにします(ここは完全に私の趣味ですので、他のキーに割り当てても構わないです)。

 Handlerクラスにはexecuteメソッドが一つあります。このメソッドは処理対象のウィンドウクラスと入力された文字を一つずつ受け取り、入力内容に応じて適切なメソッドを呼び出します。最後にself(自分自身のオブジェクト)を返しているのは小さな仕掛けを施すためです。これについては次回に説明します。

handler.rb
require "editwind"
class Handler
  def execute(wind,input_ch)
      # コマンド入力を処理
      case input_ch
      when ?s # カーソルを下へ
        wind.cursor_down
      when ?w # カーソルを上へ
        wind.cursor_up
      when ?a # カーソルを左へ
        wind.cursor_left
      when ?d # カーソルを右へ
        wind.cursor_right
      when ?q # プログラム終了
        raise "FEを終了しました"
      end
      return self
  end
end

 呼び出されるメソッドは編集エリアウィンドウクラスに追加します。追加した箇所はコメントと共にボールドにしてあります。

 基本的にはカーソルをCurses::Window#setposメソッドを使って動かせばいいのですが、ウィンドウの端をカーソルが超えようとした場合や一行の文字数以上、右にカーソルが移動しようとした場合の事を考慮しておく必要があります。

 インスタンス変数の@top_statementとは、現在表示されている一番上のデータが、実際のデータ(@data)では何行目かを表現しています。下へスクロールすると、この値は増えていき、上へスクロールすると減っていきます。ただし当然ですが実際のデータ数以上には増えませんし、0未満にもなりません。

editwind.rb
require "curses"

class EditWind
  def initialize(wind)
    # デフォルトウィンドウの高さを少し小さくしたサブウィンドウを作成
    @window = wind.subwin(wind.maxy-3,wind.maxx,0,0)
    # スクロール機能をONにする
    @window.scrollok(true)
  end

  def display(file_name)
    @file_name = file_name
    begin
      # ファイルをオープンし、全内容を配列に読み込んでおく
      @file = open(@file_name, "a+")
      @data = []
      @file.each_line do |line|
        # 行末にある改行を取り除いた上で配列に入れる
        @data.push(line.chop)
      end
      # 初期表示として0行目からウィンドウの最大行数まで一行ずつ表示する
      @data[0..(@window.maxy-1)].each_with_index do |line, idx|
         @window.setpos(idx, 0)
         @window.addstr(line)
      end
    rescue
      # ファイルをオープンできない等、なにか例外が起きた場合
      raise IOError,"FILE OPEN ERROR: #{file_name}"
    end
    # [追加]
    @cursor_y = 0
    @cursor_x = 0
    @top_statement = 0
    @window.setpos(@cursor_y,@cursor_x)
    @window.refresh
  end

  def getch
    return @window.getch
  end
  
  # ここより下 [追加]分
  # カーソルを下へ移動。ウィンドウの下端より下へカーソルが移動
  # しようとした場合はスクロール
  def cursor_down
    if @cursor_y >= (@window.maxy-1)
      scroll_down
    else
      @cursor_y += 1
    end
    if @cursor_x >= (@data[@cursor_y + @top_statement ].length)
      @cursor_x = @data[@cursor_y + @top_statement ].length
    end
    @window.setpos(@cursor_y,@cursor_x)
    @window.refresh
  end
  
  # カーソルを上へ移動。ウィンドウの上端より上へカーソルが移動
  # しようとした場合はスクロール
  def cursor_up
    if @cursor_y <= 0
      scroll_up
    else
      @cursor_y -= 1
    end
    if @cursor_x >= (@data[@cursor_y + @top_statement ].length)
      @cursor_x = @data[@cursor_y + @top_statement ].length
    end
    @window.setpos(@cursor_y,@cursor_x)
    @window.refresh
  end
  
  # カーソルを左へ移動。ウィンドウの左端より左へカーソルが移動
  # しようとした場合は無視する
  def cursor_left
    unless @cursor_x <= 0
      @cursor_x -= 1
    end
    @window.setpos(@cursor_y,@cursor_x)
    @window.refresh
  end
  
  # カーソルを右へ移動。その行の文字数以上に右に移動しようと
  # した場合は無視する
  def cursor_right
    unless @cursor_x >= (@data[@cursor_y + @top_statement ].length)
      @cursor_x += 1
    end  
    @window.setpos(@cursor_y,@cursor_x)
    @window.refresh
  end
  
  # 上へスクロール
  def scroll_up
    if( @top_statement > 0 )
      # すでにスクロールされている場合のみ上へスクロール
      #(表示されている文字は下へずれる)
      @window.scrl(-1)
      @top_statement -= 1
      # 下へずれた分、空いた場所にデータを一行表示
      str = @data[@top_statement]
      if( str )
        @window.setpos(0, 0)
        @window.addstr(str)
      end
    end
  end

  # 下へスクロール
  def scroll_down
    if( @top_statement + @window.maxy < @data.length )
      # 表示されているデータの最下行が全データの最大行より
      # 小さい場合は下へスクロール
      #(表示されている文字は上へずれる)
      @window.scrl(1)
      # 上へずれた分、空いた場所にデータを一行表示
      str = @data[@top_statement + @window.maxy]
      if( str )
        @window.setpos(@window.maxy - 1, 0)
        @window.addstr(str)
      end
      @top_statement += 1
    end
  end
end

 最後にメインクラスにイベントループ(キー入力を処理するループ)を追加します。

 追加した箇所は、同様にコメントと共にボールドにしてあります。

fe.rb
require "curses"
require "editwind"
require "commandwind"
# [追加]
require "handler"

#ファイル名をコマンドライン引数として受け取る
if ARGV.size != 1
  #コマンドライン引数がない場合
  printf("usage: fe file_name\n");
  exit
else
  file_name = ARGV[0]
end
#コンソール画面を初期化し初期設定を行う
Curses.init_screen
Curses.cbreak
Curses.noecho
# デフォルトウィンドウを取得
defo_wind = Curses.stdscr
# 編集エリアウィンドウを作成
edit_wind = EditWind.new(defo_wind)
# 情報表示エリアウィンドウを作成
cmmd_wind = CommandWind.new(defo_wind,file_name)
# [追加]イベント処理クラスを生成
handler = Handler.new

# ファイルをオープンし内容を編集エリアに表示する
edit_wind.display(file_name)
# [追加]イベントループ
begin
  while true
    ch = edit_wind.getch #1文字入力。
    # イベント処理クラスで処理分岐を行う
    handler = handler.execute(edit_wind,ch)
  end
end
#コンソール画面を終了
Curses.close_screen

 ここまで出来上がったら動かしてみてください。

 [w][s][a][d]キーでカーソルを動かして遊んでみてください。[q]キーでFEは終了します。

まとめ

 今回は、テキストエディタを作り始めました。次回は、編集モードを追加し編集内容を保存できるようにしたいと思います。

参考資料



  • LINEで送る
  • このエントリーをはてなブックマークに追加

バックナンバー

連載:作って覚えるRuby再入門

著者プロフィール

  • 越智 理夫(オチ マサオ)

    株式会社カサレアル プロフェッショナルサービスセンター所属。エンジニア向けトレーニングコースの開発および講師を行う。専門はJava,Ruby,OOAD,DOA,テスト駆動開発など。最近二歳の娘にこき使われている。

あなたにオススメ

All contents copyright © 2005-2021 Shoeisha Co., Ltd. All rights reserved. ver.1.5