pygtkで遊ぼう(19) カスタマイズ可能なエディタを自作中。

少しづつですが、次のようなカスタマイズ機能をもつテキストエディタを作成中です。

(1) テキストエディタキーバインドをカスタマイズ可能。
(2) テキストエディタの見た目をカスタマイズ可能。
(3) テキストエディタで上で選択されたPythonスクリプトをその位置で評価(実行)させることが可能。
(4) プラグインによって機能の拡張が可能。
(5) マクロの登録や実行が可能。
(6) GTKシンタックスハイライト機能を使ったキーワードのハイライト表示。


emacsのように、「環境」の域にまで持っていくつもりはないのですが、かゆいところに手が届くような自分流エディタにしようと企んでいます。(笑
目指すは、Scribesと同程度の編集機能をもっていて、さらにカスタマイズがいろいろと。。といった感じです。
いつになるかはわかりませんが、ちくちくと続けていこうと思います。


とりあえず、現時点ではこんな感じになっています。


ううむ。。ぱっと見た感じ、ありふれすぎていて。。。いけてないですね。。(T_T


現状で、最初に書いた機能のうち、(1)と(2)も一部分ですができあがっています。

このエディタは、起動時に2つの設定ファイル(Pythonスクリプト)をインポートします。

GUIの見た目の定義用スクリプト
キーバインド定義用のスクリプト


GUIの見た目の定義用スクリプト

# -*- coding: utf-8 -*-

import pineconfig

#=============================================
# 編集する場合にはここ以降を。
#=============================================
# 設定方法
# (1) 一括で設定
# pineconfig.set_gtk_properties()関数を使用して一括で設定する
# サンプル
# pineconfig.set_gtk_properties('widget-name',{'property-name': value, ....})
#
# (2) 個別に設定
# pineconfig.set_gtk_property()関数を使用して個別に設定する
# サンプル
# pineconfig.set_gtk_property('widget-name', 'property-name', value)
#

pineconfig.set_gtk_properties('EditorWindow::SourceView',
                              {'auto-indent': True,
                               'insert-spaces-instead-of-tabs': True,
                               'margin': 79,
                               'highlight-current-line': True,
                               'show-line-markers': True,
                               'show-line-numbers': True,
                               'show-margin': True,
                               'smart-home-end': True,
                               'tabs-width': 4})

上記の設定ファイルでは、set_gtk_properties()という関数を使って、
特定の名前のウィジェットの特定のプロパティの値を設定しています。
例えば、行番号の表示を非表示にする場合には、show-line-numbersの値をFalseに設定しておいてエディタを起動しなおすと見た目が変更できます。
この仕組みの利点としては、

・PyGTKのリファレンスに書かれているプロパティ名をそのまま指定できるので、PyGTKを使ったことのある人には馴染みやすい
・PyGTKのリファレンスを参照すれば、設定できるプロパティが一目瞭然

です。
PyGTKのウィジェットのプロパティ以外の設定を増やした場合でも、比較的簡単に拡張できそうかな。。と思っています。


キーバインド定義用スクリプト
以下のスクリプトは、キーがおされた場合に実行される関数を定義しています。
スクリプトの前半には、テキスト操作用の関数がたくさん定義されています。
スクリプトの最後のほうには、bind_key()という関数を使って、キーと関数の組み合わせを登録しています。
作成中のエディタでは、キーが入力されるたびに

キーバインド定義に該当する定義があるかどうかチェックし、あった場合にはその定義にしたがって関数を実行
キーバインド定義が見付からなかった場合にはgtksourceviewのキー入力処理を使用して処理を実行

という処理を行います。すべてのキーが入力されるたびに上記の処理が行われます。

# -*- coding: utf-8 -*-
from pineconfig import *

# モディファイアキーの記述方法
# <control>
# <mod1>〜<mod5>
# 環境にもよりますが、通常Altキーは<mod1>です。
#
# <shift>という設定はありません。
# <control><shift>a ==> <control>Aと指定します。
#=============================================
# 編集する場合にはここ以降を。
#=============================================

## ハンドラ関数
def __move_cursor_left(textview):
    textview.emit('move-cursor', gtk.MOVEMENT_VISUAL_POSITIONS, -1, False)
    
def __move_cursor_right(textview):
    textview.emit('move-cursor', gtk.MOVEMENT_VISUAL_POSITIONS, 1, False)

def __move_cursor_next_line(textview):
    textview.emit('move-cursor', gtk.MOVEMENT_DISPLAY_LINES, 1,  False)

def __move_cursor_prev_line(textview):
    textview.emit('move-cursor', gtk.MOVEMENT_DISPLAY_LINES, -1, False)

def __move_cursor_prev_word(textview):
    textview.emit('move-cursor', gtk.MOVEMENT_WORDS, -1, False)

def __move_cursor_next_word(textview):
    textview.emit('move-cursor', gtk.MOVEMENT_WORDS, 1, False)

def __move_cursor_line_start(textview):
    textview.emit('move-cursor', gtk.MOVEMENT_PARAGRAPH_ENDS, -1, False)

def __move_corsor_line_end(textview):
    textview.emit('move-cursor', gtk.MOVEMENT_PARAGRAPH_ENDS, 1, False)

def __move_cursor_buffer_start(textview):
    textview.emit('move-cursor', gtk.MOVEMENT_BUFFER_ENDS, -1, False)

def __move_corsor_buffer_end(textview):
    textview.emit('move-cursor', gtk.MOVEMENT_BUFFER_ENDS, 1, False)

def __move_cursor_prev_page(textview):
    textview.emit('move-cursor', gtk.BUFFER_PAGES, -1, False)

def __move_cursor_next_page(textview):
    textview.emit('move-cursor', gtk.BUFFER_PAGES, 1, False)

def __undo_buffer(textview):
   textview.emit('undo')
   
def __redo_buffer(textview):
   textview.emit('redo')

def __cut_buffer(textview):
    textview.emit('cut-clipboard')

def __copy_buffer(textview):
    textview.emit('copy-clipboard')

def __paste_buffer(textview):
    textview.emit('paste-clipboard')

def __clear_buffer(textview):
    pass
def __select_all(textview):
    pass

def __delete_backward(textview):
    textview.emit('delete-from-cursor', gtk.DELETE_CHARS, -1)

def __delete_forward(textview):
    textview.emit('delete-from-cursor', gtk.DELETE_CHARS, 1)

def __delete_line(textview):
    textview.emit('delete-from-cursor', gtk.DELETE_PARAGRAPHS, 1)

def __delete_to_line_end(textview):
    textview.emit('delete-from-cursor', gtk.DELETE_PARAGRAPH_ENDS, 1)

def __insert_newline(textview):
    textview.emit('insert-at-cursor', '\n')

def __eval_selection(textview):
    pass

#
# ハンドラ関数の登録
#
bind_key('<control>b', (__move_cursor_left, None))
bind_key('<control>f', (__move_cursor_right, None))
bind_key('<control>n', (__move_cursor_next_line, None))
bind_key('<control>p', (__move_cursor_prev_line, None))
bind_key('<control><mod1>b', (__move_cursor_prev_word, None))
bind_key('<control><mod1>f', (__move_cursor_next_word, None))
bind_key('<control>a', (__move_cursor_line_start, None))
bind_key('<control>e', (__move_corsor_line_end, None))
bind_key('<mod1>t', (__move_cursor_buffer_start, None))
bind_key('<mod1>b', (__move_corsor_buffer_end, None))
bind_key('<mod1>p', (__move_cursor_prev_page, None))
bind_key('<mod1>n', (__move_cursor_next_page, None))
bind_key('<control>z', (__undo_buffer, None))
bind_key('<control>y', (__redo_buffer, None))
bind_key('<control>x', (__cut_buffer, None))
bind_key('<control>c', (__copy_buffer, None))
bind_key('<control>v', (__paste_buffer, None))
## bind_key('<control>p', (__clear_buffer, None))
## bind_key('<control>p', (__select_all, None))
bind_key('<control>h', (__delete_backward, None))
bind_key('<control>d', (__delete_forward, None))
bind_key('<control>k', (__delete_to_line_end, None))
bind_key('<control>l', (__delete_line, None))
bind_key('<control>m', (__insert_newline, None))
## bind_key('<control>p', (__eval_selection, None))

エディタとして完成するまでには、まだまだ時間がかかりそうですが、GTKのSourceView、かなり高機能だということがわかりましたので、今後がたのしみです。

つづく。