Scintillaとmrubyでテキストエディタを作る(6) : folding

Scintillaの豊富な機能のひとつにfoldingがある。”折り畳み”である。 プログラムを編集する際、クラスやメソッド単位で表示・非表示を切り替えるようなものである。

作成中のmrbmacsというエディタでの例だが、folding機能を有効にすると、以下のようにmoduleやclassの左に「-」マークが付く。

f:id:masahino123:20161012224735p:plain

folding機能を有効にするには、foldというpropertyを1にする、という他とは違う処理が必要。 scintillaの内部構造を理解できていないので、おまじないの領域。

機能としてのfoldingは上記の処理で良いが、画面表示上は別の手順が必要となる。 「-」や「+」を表示する領域を用意するため、mrbmacsでは以下のような処理を行っている。

      @view_win.sci_set_margin_widthn(1, 1)
      @view_win.sci_set_margin_typen(1, 0)
      @view_win.sci_set_margin_maskn(1, Scintilla::SC_MASK_FOLDERS)

これで、編集画面の左側にfoldingのためのマークが表示される。

マウスのクリックで表示・非表示を切り替えるための準備は、以下。

      @view_win.sci_set_marginsensitiven(1, 1)
      @view_win.sci_set_automatic_fold(Scintilla::SC_AUTOMATICFOLD_CLICK)

sci_set_automatic_foldというメッセージは簡単に見付かったが、sci_set_marginsensitivenの存在に気付くのに時間がかかった。 これで、マウス操作での表示・非表示が実現できる。

折り畳んだ例は以下。

f:id:masahino123:20161012230350p:plain

ところで、Scintillaのterminal版実装であるscintermにはjinxというサンプルプログラムが付属している。 jinx.cは100行ちょっとのプログラムだが、マウスによる折り畳みまで実装しており、 非常に参考になる。

Scintillaのsyntax highlightingの調査

Scintillaは多数のプログラミング言語に対応していて、syntax highlighting機能もある。

編集中のファイルを言語と対応付けるには、SCI_SETLEXERLANGUAGE(<unused>, const char *name) というコマンドが使える。 nameには言語名を入れれば良い。

syntax highlighting機能で色を設定するには、SCI_STYLESETFORE(int styleNumber, int colour)を使う。 背景色を設定するSCI_STYLESETBACKSCI_STYLESETBOLDSCI_STYLESETITALICというものもある。

styleNumberは各言語の各書式で番号が決まっている。 Scintillainclude/SciLexer.hというヘッダファイルに定義がある。 例えば、

#define SCE_C_DEFAULT 0
#define SCE_C_COMMENT 1
#define SCE_C_COMMENTLINE 2
#define SCE_C_COMMENTDOC 3
#define SCE_C_NUMBER 4

のような感じ。C言語用だとコメント文のstyleNumberは1と思われる。

このような設定が各言語毎にあるので、いちいち設定するのはかなり面倒だし、 なんのことか分らない定義もある。 どこかにドキュメントは無いものか?

rubyの場合、以下のような感じだ。

定義名 番号 意味
SCE_RB_DEFAULT 0 デフォルト?
SCE_RB_ERROR 1 エラー?不明
SCE_RB_COMMENTLINE 2 コメント行
SCE_RB_POD 3 =beginから=endまで
SCE_RB_NUMBER 4 数字
SCE_RB_WORD 5 SCI_SETKEYWORDS(int keyWordSet, const char *keyWordList)で指定したキーワードに対応
SCE_RB_STRING 6 "で囲まれた文字列
SCE_RB_CHARACTER 7 'で囲まれた文字列
SCE_RB_CLASSNAME 8 クラス名
SCE_RB_DEFNAME 9 メソッド名
SCE_RB_OPERATOR 10 各種演算子
SCE_RB_IDENTIFIER 11 クラス名でもメソッド名でもモジュール名でもキーワードでも無いもの
SCE_RB_REGEX 12 正規表現/で囲まれた箇所
SCE_RB_GLOBAL 13 グローバル変数$hogeなど
SCE_RB_SYMBOL 14 シンボル:hogeとか
SCE_RB_MODULE_NAME 15 モジュール名
SCE_RB_INSTANCE_VAR 16 インスタンス変数。@hogeみたいな|
SCE_RB_CLASS_VAR 17 クラス変数。@@hogeとかね
SCE_RB_BACKTICKS 18 バッククォートで囲まれた文字列
SCE_RB_DATASECTION 19 DATA__END__以降の行。この機能使ったことないな
SCE_RB_HERE_DELIM 20 ヒアドキュメントの始まりと終りを示す文字列
SCE_RB_HERE_Q 21 ヒアドキュメントの中身
SCE_RB_HERE_QQ 22 未使用?
SCE_RB_HERE_QX 23 未使用?
SCE_RB_STRING_Q 24 よくわからない?
SCE_RB_STRING_QQ 25 不明?
SCE_RB_STRING_QX 26 これもわからない?
SCE_RB_STRING_QR 27 なんだろう?|
SCE_RB_STRING_QW 28 はて?
SCE_RB_WORD_DEMOTED 29 例えば、stmt if testifみたいなの
SCE_RB_STDIN 30 未使用?
SCE_RB_STDOUT 31 未使用?
SCE_RB_STDERR 40 未使用?
SCE_RB_UPPER_BOUND 41 未使用?

