pygtkで遊ぼう(23) gtksourceviewの検索の改良

昨日の日記でgtksouceviewの検索機能について

・大文字/小文字を無視する指定ができない。。

という宿題があることを書きました。
今日はその宿題をかたづけました。

今回とった方法は

(1) インクリメンタルサーチにする。
(2) 検索は re モジュールを使用する。
(3) 検索でヒットした全ての文字列をハイライトするのではなく、ヒットした文字列のうち、現在のカーソルに
    もっとも近いもののみとする。
(4) 検索条件をCompile中にエラーが発生した場合、検索は行わない。

です。


上記の(4)は正規検索の条件がまだ不完全な場合、たとえば全ての数字にヒットさせるための「\d」を入力するために
最初の「\」を入力した際、その文字をre.Compile()に指定するとエラーとなることを回避したいからです。


SourceViewサブクラスの検索用のメソッドを修正し、以下のようにしました。

    def search_text(self, keyword, caseSence):
        '''
        保持しているテキストのkeywordを検索する。
        検索結果(文字出現位置のリスト)はメンバ変数に保持する。
        
        keyword:
            検索する文字列            
        caseSence:
            True -> 大文字・小文字を区別する
            False-> 大文字・小文字を区別しない
        RETURN:
            True -> 該当あり
            False-> 該当なしまたはエラー
        '''
        flags = 0
        if caseSence == False:
            flags |= re.IGNORECASE

        try:
            kw = re.compile(keyword.decode('utf-8'), flags)            
            text = self.get_text().decode('utf-8')
        except:
            return False
        
        result = re.finditer(kw, text)
        if result == None:
            return False
        
        self.searchPosList = map(lambda a: a.span(), result)
        return True


次に、検索でヒットした文字列をハイライトさせるメソッドは以下のように修正しました。

    def move_cursor_to_next_search(self, direction=1, move=False):
        '''
        カーソルを検索でヒットした文字列の位置へ移動させる。
        moveフラグがFalseの場合には検索された最初の文字列には移動するが
        それ以降はカーソルは移動しない。2つめ以降の検索結果へ移動したい
        場合には、moveにTrueを指定する。

        direction:
           DIRECTION_NEXT( 1) : 下方向へ移動
           DIRECTION_PREV(-1) : 上方向へ移動
        move:
            True -> 2つ目以降の検索結果にも移動する
            False-> 2つ目以降の検索結果には移動しない
        '''
        bf = self.get_buffer()
        if bf == None:
            return

        # 現在設定さているタグを削除
        if self.searchTag:            
            bf.remove_tag(self.searchTag,
                          bf.get_start_iter(),
                          bf.get_end_iter())
            self.searchTag = None

        # 検索結果(位置情報)が設定されていない場合には
        # 処理を中断
        if not self.searchPosList:
            return

        if self.searchPosIdx != None:
            if move:
                self.searchPosIdx += direction
            if self.searchPosIdx >= len(self.searchPosList):
                self.searchPosIdx = 0
            elif self.searchPosIdx < 0:
                self.searchPosIdx = len(self.searchPosList) - 1
        else:
            curPos = bf.get_property('cursor-position')
            for idx, (start, end) in enumerate(self.searchPosList):
                if start > curPos:
                    self.searchPosIdx = idx
                    break

        # ハイライト表示用のタグを生成して設定
        start, end = self.searchPosList[self.searchPosIdx]
        self.searchTag = bf.create_tag(foreground='#FFFF00',
                                       underline=pango.UNDERLINE_DOUBLE)
        sIter = bf.get_iter_at_offset(start)
        eIter = bf.get_iter_at_offset(end)
        bf.apply_tag(self.searchTag, sIter, eIter)

        # カーソルをハイライト位置の右端に設定する。
        bf.place_cursor(eIter)
        self.scroll_to_iter(eIter, 0.2)


最後に、上記のメソッドを呼び出している部分は以下のようになります。
以下の関数(メソッド)は、検索文字列フィールドが変更されるたびに呼び出されます。

    def __on_search_keyword_changed(self, widget, keyword, caseSence):
        '''
        現在表示されているテキスト中をkeywordで検索する。
        
        keyword:
            検索文字列
        caseSence:
            True  -> 大文字、小文字を区別する。
            False -> 大文字、小文字を区別しない。
        '''
        page, editView = self.__get_current_edit_view()
        if editView == None:
            return

        # 検索
        editView.search_text(keyword, caseSence)
        # カーソルを検索した文字の位置に移動させる
        editView.move_cursor_to_next_search()

今のところ、単一のテキスト中を検索する機能については以上の方法でいこうかと思います。
さて、つぎは、

(1) 現在開かれている全てのテキスト中を検索。
(2) 任意のディレクトリ中のファイル中を検索。
(3) 文字列の置換

などを実装していこうと思います。