動作確認
ここまで出来上がったら動かしてみましょう。動かすにあたってはテスト用のテキストファイルを用意してください(面倒であれば今回のソースコードでもかまいません)。
コマンドプロンプトを立ち上げ、次のように入力してください。"testfile.txt"はテスト用のテキストファイルの名前ですので、用意したテスト用のテキストファイルに置き換えて入力してください。
c:\>ruby fe.rb testfile.txt
このように表示されれば完成です。なにかキーを入力すると終了します。
スクロールできるようにする
次はキーの入力によってスクロールするようにしてみましょう。
まずクラスを一つ追加します。キーの入力内容によって分岐し処理するHandler
クラスです。カーソルの操作は[w][s][a][d]キーで動かすようにします(ここは完全に私の趣味ですので、他のキーに割り当てても構わないです)。
Handler
クラスにはexecute
メソッドが一つあります。このメソッドは処理対象のウィンドウクラスと入力された文字を一つずつ受け取り、入力内容に応じて適切なメソッドを呼び出します。最後にself(自分自身のオブジェクト)を返しているのは小さな仕掛けを施すためです。これについては次回に説明します。
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未満にもなりません。
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
最後にメインクラスにイベントループ(キー入力を処理するループ)を追加します。
追加した箇所は、同様にコメントと共にボールドにしてあります。
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は終了します。
まとめ
今回は、テキストエディタを作り始めました。次回は、編集モードを追加し編集内容を保存できるようにしたいと思います。