実験しても、よくわからないのもあった。

Scintillaとmrubyでテキストエディタを作る(5) : 単純な検索と置換機能

ScintillaとmrubyでEmacsライクなテキストエディタ(mrbmacs)を作成している。

Scintillaにはテキストエディタに必要な様々な機能が用意されているので、自作エディタになにかしらの機能を追加する手順は、

  1. 実装したい機能を考える
  2. Scintillaのドキュメントから使えそうな機能を探す
  3. 幾つかの機能を組み合わせて実験して、作っていく

という感じ。

検索と置換に関しては、”Searching and replacing”というセクションに各種機能が用意されている。

Scintillaの機能は、SCI_SETxxxxx や SCI_GETxxxxx といった名前のメッセージを送ることで動く。 パラメータは最大2つ設定できる。 mrbmacsで使っているscintermの場合、

scintilla_send_message (sci, iMessage, wParam, lParam)

という関数を使うことで、機能を実行する。

検索

検索にはSCI_SEARCHINTARGET というメッセージが使えそうだ。 これは、あるtargetの範囲からパラメータで指定した文字列を検索するメッセージである。

mrbmacsでは、前方検索の機能をこれで実現し、

view_win.sci_set_target_start(start_pos)
view_win.sci_set_target_end(end_pos)

としておき、

ret = view_win.sci_search_in_target(search_text.length, search_text)
if ret != -1
  view_win.sci_set_sel(view_win.sci_get_target_start, view_win.sci_get_target_end)
end

という処理で検索結果を選択している。

後方検索の場合も、startとendの設定方法を変えれば簡単にできる。


置換

置換に関しては、SCI_REPLACETARGETというメッセージが使えそう。 これは、targetで指定された範囲を引数で指定した文字列で置換するメッセージ。

まず、SCI_SEARCHINTARGETで検索する。 検索結果がTARGETに指定されるので、SCI_REPLACETARGETで置換する。

