CodeZine(コードジン)

特集ページ一覧

RubyとCursesを使ったテキストエディタに編集と保存機能を追加する

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

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

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

目次

動作確認

 ここまでで一旦動かしてみましょう。コマンドプロンプトを立ち上げ、次のように入力してください。(“testfile.txt”はテスト用のテキストファイルです。別途用意してください)

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

 ESCキーを一回押してから、なにかキーを入力すると現在のカーソルの位置に文字が入力されると思います。ESCキーを再度押してから[x]キーを押し、文字を消してみてください。ただし現状では画面上の文字を変更してるだけですので、実際のデータには反映されていません。

テキストクラスの追加

 データの編集を実現するためにいくつかコードを追加する必要がありますが、ソースコードが複雑化する前に、これまで単なる配列として扱っていたテキストデータをクラスとして分けておきましょう。クラスの名前はベタですがTextクラスとします。

 Textクラスの機能としては

  1. これまでと同じように配列ぽくアクセスできる
  2. 指定された位置に1文字追加する
  3. 指定された位置から1文字削除する

 ただし今回は簡略化のため2バイト文字は考慮しません。あらかじめご了承ください。

配列ぽくアクセスする

 前述の通り、これまでテキストデータは配列(Arrayクラス)で保持していたのですが、TextクラスでもArrayクラスと同じような操作方法でアクセスできれば既存のコードの修正も少なくなって少し嬉しいかなと思います。

既存コードで使っているArrayメソッド
@data.push
@data[index]
@data.length

 なにか既存のクラスのメソッドを一部だけ流用したい場合「委譲」を使います。Arrayクラスを継承してきてもよさそうですが、継承は、元となるクラスが持つ全ての操作(要はメソッド)をそのまま受け入れられる/もしくは上書きできる時のみに使う、というのが昨今のオブジェクト指向の考え方になっていますので、今回は継承はしないでおきます。

委譲

 委譲とは、(継承のように)再利用したい機能を自分自身のものにするのではなく、その機能を持つオブジェクトに処理を依頼することをいいます。つまり処理の横流しです。

 Rubyでは、委譲を実現するためのライブラリとして、forwardabledelegateが標準で同梱されています。今回はわかりやすいforwardableの方を使ってTextクラスを作ってみます。

text.rb
require 'Forwardable'

class Text
  extend Forwardable
  def initialize
    # 委譲するArrayインスタンスを生成
    @lines = Array.new
  end
  # Arrayのインスタンス@linesへ委譲するメソッド
  def_delegators :@lines, :[], :length, :push
end

 Forwardableの使い方として、まず委譲を利用したいクラスにForwardableモジュールをextendします。

 extendとはincludeと似たものなのですが、extendを使うとクラスではなくオブジェクトに対してモジュールがMix-inされます。上のようにclass定義中に使うとClassクラスのオブジェクトであるTextクラスにForwardableモジュールがMix-inされて委譲ができるようになるという仕組みです。

 うーむ、すこし分かりにくいですね…。

 ものすごく平たく(&語弊を承知で)言ってしまうとクラス定義用の文法(具体的にはdef_delegators等)が、extendにより増えると理解してもよいでしょう。

 そしてdef_delegatorsを使って、[],length,push等が呼ばれた場合にはそのままArrayオブジェクトの@linesに中継するよと宣言しています。これにより次のようなコーディングが可能となります。

サンプルコード
  txt = Text.new
  txt.push("hoge-hoge")
  txt.push("piyo-piyo")
  p txt[0] # => "hoge-hoge"

指定された位置に1文字追加する

 次はテキストデータに1文字追加するメソッドをTextクラスに追加しましょう。

text.rb
class Text
  ## 中略 ##
  def insert_char(pos_y,pos_x,input_ch)
    # データの範囲内かをチェック
    return unless((pos_y < @lines.size) and (pos_x < (@lines[pos_y].length+1)))
    # 文字列の指定した場所に一文字挿入
    @lines[pos_y].insert(pos_x,input_ch.chr)
  end
end

 カーソルの現在位置と追加する文字を引数として渡すと、テキストデータ@linesにそれを追加します。Stringクラスには指定した位置に文字列を挿入するメソッドがあるので、それを呼ぶだけで処理は完了します。一応その前に範囲チェックもしています。チェックの中で

サンプルコード
@lines[pos_y].length+1

 としているのは、文字をその行に追加する際に、既存の文字列より一つ後ろにカーソルが存在するのを許すためです。

サンプルコード
input_ch.chr

 は文字コードを文字列にするためのメソッドです。

指定された位置から1文字削除する

 さて次は、テキストデータから1文字削除するメソッドを書きます。

text.rb
class Text
  ## 中略 ##
  def delete_char(pos_y,pos_x)
    # データの範囲内かをチェック
    return unless((pos_y < @lines.size) and (pos_x < @lines[pos_y].length))
    # 文字列の指定した場所を一文字削除するために
    # 一旦、文字列を1バイトずつ配列に分解
    arr = @lines[pos_y].split(//)
    # 該当箇所の要素を削除(つまり1バイト削除)
    arr.delete_at(pos_x)
    # 配列を再度文字列に変換
    @lines[pos_y] = arr.to_s
  end
end

 範囲チェックのやり方などは、さっきのinsert_charとほぼ同様です。しかし困ったことにStringクラスには位置の指定で文字を消すメソッドはありません。そこで少し工夫をして次のような処理方法で指定位置の文字の削除を行います。

  1. まず文字列を1バイトずつ分割し配列にする
  2. 指定場所に位置する配列中の要素を削除
  3. 再び文字列に変換

 すこし複雑ですが、irbを使って各メソッドの動きを確かめてみるとよく分かると思います。Rubyで何か細かな処理をする際に、Stringクラス/Arrayクラス/Hashクラスがよく登場します。ですので上記3つのクラスはリファレンスなどを一回熟読しておくことをお勧めします。(ついでに動かしてみるのも強くお勧めします)


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

バックナンバー

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

著者プロフィール

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

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

あなたにオススメ

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