pygtkで遊ぼう(22) gtk.TextViewの検索機能を使う

昨日の日記でPythonの正規検索モジュールで検索した結果をgktsourceview.SourceViewのサブクラスに表示したということを書きました。
で、宿題として以下のことが残っていました。

・検索した結果を全てハイライトさせるため、検索結果が多くなると、検索結果が表示されるまでに時間がかかる。
インクリメンタルサーチにreを使うのはちょっと。。


今日は、上記の問題を解決する方向でいろいろためしてみました。
今回変更したのは、

(1) 検索キーワード文字列に変更があった場合、reモジュールで検索を行わないように。
(2) 1の代わりにSourceViewのサブクラスに下記のメソッドを追加し、それを呼び出すように。
(3) 該当した文字列を全てハイライトせず、カーソルのある位置の文字列のみをハイライトするように。

です。

今回の修正を行った結果、かなりサクサク動くインクリメンタルサーチになりました。
(もう少し調整すればもっとよくなると思います)

少し補則です。
下記のメソッド中でself.searchIterというインスタンス変数を操作していますが、
この変数はカーソルの位置です。検索された文字にカーソルを移動させ、次の検索が行われた場合には、
そのカーソルの直前(または直後)で見付かった文字列に移動させるために使用しています。

    def search_text(self, keyword, direction=1, next=False):
        '''
        テキストバッファ中の指定された文字列を検索する。
        
        keyword:
            検索文字列
        direction:
            検索方向(定数)
            DIRECTION_NEXT(1) : 後方検索
            DIRECTION_PREV(-1) : 前方検索
        next:
            True -> 次の位置へ移動する
            False-> 次の位置へ移動しない
        '''
        bf = self.get_buffer()
        if bf == None:
            return False

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

        # キーワードが指定されていない場合には処理を中断
        if keyword == None or len(keyword) == 0:
                return False

        # 検索領域を取得(バッファ全体)
        start, end = bf.get_bounds()
        if self.searchIter:
            if next:
                offset = self.searchIter.get_offset()
                offset += direction
                self.searchIter = bf.get_iter_at_offset(offset)                
        else:
            if direction == self.DIRECTION_NEXT:
                self.searchIter = start
            else:
                self.searchIter = end
        # 検索
        searchFunc = self.searchIter.forward_search
        if direction < 0:
            searchFunc = self.searchIter.backward_search

        result = searchFunc(keyword, gtk.TEXT_SEARCH_TEXT_ONLY)
        if result == None:
            self.searchIter = None
            return False

        start, end = result
        self.searchTag = bf.create_tag(foreground='#FFFF00',
                                       underline=pango.UNDERLINE_DOUBLE)
        bf.apply_tag(self.searchTag, start, end)

        # カーソル位置が常に見えるように調整
        bf.place_cursor(end)
        self.scroll_to_iter(end, 0.2)
        self.searchIter = start

        return True

さて、今回のお題「TextViewの検索機能」についてですが、
手順としては、

(1) TextViewからTextBufferを取得する。
(2) 取得したTextBufferからからTextIterを取得する。
(3) 取得したTextIterのメソッドforward_search(), backward_search()を使用し、イテレータの直後(または直前)の
    文字列を検索する。

といった感じです。検索自体は結構簡単です。
上のソースコードが長くなってしまったのは、私の技量が足りないせいと、インクリメンタルサーチさせているから。。。だと思います。

で、めでたしめでたし。。。とはいきませんでした。
またまた新たな宿題が。。。。
今回の宿題は

・TextIterのforward_search(), backward_search()はどうやら「大文字、小文字を区別しない」という指定ができないらしい。。

ということです。
ちょっとショックでした。。。

ということで、次回はその宿題をなんとかするべく、いろいろとためしてみようと思います。