replace.rb ( https://github.com/masahino/mruby-bin-mrbmacs-curses/blob/master/mrblib/replace.rb ) というファイルで実装した。

Scintillaとmrubyでテキストエディタを作る(4) : GTK版

curses版 のエディタは、そこそこ動くところまで作れたので、他のUIのものも用意できないか検討してみた。

そもそも、ScintillaWindowsMac OS XGTKに対応しているので、まずはその辺りから。 一番簡単そうな、GTKから着手。

curses版(mruby-bin-mrbmacs-curses)と同じように、

mrubyでのGTKバインディングは、mruby-gtk2とmruby-gtk3と2つあった。 どちらか選んで、scintillaもそれ用にビルドする。GTK3でコンパイルするには、GTK3を定義してmakeだ。 あたらしいほうが良いだろう。

で、こんな感じ。

f:id:masahino123:20160919092548p:plain

それっぽい見た目。ただし、まだ実用不可。 編集用のwindowは問題無く作れそうだが、ファイルのopen/saveや検索/置換のダイアログが出来ていない。

せっかくGTKなのだから、FileChooserDialogなど使おうとしたが、runしたところで、以下のエラー。

(<unknown>:27316): Gtk-CRITICAL **: gtk_dialog_run: assertion 'GTK_IS_DIALOG (dialog)' failed

やりかたが分らない・・・

一旦、あきらめて、FileSelectionを試してみる。 FileSelectionはGTK3には無いので、GTK2用にビルドし直し。

やってみると、 なぜか、メインのwindowを作成する前にFileSelection.newを書かないと、ファイル選択画面が出てこない。

そして、FileSelectionのok_buttonとcancel_buttonを取得する方法も分らない・・・

ここで、挫折。

GTKのウィンドウの描画やsignal_connect関係は、単純にC言語で書いたほうが悩みが少ないかも。 ただ、そうすると、mruby-scintilla-gtkはどう使う?

また、今度考えることにして、しばらくcurses版のみをがんばって作っていこう。

Scintillaとmrubyでテキストエディタを作る(3) : mruby-cli化

前回のさらに続き。

ビルド方法の検討

作成中のエディタScintillacurses等のライブラリに依存しまくっているので、ビルドが面倒であった。

また、複数のプラットフォームで利用できないか、検討した。

当初は、buildスクリプトを頑張って用意していたが、なかなか大変だったので、 mruby-cliを使ってマルチプラットフォーム化に取り組んだ。

まず、どっか新しいディレクトリで、

$ mruby-cli --setup mruby-bin-mrbmacs-curses

を実行して、必要なファイルを用意する。

作成された、Dockerfile、Rakefile、build_config.rb、docker-compose.ymlをガシガシ弄る。

x86_64-pc-linux-gnui386-pc-linux-gnux86_64-apple-darwin14、i386-apple-darwin14、i686-w64-mingw32、x86_64-w64-mingw32 で、コンパイルできるところまでは行き着いた。動くかどうかは、未確認なものもあり。

Dockerfileでは、ビルドに必要なcursesとiconvを用意する処理を追加している。

色々と悩んだ末に、WIndows環境ではPDCursesを使うようにしている。 また、mruby-requireを一時的に組込まないようにしている(確か、ビルドでエラーが出たため)。

Linux環境では、iconvはglibcに含まれているため、コンパイル時の-liconvが不要。 build_config.rbでは以下のような場当たり的な対処を施している。

  conf.gem :github => 'masahino/mruby-iconv', :branch => 'add_iconvlist' do |g|
    g.linker.libraries.delete 'iconv'
  end

ここで、実験的にmruby-iconvをフォークして、iconvlistを追加していたことを思い出した。

我ながら、色々と中途半端で良くない・・・

Scintillaとmrubyでテキストエディタを作る(2)

前回 の続き。

用意するmrbgem

mruby-curses

まずはこれが必要だろうと考えた。検討した段階でgithubには存在したので、それを利用した。 その後、幾つか欲しい機能が出てきたので、一旦fork。

GitHub - masahino/mruby-curses

後から考えると、scinterm越しに利用するので、使わない手もあったかもしれない。

mruby-termkey

入力にlibtermkeyを使うので、作った。

GitHub - masahino/mruby-termkey

mruby-scinterm

Scintillacurses向け実装であるscintermを使うため、作成。

GitHub - masahino/mruby-scinterm

mruby-scintilla-base

curses以外のUIにもそのうち対応させたいと思い、共通的な定義をこちらに移した。

GitHub - masahino/mruby-scintilla-base

mruby-bin-mrbmacs-curses

本体。

GitHub - masahino/mruby-bin-mrbmacs-curses: Scintilla base text editor written in mruby

mruby-mrbmacs-base

こちらもcurses以外のUIに対応できるように、共通的な機能を抜き出した。

GitHub - masahino/mruby-mrbmacs-base: Scintilla based text editor

その他のmrbgems
  • mruby-iconv
  • mruby-dir-glob
  • mruby-regexp-pcre
  • mruby-require

あと、これらが依存しているもの。

Scintillaとmrubyでテキストエディタを作る

しばらく前から、Scintilla (http://www.scintilla.org) というテキスト編集用のライブラリとmrubyを使ってテキストエディタを作成している。 検討結果の備忘録として、色々と記録しておきたいと思う。

概要

ScintillaWin32GTK+OS Xに対応しているが、terminalで使いたかったので、 curses実装であるScinterm(https://foicica.com/scinterm/)を主に利用している。

名称はmrbmacs (https://github.com/masahino/mruby-bin-mrbmacs-curses)。 名前から容易に推測できるようにEmacsライクなエディタである。 mrubyのmrbgemとして作成している。

現時点での見た目は、以下のような感じ。 f:id:masahino123:20160912232905p:plain

右側のスクロールバーがいまいちだ。

キー入力には、 libtermkey を使っている。