| 2004/07 | ||||||
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | ||||
| 4 | 5 | 6 | 7 | 8 | 9 | 10 |
| 11 | 12 | 13 | 14 | 15 | 16 | 17 |
| 18 | 19 | 20 | 21 | 22 | 23 | 24 |
| 25 | 26 | 27 | 28 | 29 | 30 | 31 |
| 2004/08 | |||||||
|---|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | |
| 8 | 9 | 10 | 11 | 12 | 13 | 14 | |
| 15 | 16 | 17 | 18 | 19 | 20 | 21 | |
| 22 | 23 | 24 | 25 | 26 | 27 | 28 | |
| 29 | 30 | 31 | |||||
| 2004/09 | |||||||
|---|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | ||||
| 5 | 6 | 7 | 8 | 9 | 10 | 11 | |
| 12 | 13 | 14 | 15 | 16 | 17 | 18 | |
| 19 | 20 | 21 | 22 | 23 | 24 | 25 | |
| 26 | 27 | 28 | 29 | 30 | |||
top > かいはつにっき 2004 Q3
既存の拡張子クラスを *return* を使うように書き換えてみます。
あ、ユーザレベルでスクリプトを書く場合なんですけども。標準でついている html.javascript.txt とかに直接書き加えてったりしてもいいですけども、新しいファイルを配布するときにめどいことになるので、てきとうな自前のスクリプトを読み込むようにして、そちらで拡張子クラスの jscript 側の実体(class_html とか)のプロトタイプに対して関数を加えたりするようにすればいいと思います。
html-userext.javascript.txt とか作って、
var f = class_html.prototype;
f.onKeyPrintable = function (arg, classname, methodname) {
: //好きな動作を書く
:
}
とかするわけですね。
あと、例えば標準の拡張子クラス html を置き換えたい(特に、html の字句解析ルールを置き換えたいけど、html 向けのキーバインドは残したいなどの場合)は、html を親に持つ新しい拡張子クラスを作ってしまってもいいです。
function class_html_ex() {
this.name = 'html_ex';
this.parent = 'html';
this.ext = '\\.html?$'; //拡張子クラス html と同じ
}
var f = class_html_ex.prototype;
f.onInitProp = function (arg, classname, methodname) {
invoke(arg, this.parent, methodname); //とりあえず html で初期化
: //好きな動作を書く
:
}
のような感じ。
ファイル名の拡張子から適用する拡張子クラスを見つける処理は、拡張子クラスのツリーを深さ優先で行います。なので、上の場合 .html なファイルは常に html_ex が対応することになります。
あれ、Q3 もおしまい?
バッファを変更して再表示する際、バッファのどこをどういうふうに変更したかというのはビューのクラスでは直接は関知しないようになっています。そのかわり、ビュークラス側では前回表示した内容を覚えておいて、それと異なる(折り返し)行のみ再表示しているわけです。
で、「異なる」とみなせる条件が、
とかいろいろあるわけですが、「色分けの情報が変わったら」というのは入れてない(色分けの情報はキャッシュしてないし、変わったかどうか判定するために字句解析しなおしたらキャッシュの意味がない)。なので、例えば折り返し 1 行目を変更したことで、折り返し 2 行目以降の色分けが変わったとしても、変更を認識できないです。
ということで、バッファの変更に伴う再描画は段落単位、ビューのスクロールに対する再描画は折り返し行単位で行うようにします。
字句解析の遷移で *return* をするためには、概念上は、段落行ごとにテキスト先頭から解析していった場合の入れ子の状態を蓄えておかなければならないのですが、そんなことをしたらメモリを湯水のように使ってしまいます。その更新も大変です。
なので、*return* したときに遷移の結果を動的に再現させることになります。
することは単純で、字句解析の対象となる段落 α から前方にさかのぼって字句解析していきます。言語 C から *return* しようとしている場合、言語 C の領域の左端を見つけて、その領域のすぐ左隣の言語 B の解析器で段落 α の解析を続行するだけです。
するだけなんですけど。。。解析の中で解析するときに、*return* すべき解析器が遠いとけっこう重くなりそうです。なお、同じ段落の中で transit 〜 *return* する分には、内部のスタックが 1 段ずれるだけなのでそれほど重くはないです(ただし、入れ子が 30 レベルまでに制限されます)。
とりあえず *return* できるようになりました。
ということで、正規表現じゃない文字列を追加するには、
lex.AddKeywords(
'foo',
'1/auto break case char const continue default do ' +
'double else enum extern float for goto if ' +
'int long register return short struct switch signed sizeof ' +
'static typedef union unsigned void volatile while/',
'color:@blue');
な感じで、キーワードをスペースで区切って指定します。ということは、スペースはキーワードにできないということです。また、キーワードの境界は、「左右とも [^a-zA-Z_] であること」が条件です。この辺って言語によって変えられたほうがいいのかな?
これで、同じ照合を正規表現で行うよりは、いくらかましになると思います。
なんか隕石が来ているそうですね。超ヤバいです。
いろいろ使ってみると不便な点がたくさん出てきます。
前に書いた気もしますが、プロパティの値を ini ファイルに書き出しているので、プロパティをいじるコードをスクリプトに追加しても起動するとそれが反映されないという動作をしてしまいます。これをどうするか?
スクリプトが有効のときは ini ファイルに書き出さないようにしちゃおうかなあ。そのかわり、環境設定画面でのプロパティの変更は、そのとき限りということになってしまいます。
あるいは、ini ファイルの代わりにスクリプトファイルを直接書き換えて、プロパティの設定コードを出力してしまうとか。すごく。。。力技です。。。
やっぱり *return* が使えないと不便だなあ。ということで考え中。
大量にルールを追加するとどんどん重くなるわけですが、正規表現を使うまでもない(固定のキーワードとか)照合はそれ専用のルーチンを用意したほうがいいかなあ。
複数の言語に対して、コンテキストに応じた色分けをするのは PeggyPro くらいかな、と思ってたら EmEditor もそうなのかな? ただ language や type 属性までは見てくれないような。
タブとスクロールバーのスプリッタをドラッグすると例外が発生するのを修正。
コマンドラインからエンコード名を指定すると強制終了するのを修正。
スクリプトを無効にすると ini ファイルの [script] セクションが消えてしまうのを修正。
*return* をどうにかして実装できないものかな。。。
Caret オブジェクトに追加:
ありがちですが、ウィンドウのタイトルを指定のフォーマット文字列にしたがって構成するようにしてみました。
とりあえず 0.5 を上げてみました。
とりあえず残り半分も書いてみました。
なんかすべてのドキュメントが新しくなってるように見えますが、スタイルをいじっただけなので色分けドキュメント以外は中身はかわってないです。
まだカラーテーマを作ってないけど、公開してしまおうかな。。。
といっても、あまりいろんな言語を知ってるわけじゃないんですよねー。簡単そうな VBScript でも書いてみようかな。
と思ったら VBScript の(字句解析をどうするか〜レベルの)文法の解説ってどこにあるのかわかりません。。。とりあえず適当に作ってみました。
カラーテーマ用の色名ですが、'@keyword' とかがどういう色に定義されているかわからない場合もあるので、基本の 16 色に対応する色('@black' 〜 '@purple')も認めるようにしました。
カラーテーマを書くときはほかの色と同じ扱いなので、まったく違う色に定義しなおしてもかまいません。が、いちおう位置付けとしては、「どんなカラーテーマでも '@blue' とか指定したらだいたい青っぽい色が割り当てられている」という前提にしたいので、どうぞよろしくです。
スタイルシートも作ってみました。プロパティに設定する値がけっこう多いのでそれは色分けの対象に入れませんでした。。。(一覧がわからない)。しかも文法が合っているのかいまいち自信が。
さて、C、javascript、VBScript、html、css と作ってみましたが、あとは Delphi くらいしか知らないなー。色分け定義のドキュメントを作ったほうがよさそうです。
ドキュメントを半分書いたら気力が。。。
いろいろ直して html の定義を書いてみました。正確には、だいたい XHTML なのですが。タグの中が問題で。。。

という状態遷移図に従うことにしました。そうすると、
なんてことができるわけです。なので、知らない識別子に下線を引いたりすれば、分かりやすそうです、たぶん。右の例だと lang 属性は XHTML(1.0) では推奨されないそうなので、色付けする代わりに下線を引いています。
それにしても、こういった状態の遷移にも対応できるようにしたものの、実際にやってみると定義を書くのがけっこうめどいです。単一の状態の中で色分け要素を列挙するだけにしても、実用上困らないような。
ああ。。。次は script だ。。。腹の中がパンパンだぜ。
こんな感じに。あとは <style> タグも同じようにして、スタイルシート用の色分けをすればいいのかな?
exe 側の仕様はだいたい固まったと思うので、適当にいろんな言語のクラスを書いてみます。
やはり、解析器の中で状態を管理したほうが速度的にもよさそうです。
ということで、状態を数値で管理するようにして、解析器の中で 0 〜 31 の状態を取れるようにします。
そいで、
//'script' を見つけたら状態 1 へ移行する
lex.Add(
'', 'state:0/\\<script\\>/i', 'color:@keyword1; state:1');
//状態 1 のときに 'language' を見つけたら 2 へ移行する
lex.Add(
'', 'state:1/language/i', 'color:@identifier1; state:2');
//状態 2 のときに言語名を見つけたら 3 へ移行する
lex.Add(
'', 'state:2/"?javascript"?/i', 'color:@string1; state:3');
//状態 3 のときに '>' を見つけたら javascript の解析器へ移行する
lex.Add(
'', 'state:3//?>/', 'transit:javascript');
//これ以降はすべての状態でマッチされるルール
lex.Add(
'comment',
'/<!--/',
'color:@comment1; transit:html-comment');
lex.Add(
'xml-declaration',
'/<\\?/',
'color:@keyword1; transit:html-xmldecl');
:
:
ステートを指定すると、解析器内部の状態と一致するときだけそのルールがテストされるようになります。ふつうの Add() で追加したルールは、すべての状態でテストされます。これだと、もしかしたら yacc のように「n 個めの '%%' で解析対象が変わる」とかでもいけそうだなあ。
よく考えると、なんかこのしくみだと解析器は 1 個で済んでしまうような気がしないでもありませんが。。。言語の中のコメント程度はこっちで定義したほうが、無駄な解析器を作ることがなくてよいのかも。
解析器の内部状態で済ますか、独立した解析器へ移行するかは、
なんかを目安にすればよいと思います。
<script language="..."> (スクリプト本体) </script>
<script type="..."> (スクリプト本体) </script>
<script language="..." src="..."></script>
<script type="..." src="..."></script>
のどれかを書いておけばだいたい安心なわけですが、これの場合 /* 〜 */ コメントのように、ある記号を見つけたら即字句解析器を変更する、というわけにはいかないです。
場合に、'>' での遷移先が html ではなく javascript になる。この判断を、どこでだれがどうやって行うのか? ということを考えなければいけません。
単純に 'script' を見つけたら、決めうちで javascript とかにしてもいいのですが、そうすると html の中に javascript と VBScript が共存しているファイルを解析できません(そんなソースあるんだろうか。。。という気もしますが。runat="server" とかしている asp とかかな?)。
たとえば、
lex.Add('tag-script', '\\<script\\>', 'script:true');
lex.Add('attribute-type', 'type', 'script:true');
lex.Add('mime-type', '"?text/javascript"?', 'script:true');
lex.Add('terminator', '/?>', 'script:true');
とかやって、マッチするたび javascript に制御を移して、javascript 側で状態を管理するとともに、適用すべきスタイルを返してもらうようにすればどうにでもなりそうですが、遅そう。
ふつうは複数のルールのうち最初にマッチしたら、残りのマッチは飛ばしてしまうわけですが、表示系のスタイルを設定していないときは残りのルールを見るようにするとか。
ついでに、transit の移行先を置き換えるスタイルも設ける。
1 lex.Add(
'tag-script1',
'\\<script +type *= *"?text/javascript"?',
'subst:javascript');
2 lex.Add(
'tag-script2',
'\\<script +language *= *"?javascript"?',
'subst:javascript');
3 lex.Add(
'terminator', '/?>', 'transit:html');
4 lex.Add(
'keyword',
'/\\<(a|abbr|acronym|address|applet|area|b|base|basefont|' +
'bdo|big|blockquote|body|br|button|caption|center|cite|' +
'code|col|colgroup|dd|del|dfn|dir|div|dl|dt|em|fieldset|' +
'font|form|frame|frameset|h[1-6]|head|hr|html|i|iframe|' +
'img|input|ins|isindex|kbd|label|legend|li|link|map|menu|' +
'meta|noframes|noscript|object|ol|optgroup|option|p|param|' +
'pre|q|rb|rbc|rp|rt|rtc|ruby|s|samp|script|select|small|' +
'span|strike|strong|style|sub|sup|table|tbody|td|textarea|' +
'tfoot|th|thead|title|tr|tt|u|ul|var)\\>/i',
'color:@keyword3');
まず 1 行めで 'script type="text/javascript"' を見つけると、移行先を 'javascript' にします。で、マッチ開始位置はそのままにして、この後に続くルールのマッチを行います(4 行めにマッチして、色付けされる)。
次に、'>' を見つけたときに解析器を移行するのですが、すでに解析器 'javascript' へ移行するように置き換えられているので、html ではなく javascript を解析するようになる。
でもこれだと、
<script
type="text/javascript">
なんて書き方に対応できないですね。色分けは 1 行単位に行うので。。。
複数の表示色を考えたとき、背景色についても配色を考える必要があるのですが、背景に画像を表示すると設定した背景色は透過色として扱われるので、なんというか台無しです。センスのある人はそれでもうまく配色するんでしょうが。。。
わたしは早々にあきらめたので、かわりに背景画像に対して、従来のそのまま表示、明るく表示、暗く表示に加えて、「背景色と合成」というのを実装して、背景色と背景画像のいいとこ取りを狙ってみました。これで解決?
と思ったらめちゃくちゃ重いのですが。。。なんかスクロールすると CPU 使用率 100% になるし。うちのマシンのビデオカードがどれだけ速いのかわかりませんが(RADEON VE というやつらしい)、どうなんでしょうか。。。
とりあえず速いビデオカードを使っている人向けのために、残しておきます。
ちなみにこんな感じです。上のビューは白の背景色、下のビューは緑の背景色でそれぞれ合成しているわけです。
javascript と html の色分けをしてみます。javascript って数値はぜんぶ実数だったのかー。
起動時に読み込むスクリプトに文法エラーがあった場合でも、これを出しているのですが、スクリプトのエラーは萌ディタ自身がやっちまったわけではないので、そのときは違うのを出してみるようにしました。こうしてみるとメッセージが、「責任転嫁、必死だな!」って感じがします。

この画像は、Photoshop で作っただけなのですが。構文解析をしない状態で特定の領域の開始行、終了行を抽出しようとしたら、「それっぽい正規表現にマッチする」くらいしか思いつかないな。。。
カラーテーマを切り替えられるようにしてみました。
まず、従来の色名に加えて、以下の色名がつかえるようになります。
| 色名 | 色付けが適用される対象 |
|---|---|
| @foreground | 前景色 |
| @background | 背景色 |
| @linenumber | 行番号色 |
| @newline | 改行色 |
| @eof | eof色 |
| @linenumberguide | 行番号との区切り |
| @wrapguide | 折り返し位置の区切り |
| @eofguide | eof行の区切り |
| @currentlineguide | 現在行の区切り |
| @currentlineguideime | 現在行の区切り(ime on 時) |
| @infoboxguide | ビュー情報の区切り |
| @infoboxfilename | ビュー情報内のファイル名 |
| @mark | マーク |
| @tab | タブ |
| @space | U+0020 SPACE |
| @altspace |
U+00A0 NO-BREAK SPACE U+1680 OGHAM SPACE MARK U+180E MONGOLIAN VOWEL SEPARATOR U+2000 EN QUAD U+2001 EM QUAD U+2002 EN SPACE U+2003 EM SPACE U+2004 THREE-PER-EM SPACE U+2005 FOUR-PER-EM SPACE U+2006 SIX-PER-EM SPACE U+2007 FIGURE SPACE U+2008 PUNCTUATION SPACE U+2009 THIN SPACE U+200A HAIR SPACE U+200B ZERO WIDTH SPACE U+202F NARROW NO-BREAK SPACE U+205F MEDIUM MATHEMATICAL SPACE |
| @igspace | U+3000 IDEOGRAPHIC SPACE |
| @identifier1 〜 @identifier5 | 識別子 1 〜 5 |
| @altforeground1 〜 @altforeground5 | 一部の前景色 1 〜 5 |
| @altbackground1 〜 @altbackground5 | 一部の背景色 1 〜 5 |
| @keyword1 〜 @keyword5 | キーワード 1 〜 5 |
| @operator1 〜 @operator5 | 演算子 1 〜 5 |
| @string1 〜 @string5 | 文字列 1 〜 5 |
| @comment1 〜 @comment5 | コメント 1 〜 5 |
| @number1 〜 @number5 | 数値 1 〜 5 |
| @link1 〜 @link5 | ハイパーリンク 1 〜 5 |
次に、exe のあるパスに 'theme' というディレクトリがあって、その下に ini ファイルを置いておきます。'春.ini' とかつけておくと、メニューには '春' と出てきます。このファイルが、'@' つきの色名を実際の色名にマッピングするテーブルになっているわけです。
[palette]
foreground=white
background=#808080
:
:
number4=#0000FF
number5=fuchsia
それから、スクリプトで、カラーテーマの色を反映させたい個所にテーマの色を指定しておきます。
App.Prop('textfile', 'background-color') = '@background';
とか
lex.Add('tag', '<html>', 'color:@keyword1');
とか。
で、カラーテーマを切り替えると、自動的に色が変わります。スクリプトで設定するときは、プロパティ 'color-theme' に '春' とか、'pascal ソース(黒背景)' とかを設定します。
ためしにカラーテーマを作ってみたのですが。。。わたしには、色彩のセンスがないことを思い知りました。
とりあえず C で色分けしてみました。なんで C かというと、たまたま K&R が手元にあったからだったりします。純粋な C のソースを探してみたら 6 年くらい前に書いたのが出てきました。なんかスクリプトエンジンを書いてた。
で、案の定、ルールの数が増えてくると体感で遅くなってきます。いろいろ高速化を考える必要がありそうです。
スクリプト側の定義はこんな感じです。
f.onInitProp = function (arg, classname, methodname) {
var lex = App.Lexes.Add('c');
App.Prop(this.name, 'lex') = lex.Name;
lex.DefaultColor = 'gray';
lex.Add(
'literal',
'L?"([^"]|\\\\")*"',
'color:aqua');
lex.Add(
'char-const',
"'([^']|\\\\')*'",
'color:teal');
lex.Add(
'keyword',
'\\<(auto|break|case|char|const|continue|' +
'default|do|double|else|enum|extern|' +
'float|for|goto|ifint|long|register|return|' +
'short|struct|switch|signed|sizeof|static|' +
'typedef|union|unsigned|void|volatile)\\>',
'color:red');
lex.Add(
'pp-directive',
'^# *(define|undef|include|line|error|pragma|' +
'if|ifdef|ifndef|endif|elif|else)\\>',
'color:gray');
lex.Add(
'single-comment',
'//.*$',
'color:lime');
lex.Add(
'block-comment',
'/\\*',
'color:lime; transit:c-comment');
lex.Add(
'identifier',
'[a-zA-Z_][a-zA-Z0-9_]*',
'color:yellow');
lex.Add(
'float-const',
'/((([0-9]*\\.[0-9]+|[0-9]+\\.)(e[-+]?[0-9]+)?|' +
'[0-9]+e[-+]?[0-9]+))[fl]?/i',
'color:fuchsia');
lex.Add(
'int-const',
'/([1-9][0-9]*|0(x[0-9a-f]+|[0-7]*))(ul|u|l)?/i',
'color:fuchsia');
lex = App.Lexes.Add('c-comment');
lex.DefaultColor = 'lime';
lex.Add(
'terminator',
'\\*/',
'color:lime; transit:c');
};
いかにもめんどうそうなパターンがたくさん。
プリプロセッサのディレクティブを忘れていたので追加しました。めどいのでやりませんでしたが、ほんとはもっと複雑で、別の解析器に移行させなければならないですね。
高速化。
段落中のある文字でマッチに失敗したとしても、正規表現エンジン側で段落最後まで走査してしまうのがかなり無駄です。正規表現エンジン側ではマッチに失敗したら即戻るようにして、マッチ開始位置のインクリメントは解析器側で一括して行うようにしました。無駄に段落を走査することがなくなった。
それから、最初にマッチしたルールで色分け確定するようにしました(今までは、常にすべてのマッチを行って、最長のものを採用していた)。そのかわり、解析器にルールを追加する順番、あるいはパターンの書き方がちょっとだけ重要になるかも。
正規表現をコンパイルした結果を、呼び出し側が用意したバッファに格納して、マッチ時は直接それを実行するようにしました。あんまり効果ないのかなーと思ってたらめちゃくちゃ効果があった。速度のチューニングを勘でやるのはやっぱり禁じ手なんですね。。。
ということで、わりと普通に使える感じに。キャッシュはしなくても大丈夫そうです。
正規表現エンジンを使うときに Create するのがめどいので、1個だけシングルトンで作っておいて、簡単なマッチはそれを使いまわすようにしてみました。
プロパティ ext は、拡張子クラスを適用する拡張子を空白で挟んで指定するようにしていましたが、正規表現で指定するようにしました。
function class_c() {
this.name = 'c';
this.parent = 'srcfile';
this.ext = '\\.(c|h|cpp|hpp)$';
}
とこんな風になるわけですが。というか、これ拡張子じゃないじゃん、という気も。'^foo\.txt$' とか指定すれば foo.txt 専用の拡張子クラスが作れたりしますね。まあそれはそれで、いいか。
ほかにもマッチが必要なところは随時正規表現を用いるように置き換えてしまってもよいかも。
複数の言語を入れ子に扱う基礎ができたかも。
まず、解析器に登録するルール(のスタイル)に 'transit' というのを追加しました。
var lex = App.Lexes.Add('c');
// '/*' があったらコメントに移行する
lex.Add('comment-start', '/\\*', 'color:lime; transit:c-comment');
上の例は、C に対応する解析器 'c' のルールにコメントの開始記号 '/*' を追加して、それを見つけたら解析器 'c-comment' に移行するよう指示しています。
次に、その解析器 'c-comment' を定義します。
//コメント解析器
var lex = App.Lexes.Add('c-comment');
// 'なんたらかんたら */' のパターン
lex.Add('terminate', '.*\\*/', 'color:lime; transit:*return*');
// 1 行まるごとコメントのパターン
lex.Add('non-terminate', '.*', 'color:lime');
最初に Add しているのは、コメントの終了記号 '*/' を最後に含んだ、任意の文字列です。この場合、解析器 'c' に戻す必要があるので、スタイルに 'transit:*return*' を指定しています。'*return*' は解析器名ではなく、移行元に戻ることを意味しているわけです。
次に Add しているのは終了記号を含まない任意の文字列です。
ということで、コメントに限らず、html に含まれる javascript だとか、そんな感じのファイルも割と正確に字句解析できると思います。C のコメントは 1 回しか入れ子になりませんが、解析器はとりあえず最大 15 回入れ子にできます。
ちなみに正規表現エンジンに否定先読みを実装するとコメントの定義はもっと簡単になりそうだなあ。
ついでなのでスタイルに背景色、アンダーライン、打ち消し線、アッパーラインを追加してみました。
大いなる問題が。
折り返し行ごとに再表示するかの判断に、
というのがあって、解析器 ID は段落のノードに in_lex, out_lex : byte という形で持っています。これ以外は持っていない。で、そうすると、入れ子になっている段落を解析したとき、入れ子の状態を再現できないです。ということは、'transit: *return*' を使えなくなってしまいます。
しょうがないので、'*return*' のかわりに、直接遷移する解析器を指定するようにします。これだと、入れ子の個数制限はなくなるものの、解析器の使いまわしができないです。例えば C と Java の解析器でそれぞれに専用のコメント解析器を定義しなければいけない(定義するものはほとんど同じなのに。。。)。無駄といえば無駄だなあ。
解析器にデフォルトの前景色を指定できるようにしてみました。例えば C のコメントは、
//コメント解析器
var lex = App.Lexes.Add('c-comment');
//デフォルト前景色
lex.DefaultColor = 'lime';
// '*/' を見つけたら解析器 'c' へ移行する
lex.Add('terminate', '\\*/', 'color:lime; transit:c');
という感じになります。もちろん、'*/' 以外のルールを加えてもかまいません。'@author' とかを別の色で表示したりするとよいのかも。
字句解析とは関係なかったりあったりする色の指定ですが。
背景の画像を「明るく表示」「そのまま表示」「暗く表示」なんてのがあるので、同一の色を指定してもその設定次第で見にくくなったりします。色の指定を簡単に変えられないものか。
色についてはいまのところ、以下のような指定と、#RRGGBB、#RGB の形式が可能です。基本の 16 色以外は、マシンの環境によって異なります。
| black | |
|---|---|
| gray | |
| silver | |
| white | |
| red | |
| yellow | |
| lime | |
| aqua | |
| blue | |
| fuchsia | |
| maroon | |
| olive | |
| green | |
| teal | |
| navy | |
| purple |
| activeborder アクティブなウィンドウの枠 | |
|---|---|
| activecaption アクティブなウィンドウのタイトルバー | |
| appworkspace アプリケーション内のウィンドウの背景 | |
| background デスクトップの背景 | |
| buttonface 立体的なボタンの表面 | |
| buttonhighlight 立体的なボタンの光の当たっている面 | |
| buttonshadow 立体的なボタンの影になってる面 | |
| buttontext 立体的なボタンのテキスト | |
| captiontext タイトルバーのテキスト | |
| graytext 選択できないテキスト | |
| highlight 選択している状態 | |
| highlighttext 選択しているテキスト | |
| inactiveborder 非アクティブウィンドウの枠 | |
| inactivecaption 非アクティブウィンドウのタイトルバー |
| inactivecaptiontext 非アクティブウィンドウのタイトルバーテキスト | |
|---|---|
| infobackground ツールチップの背景 | |
| infotext ツールチップのテキスト | |
| menu メニューの背景 | |
| menutext メニューのテキスト | |
| scrollbar スクロールバー | |
| threeddarkshadow 立体的に表示される部分の暗い影 | |
| threedface 立体的に表示される部分の表面 | |
| threedhighlight 立体的に表示される部分の光の当たっている面 | |
| threedlightshadow 立体的に表示される部分の明るい影 | |
| threedshadow 立体的に表示される部分の影 | |
| window ウィンドウ | |
| windowframe ウィンドウのフレーム | |
| windowtext ウィンドウのテキスト |
で、これ以外に例えば '@custom' みたいな感じでユーザ定義の色名を許すようにして、実際にどの色にマップするかのテーブルを提供すればいいんじゃないでしょうか。
それも例によってスクリプトに公開して、テーブルに名前を付けて管理できるようにすれば、決まったテーマに沿った色合いに簡単に切り替えることができそうです。というか、実は「書式(O)」メニューの「カラー テーマ」というのはそういうのを狙っていたわけです。驚愕の事実。
そいで、テーブルにつけた「春」とか「アイス」とかの、いかにもな名前がカラーテーマメニューに出てきて、適当なのを選ぶと、ユーザ定義の色を指定した要素がどひゅーと切り替わるという寸法です。
解析器への定義を考えると、差分によるやりかたにしてもあまりメリットがなさそうな気がします(かえって、常に複数の解析器に対して、「あの定義はここでした」「この定義はあそこでした」となりそう)。とりあえず独立させる方向で行きます。
で、こんな風に。まずスクリプトの onInitProp で
f.onInitProp = function (arg, classname, methodname) {
:
:
var lex = App.Lexes.Add('std'); //字句解析器 'std' を生成
lex.Add( //URI を色分け対象に追加
'uri' //このルールの名前
'h?ttp://[-;?:@&=+$,./_!~\'()%#a-zA-Z0-9]+', //パターン
'color:aqua'); //スタイル
App.Prop(this.name, 'lex') = 'std'; //この拡張子クラスの解析器に設定
:
:
}
などと定義します。正規表現がものすごくうそ臭いですが。。。そうすると、右のような感じに。やっといまどきのエディタっぽくなってきた。
今のところ指定できるスタイルは文字の色だけですが、背景色、アンダーライン、オーバーライン、カーソルの形状なども指定できるようにしたらよいかも。
ついでにマウスのクリック時に、ルールの名前を渡すようにしたり、キャレットが位置する部分のルール名をスクリプトで参照できるようにすれば、いろいろ応用できそうです。URI だったらブラウザを〜とか。
あと、色分けするためには、裏で正規表現のマッチングがびゅんびゅん行われるわけなので、いろいろと高速化しなければいけません。
拡張子クラスのほうは、差分によって環境を定義していくことができるわけですが、字句解析器を昨日考えたようにすると、それぞれの解析器は独立することになってしまいます。継承のしくみを入れたとしても、一部だけ差し替えるのはちょっと難しいです。
どうしよう。
どうでもいいのですが、解析器のコレクションの名前はなにがいいんでしょう。LexicalAnalyzers というのは長すぎなのですが。Lexes とかでもいいのかな。。。
仮に、字句解析を汎用的に作った場合、パラメータを変えることでいろいろな言語に対応させることになります。まず字句解析器のリストをスクリプトに公開して、名前で個々の解析器を管理できるようにします。で、色分け対象(の正規表現)とそれに対応する表示上の属性を設定します。
C の場合だと、識別子、キーワード、定数(整定数、浮動小数点定数、文字定数、列挙定数)、文字列リテラル、演算子、その他の区切子について認識する必要があって、
/* キーワード */
App.Lex('c').Add(
'(auto|break|case ...(中略)... void|volatile|while)\>',
'color:blue');
/* 識別子 */
App.Lex('c').Add(
'[a-zA-Z_][a-zA-Z0-9_]*',
'color:black');
/* 文字列リテラル */
App.Lex('c').Add(
'"([^"]|\\")*"',
'color:maroon');
/* 整定数 */
App.Lex('c').Add(
'([1-9][0-9]*|0([Xx][0-9a-fA-F]+|[0-7]*))',
'color:magenta');
/* 演算子 */
App.Lex('c').Add(
'(++|--|&&|\|\||->|[][,.?:()]|([-+<>=!*/%&^|~]|<<|>>)=?)',
'color:silver');
/* 区切子 */
App.Lex('c').Add(
'[{};]',
'color:white');
:
:
:
な感じで設定していけばいいかな(上の間違ってても、気にしない)。で、段落に対して、空白をスキップ→設定されたパターンでマッチというのを繰り返して、色情報を作ります。
で、拡張子クラスごとに、どの解析器を使うかをプロパティで指定するようにして、表示のときにこれを解析しながら色を変えていけばいいんじゃないかなーと。速度に問題があれば、なんかキャッシュするかもしれません。
if (a == 0xff) break; //comment
-- 1 文字目から 2 文字分: キーワード, blue
- 4 文字目から 1 文字分: 区切子, white
- 5 文字目から 1 文字分: 識別子, black
-- 7 文字目から 2 文字分: 演算子, silver
---- 10 文字目から 4 文字分: 整定数, magenta
- 14 文字目から 1 文字分: 区切子, white
----- 16 文字目から 5 文字分: キーワード, blue
- 21 文字目から 1 文字分: 区切子, white
--------- 23 文字目から 9 文字分: コメント, green
これの問題は、字句解析だけである程度構造を把握できるような言語じゃないと破綻してしまうことです。javascript だと、'/' は除算の演算子であったり、正規表現リテラルのデリミタだったりして、これは構文解析しないと判断できないです。
きっちり構文解析すれば、キャレットの移動なんかも、関数定義単位、文単位、式単位とかできるようになるので、それはそれで(かなり)便利そうなのですが。。。言語ごとにパーサを書くことになってしまいます。それとも emacs の S 式のように、ある程度一般化したものにすればいいのかな。
文字入力の結果スクロールが必要になる場合に再描画に失敗するのを修正。
IME からの入力を直前の Undo と結合させないように修正。
色分け。たいていの場合、色分け機能というのはプログラム言語に対して適用されると思います。とりあえずそれに絞って考えます。
プログラム言語に対する色分けは、文字単位ではなくトークン単位になるのが特徴的だと思います。'function' が色分けの対象だったとして、変数 'functionPointer' の一部に色づけされるべきではありません。これは、'functionPointer' 全体で 1 つのトークンだからです。
で、このトークンの定義というのは言語によって異なるので、その差異をどこで、どうやって吸収するかを考える必要がありそうです。
あと、テキストが依存する言語の文法は常に 1 種類とは限らないのもめどいところだと思います。html でもスクリプトやスタイルシートが混ざる場合もあるし、単一のプログラム言語でさえ、文字列リテラルやコメントブロックは厳密には独立した文法を持つ言語と考えられるからです。入れ子になった言語をどう扱うか。
あ。
常に Undo の結合をしないようになっていました。こっそり修正しました。あと、changelog に書き忘れてた項目を追加しました。
Undo 関連のバグを修正してまた nightly build を上げました。
Undo/Redo 実装済みの nightly build を上げました。
次は色分け?
置換をしていく場合、1 個所見つけるたびにユーザに問い合わせを行うわけです。そこでは、大きく分けて「すべて置換」かそれ以外の置換で分けられます。
で、すべて置換した場合も操作 1 回 = 置換 1 回の形で操作を保持していくと、大量に置換した場合 Undo リストにすべて入りきらないことがありえます。なので、すべて置換した場合は 1 つの操作がもつ情報の中に置換した位置やらなにやらを全て詰め込んだほうがよいと思います。
ということで、ふつうに置換していった場合は 1 操作について 1 回の置換処理を入れることになって、Undo して戻るのも置換処理 1 個ずつ。これに対してすべて置換した場合は、 Undo すると置換処理で影響を受けた変更点がずばーっと元に戻るという動きになります。
ということで、そうしてみました。やっと公開できそうなレベルになってきたかも。
Caret オブジェクトにメソッド Undo、Redo を追加。アクションに Undo、Redo、OpHist を追加。
連続して文字を入力してった場合、単一の操作にまとめるわけですが、常にまとめるわけではなく、直前に入力した文字と新しく入力した文字が単語の一部とみなせる場合に限られます。
たとえば、'a'、'b'、'c' を順に入力するとまとめられますが、そのあと 'あ' を入力すると、それは別の操作となります。なので、Undo していくとまず 'あ' が消え、次に 'abc' が消えるようになります。
で、それはそれでよいのですが、IME から文章をどばっと入力した場合はそういう判定を通らずにひとまとめに単一操作にしてしまうので、当然 Undo/Redo もどばっと消えたり入力されたりします。
入力操作をリストに追加するとき、一気に追加しないで単語ごとに区切って追加していけばいいだけの話なのですが、文字の入力まわりにほいほい処理を追加するのはちょっとためらわれるところです。
なんか手持ちのエディタで試してみたら、すべて、IME からの入力は単一の操作にしているみたいです。どのエディタも同じ動作をするのはめずらしいです。
とりあえずそれに倣います。
プロパティに operation-history-max を追加しました。
1 度書いたような気もしますが、Undo 用の情報の最大サイズの指定がキロバイトだとして、実際どのくらいの操作を保持できるものなのか、よくわからないです。
ということで、操作の手数を単位に指定するようにしてみました。
範囲選択の置き換えなど、内部的に複数の処理が発生する操作は、それらをまとめて 1 手と数えます。簡単にいうと、編集の履歴ウィンドウに出てくる 1 行 = 1 手です。
あー、やっと置換操作のクラスを書くところまできました。
なんか最近、まったくコメントを書かなくなったのですが。時間をおいて改めてみたとき、やってることを把握できるのかなぁ。それほどスパゲッティーにはなっていないと思いますが。。。いまのところ 50000 行くらいです。
8 月 30 日のは、そのまま実装すると微妙に使いづらいです。キー入力のたびにステータスバーを更新するのでもたつく感じがするし。。。けど、捨てるのももったいない気がします。
そこで、Undo/Redo したタイミングで次の操作内容を表示するようにしてみました。
どうなんでしょう。

更新フラグとの絡み。
まず、保存操作も Undo と Redo の対象にします。これは、Undo 時は更新フラグを立て、Redo 時は更新フラグを下ろすようにします(実際は、Undo 操作はなぜか必要ない)。それから、操作の属性に「後続可能かどうか」というのを加えます。
ある時点で Undo/Redo できるかを判断するために、リスト中の着目点を保持するのですが、後続可能な操作は着目点にならないことにします。つまり図の 2 に着目しているとき、次の Redo 対象は 2.5 ではなく 3 だったりというふうに飛び越えるわけです。同様に、3 に着目しているとき、Undo 後は 2.5 ではなく 2 に着目するようになります。
そのかわり、後続可能な操作に先行する操作(図の 2)に着目したとき、後続する操作(図の 2.5)の Redo 処理を呼び出します。
あと、この更新フラグが下がっている状態というのは、正確には、「ディスク上に最後に保存された状態から更新されていない」ことを意味しているとみなしていいと思います。
それを踏まえると、保存するたびに保存操作を Undo リストに追加していくとして、Undo/Redo で保存操作をまたぐたび単純に更新フラグを下ろすわけにはいかない(ふるい保存操作は、それを行ったからといってディスク上の最新の状態にはならない)です。
ということで、なんからの条件をつけなければいけません。とりあえず保存操作内に、保存した時点のファイルのタイムスタンプを保持しておいて、Redo 時は「保存操作オブジェクトに保持したタイムスタンプと、ファイルの現在のタイムスタンプが一致するとき」フラグを下ろす。。。というふうにしました。
それにしてもめどいなあ。もうちょっとシンプルに組めそうな。。。というか、更新フラグのつじつまあわせ自体が蛇足なような感じもしないでもありません。
ぼちぼち落ち着いたです。
わりとどうでもいいのですが、選択範囲が存在するときになんか文字を入力すると、結果的に選択範囲の置き換えみたいな動作になりますが、内部的には
という複数の動作を行っていることになります。
これを Undo したとき、選択範囲を再現したほうがいいのかなーと。
図解すると、
abcdef
この状態で文字を入力して
abcDEF
となったとします。これを Undo したときの状態は、
abcdef
こうなのか(a)
abcdef
こうなのか(b)、です。んーどうでもいいですね。。。基本的には、Undo は行った動作の反対のことをすればいいので、
という動作をすればいいはずです。それをふまえると、より正確に状態を復元しているのは (a) のような気もしますが、(b) が間違っているというわけではなくて、(a) の状態からさらに範囲選択する前の状態に戻った、と考えることもできます。つまり、時系列上のどのポイントを、状態の保存/復元地点とするかの問題なのですが、使い勝手が勝るのであれば、保存地点と復元地点が完全に一致する必要はないのではないかなーと。
ということで、いろいろなエディタで試してみたのですが、選択範囲は再現しないエディタがほとんどです。再現してくれたのはぎょえ(仮)と Ziro くらいでした。なるほど。。。ちなみに、手持ちのエディタですが、33 種類になりました。
どっちにしたものかな。というか、実はどっちが使い勝手が良いのかよくわからないというか、どうでもいいというか。素直に組むと (a) の動作になるので、とりあえず選択範囲も再現するようにしてみます。
ついでなので、範囲選択中のキャレット位置も再現するように。マウスのダブルクリックとかで単語選択した場合、選択範囲の端点にキャレットが位置しないこともありえるのです。
といいつつ、本業がわりと忙しかったりします。
はがきと宛名のラベルをたくさん印刷した。最近のインクジェットプリンタって写真みたいに出るんですね。。。
よく考えると、ODBC でつなげればバックエンドになる DB はなんでもいいのかも。
萌ディタとは関係ありませんが、フリーのデータベースが欲しいなあと。Delphi といえば、よく? Interbase(現在は Firebird)との組み合わせが挙げられるのですが、どんなもんなんでしょ。
でも、レポートの印刷とかまで考えると Access をフロントエンドにして MSDE(新しいのは SQL Server Express になる?) あたりにしておいたほうが楽なのかも? もう SQL 自体、久しく書いてないので忘れてしまってるし。。。
入力、削除、範囲削除についての Undo クラスを書いたので、置換のそれを書きます。これがいちばんやっかいかもしれず。
デフラグしてたらマシンが落ちた('A`)
今日も Illustrator。午後から展示会場。
すみません、ここ数日開発してません。
新聞の取材の人がきました。8 日に載るそうな。
ステータスバーに明示して文字列を表示した後に一定秒経過すると、元に戻る、というか規定の文字列(プロパティ statusbar-ready-prompt で指定)を表示するわけです。
で、思いつきで規定の文字列の代わりに、現時点で可能な Redo と Undo の内容を表示するようにしてみましたが。。。便利なような、不便なような。
今日は 1 日、Illustrator、スプレーのり、カッターナイフ、ラミネート。
また開発と関係ないのですが。
Macintosh をとりあえず LAN に参加させてみたのですが。Windows 側から見えないし、Mac からも見えません。まああたりまえなのですが。。。
んー、スキャナ(USB 接続)は Windows にしか繋げないし、プリンタ(SCSI 接続)は Mac にしか繋げないという状態に。とりあえず Windows 側で ftp サーバを立ち上げたので、せこせこ転送すればやりとりはできるようになったけど、せつなすぎます。。。
そういえば、古いマシンがある(MediaGX 133MHz?)ので、Linux でも入れてサーバにしてみようかな。
机をゲット。ふふ。
開発とは全然関係ないことですが。
マシン、というかディスプレイを置く机が、いまのところしまむら(主婦の味方)からもらってきた商品陳列用の台なのです。机にするにはせまいし、高すぎて首が痛い。。。ついでに椅子は木製なのでおケツも痛いです。
で、リサイクルショップで机が ¥2,000 くらいで手に入るもよう。
これで椅子がアーロンとかなら満足なんだけどー。
とりあえずこうしてみました。

とりあえずこんなふうに。モーダルなのでパレットではないです。カーソルキーで白地と灰色の境界を移動して、確定すると白地の項目を操作した状態になるように適宜 Undo されたり Redo がまとめてなされたりするわけです。
と思ったらなんか入力した項目と削除した項目の区別がつきにくいですね。アイコンかなにかを頭に表示してやればいいんでしょうか。。。それか、削除した項目は打ち消し線入りにするとか。
あと、単純に連続する操作をまとめると、長文を一気に打ち込んだ場合などに使いにくいかも。Undo すると全てが消えてしまいます。単語境界、または文境界で区切ってみようかな?
WHiNNY を入れてみました。いいかんじ。なんて読むんでしょうか。ふいにー?
なんか新聞社がうちに取材にくるそうです。
文字削除操作の保持クラスも書いてみる。
ヒストリビューアを書いてみる。Redo は ctrl+Y、ビューア表示は shift+ctrl+Z にしてみました。
あ。ヒストリビューアに、たとえば「文字入力: 'abc'」とか表示させるためには、きのう考えた小細工があだになってしまうなあ。やっぱり保持するようにしようかな?
操作保持クラスやそれを管理するクラスを実装。前者は抽象クラスだし、後者はほとんど前者に処理を丸投げするので簡単です。Undo/Redo のしくみってオブジェクト指向に落としやすい(GoF のパターンにあるくらいだし。。。)かも。
実際の操作に応じて、操作保持クラスを継承したクラスを書くのがめんどいです。
とりあえず文字入力操作を保持するクラスを書いてみる。内部的には、入力を行ったバッファ中の位置と、入力した文字列を覚えておくわけです。
バッファ中の位置は、折り返し行単位ではなく段落と段落先頭からのオフセットという形で保持します(そうしないと、折り返し幅を変更したら Undo 不可になってしまうので)。
入力した文字列はそのとおりなのですが。ただ、文字列自体は覚えておかなくてもいいんですよね。入力の Undo は削除なので、入力後の位置と入力文字列の長さだけ分かっていればいいのです。そのあとの Redo には必要なので、初回 Undo 時に文字列を実際に取り込むように遅延させることで、けっこうなメモリの節約になりそうです。
あと、23 日の 3-4 ですが、文字入力ごとに Undo 情報を追加していくときに、連続した文字入力ならまとめてしまってもよさそうです。つまり 'a' を入力した直後に 'b' を入力したら、個別の Undo 情報にするのではなくて、1 回めの Undo 情報を書き換えて 'ab' を入力したというふうにしてしまうとか。
Undo/Redo について考えてみます。
とりあえず決めなくてはならないのは、
まず 1 から。テキストエディタということで、とりあえずバッファに対する操作を対象にします。これはまあ、あたりまえといえばあたりまえなのですが、後述。
2。わたしはよくやっちゃうんですけども、がーっとテキストを編集したあとで「あ、まちがったー」となったとき、Undo しまくって元に戻すわけです。そのとき、勢いあまって Undo をしなくていい操作まで Undo してしまって、現在テキストがどういう状態なのかさっぱりわからなくなると。
そういうのを解決しているプログラムとして、例えば Word の場合、Undo に対応するメニューのキャプションが、実際の Undo 操作にあわせて変わったりします(「元に戻す(U) 入力」とかになります)。でも、編集メニューとかを逐一開いてみないとわからないのが不便といえば不便です。
より良さそうなのは Photoshop で、「ヒストリーパレット」というものがあります。これは過去に行った操作をリストにして表示してくれる。そいで、適当な操作のひとつをクリックすればそこまで一気に Undo してくれたりします。これは便利なので、萌ディタでもパク、いえ参考にしようと思います。
そいで、3。実際にどういう構造にするか。まずバッファに対する操作を洗い出してみると、
と意外にシンプルです。しかし、Undo の処理をバッファ(のクラス)内で完結させてしまっていいかは微妙です。というのは、これはあくまでバッファへの内部的な操作であって、外部に(アプリケーション内のバッファ操作にしろ、スクリプトからの操作にしろ)公開しているバッファの操作というのはつまり、Caret オブジェクトのメソッド群であるからです。
ということは、Undo 情報のドメインはあくまでバッファだけど、情報の内容としては Caret オブジェクトに対する操作ということになるのですが。。。なんか変なような気もしますが、気にしないことにします。
実装の話をたくさん書いてもつまらないので、もうちょっと操作性のほうに目を向けます。
1。更新フラグというのは、ふつうは「最後に保存した状態から更新されているかどうか?」だと思います。そこで、適当なタイミングでファイルに保存したあと、1 文字入力して(ここで更新フラグが立つ)、それを Undo した場合、更新フラグはどうなるべきか? 更新フラグをおろさなければなりません。そして、そこからさらに Undo した場合は、更新フラグがまた立たなければなりません。さらにさらに、そこから Redo した場合は、更新フラグをおろさなければなりません。んーめんどいなあ。
2。ある操作を Undo した場合、Undo 用の情報は同時に Redo にも用いられることになるわけですが、その状態で新たに Undo の対象となる操作をした場合、Redo 用の情報をどうするか? 破棄しちゃってよさそうですが、ぐぐってみたら、ここで非線形的/選択的 Undo 機構というものについて記述がありました。へぇ〜。難しそうだなあ。
とりあえず、破棄するようにします。
3。素朴な疑問なのですが、無限回(というかメモリのある分だけ)の Undo/Redo ができるプログラムって、本当に回数制限をメモリ量だけによっているんでしょうか。それとも、実は回数制限はあるけど、何万単位とかものすごく多いので、実質無限回ということなのでしょうか。プログラムによると思いますが、前者だとプログラムを立ち上げっぱなしで同じバッファを編集してると、Undo の情報もけっこうな量になってしまうような。。。
ということで、プロパティで上限を指定できるようにしようと思います。ただ、n Kbytes 分保持、とかで指定させても多いのか少ないのかよくわからないので、操作 n 回まで保持みたいな感じにしてみます。
萌ディタスレの見直しの続き。「現時点で対応できそうなもの」を考えてみます。
リードオンリーモード。これはあれですね、スクリプト側で生成したテキストで、ビューア的な動作をさせたいってことですね。
ホイールボタンでの自動スクロール。これはいいだしっぺがわたしなのですが。ミソは、自動スクロールをエディタ側で完結させないで、スクリプト側からも起動させたいなあと。
Mac 的なグラバーハンド。どのキーバインドを使うか? がむずかしいところです。文字入力のためのキーは当然使えないので、修飾キーを使うことになるのですが、shift も ctrl も alt もすでに割り当て済みなんですよね。。。
カーソルが選択範囲内かでコンテキストメニューが変化。これは正しい「コンテキスト」メニューのあり方のような気がしますね。IE やエディタでは秀丸エディタの動作が参考になります(秀丸エディタの場合は、選択範囲が存在するかどうかで判定)。
OLE D&D。これは要望ではないのですが。ふと思ったのですが、キャレット位置の文字列を検索したい場合、それをコピーしてからダイアログを出して貼り付けるのがふつうだと思います。これ、ダイアログ自体にキャレット位置の文字列を「吸い取る」機能を持たせることで代替できないかな、と。
背景画像の繰り返し指定や位置指定。css の background- 系のプロパティ互換とかにすればいいかなあ。
プロパティ purge-empty-buffer を追加しました。
ファイル(F) - 開く(O) か、ファイル(F) - 最近のファイル(D) でファイルを開いたとき(正確には、Open か OpenFromRecent アクション)にこのプロパティを参照します。
で、6 月 30 日 で決めた、バッファリストに対するルールは、
あるとき、アクティブなバッファを選択したファイルで置き換えるようになります。
プロパティ multi-click-limit を追加しました。
これは 1 から 4 までが設定可能で、以下の意味を持ちます。
なんかうちにテレビ局の取材がきたです。
けっこう撮っていったのですが、放映されたのは 10 秒くらい。。。ズゲッときたワ。
とりあえずふつうに検索、置換ができるように。ただ、
などが残っています。とりあえず使えないこともないので nightly build で公開しておこうかな。
次は Undo/Redo。
萌ディタスレを最近見てなかった(すみません)のでじっくり見てみる。
Meたんフィギュア。よくできてますねー。
大文字・小文字を区別しないマッチについて。いまのところ全体的に、CharLowerBuffW() API でそういうマッチをさせているわけですが、UTR#30 Character Foldings のCase folding で参照している CaseFolding.txt を見てみると、CharLowerBuffW() で変換の対象外になっている文字もあるようです。
U+24B6
は U+24D0
として扱うよう CaseFolding.txt には書いてありますが、CharLowerBuffW() ではそれを変換してくれないとか。
またテーブル作るのめどいな。。。とりあえず CharLowerBuffW() のままにしておきます。本気でやりだすと、たぶん合成との兼ね合いも考えなければならないのでひたすらめどいです。
検索周りのインターフェース。まずオートメーションオブジェクトに増えたもの。
Target と Replacement はそのとおりの意味です。Option はカンマ区切りの文字列で、'r,c,d' のように指定します。意味は以下の通り。
それから、アクションに増えたもの。
最後に、拡張子クラスのプロパティに増えたもの。
やたら増えましたが、'last-' 系は萌ディタが勝手に設定するものなので特にいじるものはないと思います。それ以外を以下に説明します。
たとえば WZEditor でツールバーを表示させると、その中に検索文字列を入力するコンボボックスが含まれています。ctrl+F を押したときの動作も、ツールバー表示中はコンボボックスにフォーカス、非表示中は検索用ダイアログ表示に切り替わるなど、芸が細かいです。
で、萌ディタの場合は検索用のコンボボックスは常に表示されているので、ツールバーが表示中だったら〜といった条件をつかえません。そこで、ユーザに指定してもらうようにしていて、それが 'direct-find-dialog' と 'direct-replace-dialog' です。
これらが True の場合、Find アクション、Replace アクションは直接それぞれのダイアログを表示します。False の場合、エディタのウィンドウの 1 行入力パネルにフォーカスします。
1 行入力パネルのコンボボックスに文字列を入力して Enter を押下した場合、検索/置換を実行しますが、オプションは前回時のものを使用します。では、オプションを変更したくなったらどうするか。
WZEditor だと、Alt+S, F のキーストロークは常に検索ダイアログを表示するのでそれを入力すればよいのですが、それでは、2 種類のショートカットを使い分けなければならないので混乱しそうです。
ということで、コンボボックスにフォーカスがあるとき、特定のキー入力があればダイアログを表示する、というふうにして、「特定のキー入力」を 'find-dialog-shortcut' と 'replace-dialog-shortcut' で(たとえば 'ctrl+F' と)指定するようにしてみました。
そいで、それらには、基本的には Find/Replace アクションを実行するキーバインドと同じものを定義することを想定しています(別に異なってもかまいません)。
そうすると、たとえば検索の場合、まず 1 回 ctrl+F を押すとコンボボックスにフォーカスし、そこで再度 ctrl+F を押せばダイアログが開く。。。という感じで、同じショートカットで目的に合った異なる動作をさせることができるので、いいんじゃないかなー、と。
ということで、KMP 法で実装してみました。なんか手元の資料だと、「BM 法で検索しないエディタはもぐりといっても過言ではない」とかすごいことが書いてあるわけですが。。。
完全にリニアなテキストに対してなら、迷わず BM 法を実装するところなのですが、萌ディタのバッファの場合、複数の段落を 1 つのテキストとして仮想的に扱わなければならないので、ポインタの加減算が並行する BM 法だといろいろつじつま合わせが大変そうだなーと。KMP 法のほうが、常にポインタをインクリメントしていくだけでいいのでその辺は楽なのです。
ちなみに文字列検索のアルゴリズムは、ここがたくさん載ってて便利ですね。C でのサンプルコードや動作図解まで載ってて至れり尽せり。それを邦訳してたりするこことか超偉い。もっとがんばれ。超がんばれ。
実はうちのマシンはデュアルディスプレイというやつなんですが、Delphi のモジュールエクスプローラとかオブジェクトツリーとかオブジェクトインスペクタとか、セカンダリモニタに置くようにして、プライマリにはエディタと作成中のフォームだけ残すようにしてみました。
ひろびろ。
ふつうの検索もクラスに分けることにしてみました。
いまのところ、ふつうの検索は単純に頭から探していくようになっています。いちおう、照合のアルゴリズムとしてはより速い BM 法(Boyer-Moore algorithm)とか、KMP 法(Knuth-Morris-Pratt algorithm)というのがあります。傾向としては、単純な照合に比べて BM 法はたいていの場合速くなるけど、照合対象によっては遅くなる(というか、速くならない)ときもある。KMP 法は照合対象に関わりなく、わずかに速くなる感じです。
クラスに分けてすっきりしたので、せっかくなのでどっちかを実装してみようかな。ただ、いまの PC は速いから、単純な照合でもぜんぜん困らなかったりするんですよね。。。
11 日 のは、甘かった。もしかして並列型だと後方参照の範囲を正確に切り出すことはできないのかな?
どうしたものか。
正規表現の検索での、ループ検索を実装。
少しお昼寝してから庭の草むしりでもしようかしらと思ったら、思いっきり寝てしまった!
いまどきの正規表現エンジンは、\p{IsCJKUnifiedIdeographs} みたいな感じで Unicode のプロパティをマッチの条件にできるそうなのですが。同じものを実装するのは簡単だと思いますが、たぶんわたし自身は、使わない(プロパティ名とか覚えられないし)。Peggy みたいに補完のしくみがあればいいのかな。
なんか昨日のやり方にすると、いろいろと問題が。。。
ほかのやり方としては、コンパイル時、というか字句解析時に切り出したトークンを逆向きに繋いでいけば後ろから照合するパターンを生成できるので、それを逆照合用の NFA 要素としてあらためてコンパイルするとか。オートマトン側は共有して、照合位置を進めるところだけ符合が絡むようになります。
つまり、パターン '(abc)+def' をコンパイルすると同時に、文字列 'fed(cba)+' を生成するので、それもコンパイルします(コンパイラ自体は共通)。順方向用、逆方向用の NFA 要素をそれぞれ生成するということです。
ということで、そうしてみました。とりあえず後ろ向きの正規表現検索もできるように。
後方検索ですが、やっぱり照合も後方に向かうようにしようかと。そいで、どうするかですが前方向用のコンパイラとオートマトン、後ろ方向用のそれらという形で 2 種類用意すると、管理がめどい(同じ修正を両者にしなければならなくなる)ので、できれば共有したいところです。
いまのところ NFA 要素は後ろから実行していくようにはできていないので、構造を少し見直す必要があります。
'([0-9]+)' だと、内部的には
0: 0000000A 00000001 backref-open 1 ;後方参照 1 の開始位置に記録 2: 00000003 00000005 class [0-9] ;クラスにマッチ 4: 00000030 00000039 6: FFFFFFFF 7: 00000011 FFFFFFF9 main 2 ;分岐 9: 0000000B 00000001 backref-close 1 ;後方参照 1 の終了位置に記録 11: 00000000 termination ;照合成功
のような感じで、頭から実行していく前提になっています。これを、後ろから実行できるようにすれば共有できそうです。
0: 00000080 termination (backward) 1: 0000000A 00000001 0000000A backref-open 1 4: 00000091 0000000B 00000091 main 18 (backward) 7: 00000003 00000008 FFFFFFFF class [0-9] 10: 00000030 00000039 12: FFFFFFFF 00000008 00000003 15: 00000011 FFFFFFF2 00000011 main 1 (forward) 18: 0000000B 00000001 0000000B backref-close 1 21: 00000000 termination (forward)
個々の NFA 要素について、オペコードを前後に置く形にするのと、分岐系のオペコードは前から実行しているときか、後ろから実行しているときかで分ける感じ。こうすれば、プログラムカウンタの増分方向の符号を変えるだけで、前へもスイスイ、後ろへもツウツウいけると思います。
と思ったら、並列型だと後方参照の範囲があいまいになってしまうなあ。後方参照と閉包の組み合わせがむずかしい。
パターン '!(.*)!' で '!abc!' を照合するとして、並列型だと '.' の照合と、右側の '!' の照合が同時に発生します。そうすると、テキスト中の右のびっくりマークに両方マッチして、前者はそのまま ')' へ進んでしまう。そして、その位置を後方参照の末尾として記録して、対象が 'abc!' になってしまいます。期待しているのは、'abc' なのですが。
そのほか、パターン '((abc)+)abC' を文字列 'abcabcabC' でマッチさせたとき、後方参照 \1 が 'abcabc' になって、\2 が 'abc' になるかとか。
パターン '-(.)*-' を文字列 '-0123456789-' でマッチさせたとき、後方参照 \1 は右の '-' の直前の 1 文字である '9' になるかとか。
この 2 つは、いま試してみたら WZEditor4 と PeggyPad がうまく記憶できないみたいです。
どうしたものかなー。
とりあえず、ε遷移をすべて洗い出したあとの遷移先('.'、単純リテラル、クラス、補クラスのいずれかになる)のうち、'.' の優先度を最も低くして、かつ他の要素にマッチしたら '.' マッチは失敗するようにしてみました。
これでよいのかいまいち自信がありません。一応上記のパターンと、ユニットテストはクリアしていますが。。。
それにしても、予想はしてましたが、予想どおり正規表現のロジックにかかりきりになってしまっている今日この頃。ええと、わたしは何を作ってたんでしたっけ?
以下、よく分かっていない事柄を、さも分かっているように書いてみるテスト。
NFA エンジンということで、バックトラックの効率がついて回ります。バックトラックとは。。。たとえばパターン '正規表現は(めどい|めどすぎ)です' を使って、'正規表現はめどすぎです' にマッチさせるとします。
'正規表現は' までマッチして、選択式に出会います。選択式の中では 'めどい' が最初にあるためそこからマッチを開始します。ところが、'めど' までマッチしたところで、次の文字が 'す' であることが分かります。
つまり、選択式の最初の式ではマッチしない。残った 2 番めの選択式に切り替えてマッチを継続することになるわけです。そのためには、現在のテキスト中の着目点から、選択式に出会った時点の位置('正規表現は' までマッチした状態)まで戻らなければなりません。これをバックトラックといいます。
内部的には、バックトラックするために、マッチ中は「バックトラックでテキストの 'ここ' に戻って、パターンの 'ここ' からマッチをやり直す可能性があるよ」という印を覚えておいて、メモリに溜め込んでおくことになります。
ところが、パターンによっては、バックトラックするための情報を 1 文字につき 1 つ(あるいは、それ以上)保持しなければならなくなる可能性があります。そうすると、マッチのために必要な作業用メモリの量は膨大なものになり、正規表現ライブラリ内部に確保されたバッファを簡単に食い尽くしてしまいます。同時に、その分バックトラックの回数が増えるので、途中までマッチして戻って。。。というやり直しが多くなり、速度的にも大きなロスが発生します。
ここで述べられている例ですが、'=======================A' という行に対して '^(=*)*$' でマッチさせます(マッチ自体は失敗します)。これを試してみると、
というようなエディタがけっこうありました。
同じような例で、'=XX=======================' という行に対して 'X(.+)+X' でマッチさせる(マッチ自体は失敗します)のもあります。これは、「詳説 正規表現」に記載されています。
ということで、正規表現での検索は、パターンの書き方とテキストのサイズにより、簡単にリソースを消費し尽くすことができるので、バックトラック周りのアルゴリズムをより低コストなものにするとか、せめて落ちないように、メモリの境界チェックなんかはきっちり書かなければいけないなー、気をつけよっと。ということです。
うーん我ながら、よくここまで、いかにももっともらしいことを書けるものだ。
そいで、萌ディタの正規表現エンジンですが、ここまで書いてなんですが、実はバックトラック自体をしない(バックトラックが必要ないわゆる試行錯誤型ではなく、並列型のオートマトンで書いた)のでメモリが足らないエラーになることは、たぶん、ないです。
検索後のキャレット位置が地味に重要です。
今のところ、前方検索の場合はマッチした文字列を範囲選択し、左端にキャレットを置いています。
というのは、そのほうが検索直前のキャレット位置からの相対移動距離が小さくなるので、無駄なスクロールを抑えられるんじゃないかな。。。という企みがあるからです。それに、マッチ文字列の先頭が見えていたほうが、マッチ文字列がテキスト全体においてどういう文脈に位置するかがわかりやすいような気がします(微妙?)。
正規表現での後方検索をどうするか。まずやり方ですが、文字列を後ろから照合する専用のルーチンを書くか、前方検索のルーチンを流用して検索開始位置を後方にずらすことで対応するか、が考えられます。
どう違うかというと、照合の方向が異なることになります。前方検索の場合、基本的には、検索はキャレットのつぎの文字から開始します。
aaaaaaaaaaa|aaaaaaaaaaa
^検索開始位置
それを踏まえると、後方検索の場合の検索開始位置は
aaaaaaaaaaa|aaaaaaaaaaa
^検索開始位置
というのが自然だと思います。とりあえず検索開始位置は後方に進みました。次にいよいよ、照合をどちらの方向に進めるかになります。例えばパターンが a+ の場合、前方に照合すれば、
aaaaaaaaaaa|aaaaaaaaaaa
^検索開始位置
前方検索であっても照合は後方に進めば、
aaaaaaaaaaa|aaaaaaaaaaa
^検索開始位置
となります。後者は後方検索といっていいのかよくわかりませんが。。。秀丸エディタなどがこのタイプです。
さて、どちらにしようかな。
\< と \> による単語境界について。一般的には、ここでの「単語」とは [A-Za-z_] のことのようなのでそれに合わせるわけですが(加えて、マルチバイト文字はすべて単語構成文字として扱う実装もあるようです)、せっかくなので Unicode のプロパティを参照するようにしてもうすこし汎用にしてもよいと思います。
ただ \w とか \W とかをこのあと組み込むとなると、そちらにも影響が及ぶので変えるのはちょっと勇気がいりますね。。。
\< と \> に限って、「隣あうリテラルの Script プロパティが異なるとき、マッチする」という扱いにすればいいのかな。とりあえずそうしてみます。
たとえば (.|\n)* とかで前方検索すると、キャレット位置以降のすべてのテキストがマッチするはずです。ついでに、後方参照 \1 でそれを取り出せるようになります。
ところが、いろいろなエディタで試してみるとなぜかそうならないです。というか、エスケープシーケンスを理解して、かつ \n(または \r\n) が改行にマッチして、かつ閉包の対象が ( 〜 ) でグループ化されたブロック全体になるエディタという時点でけっこう絞られてしまうのかも。
で、いろいろ調べてみると、マッチしない、狙ったようにマッチしない、あるいはマッチするけど最大 n 行までに制限、とかの動作をするエディタが多いです(もしかしたら、エディタごとに正規表現の文法が微妙に違うので、間違った書き方をしたせいかもしれません)。
WZEditor 4.00F は狙ったマッチをするっぽいのですが、テキストのサイズが大きくなると落ちてしまいました。惜しい。
ということで、ちゃんとマッチするようにしてみました。まあ、萌ディタの正規表現エンジンはものすごく単純なので、変な組み合わせをしてもそれなりに動くというのが真相というところなのですが。
ぼちぼち開発再開。
とりあえずオートマトンを書いてみました。エディタに組み込んでみる。そういえば、正規表現での前方検索ってどうすればいいんでしょうか? ちょっと考える必要がありそう。
「インターネットでお店やろうよ! 」とかの本を読んでみる(本業で)。ほほー。
オンデマンド印刷のサイトを探してみる(本業で)。こことか、A4・4色・10,000部 で¥37,900- って異常に安いのですがどういうしくみなんだろ。。。
Macintosh でバックグラウンドプリントさせていても、ダイアログとか出すとプリントが一時停止してしまうっぽいあたりが、なんか斬新です。MacOS8 なのでそういうものなのでしょうか?
ということで、なんか合格してしまいました(おかしい)。
うちに帰ってきましたよー。
杉さま似の先生にいろいろ裏技を教えてもらいました。
今日は午後が空きなので、お土産とかいろいろ買っちゃった。
これであした合格しなかったらどうしよう〜、と。
とても疲れた。あさって卒業検定です。
今日は経路設計というものをやりました。地図を見てルートを自分で決定して、目標地点に行くというものです。明日は下り坂の走り方と高速道路だそうです。
正規表現のシンタックスのうち、'$' と '\n' は紛らわしいのですが、簡単にいうと、前者は段落の最後にマッチする(文字にマッチするのではない。。。)のに対し、後者は改行コードにマッチするというのが違いです。なので、'^.*\n' は最終段落にはマッチしない。
ちなみに '\n' というのはいわゆる U+000A、LF に直接対応するわけではなくて、論理的な改行、つまり段落の最後であり、かつ次の段落が存在するという状態に対応します。なので、ファイル上の改行コードが CR だろうが LF だろうが CRLF だろうが正規表現上は '\n' で共通です。
'\<'、'\>' は Unicode ベースの単語境界にするかもしれません。このあたりの、文字の属性を判断する部分(とか、複数行の取得とか)、つまり正規表現エンジンの動作とは直接には関わらない部分はすべてコールバックで呼び出し側に丸投げするので、エディタ本体で使用している単語境界と共通化できるとかの仕組みが、正規表現まわりを自前で書いたことの強みといえば強み。
今日は実地が 5 時間(車が 3 時間、原付が 2 時間)もあって疲れました。。。
実は持ってきたノート PC には Delphi が入っていないのです(ソース自体は一応持ってきましたが)。そいで、とりあえず、書いた正規表現エンジンは、以下の文法を認識します:
とまあ、別に書き出す必要もないほど普通の仕様なわけですが。これ以外では、複数行のマッチがちゃんとできるとか、よくばりマッチか非よくばりマッチかを切り替えることも(一応)できるとか、大文字小文字を区別しないマッチとか。
あとは \u でコードポイントの直接指定と {n[, m]} の繰り返し指定くらいを実装しようかな。なんか Perl には、先読みマッチとかいろいろすごい機能があるみたいですが、そこまでは実装しないです(というか、あまり詳しいことはよくわからない)。
近況ですが。
今のところ順調で、修了検定とかいうのに(どういうわけか)合格して第 2 段階の講習を受けています。卒業検定もうまくいけば、8/3 あたりにはうちに帰ることができるかもしれません。
んーそれにしても、わたしには自動車はやっぱり向いてなさそうだということがよくわかった。
はやく開発に戻りたいなあ。
風邪が治った。。。かも。
オートマトンをちょっと書いてみました。なんか「後方参照」なのか「前方参照」なのか文献によって混乱してたりするのでしょうか。backward reference だから、感じとしては後方参照でいいような気もしますが、「前」が過去の意味なら前方参照も合ってる?? 日本語むずかしい。とりあえず後方参照で統一してみます。
そいで、明日から免許を取るために、泊り込みの合宿にいくことになったので、開発とここの更新が止まります。止まらないかもしれません(いちおうノート PC は持っていくので、余裕があれば。でも暇があったら温泉を満喫してしまう可能性もなきにしもあらず)。
ふらふらしますよ(;´Д`)ハァハァ
ここのところ超暑かったのに突然寒くなったりしたせいか、みごとに風邪を引いてしまいました。。。
うーあとはオートマトンを書くだけなんですけど。。。風邪が治るまでに、何を書いたかすっかり忘れてしまうかも。
仕事で Macintosh (MacOS 8.1) を使ってみました。なんかバックグラウンドプリントが効かなかったり、突然 Photoshop が落ちるとかで、Mac は本当に×××で××××なあと思った(謎)。
とりあえず正規表現のパターンを内部的な NFA の要素にコンパイルする部分を書いてみました。{n [,m]} と POSIX の文字クラスはまだ。後者は実装しないかも。。。
JCL は strong advocate of the "open source" なんですね。なんか DFA と NFA を適宜変換したりしているみたいでソースを追うのは大変そう。
検索時、\n で改行を指定できると謳っているエディタの中には、2 行以上のマッチング(aaaa\nbbbb\ncccc のような)に対応していないものも結構あるのかも? 勘ですが、汎用的な正規表現ライブラリ(あるいは、外部 DLL)を使用しているエディタは不思議な動作をするような気がします。やっぱり、対象となるバッファ構造をよく知る正規表現エンジンじゃないと改行の扱いは難しいのかな。
でも自前で書いてもベタな NFA エンジン+ピープホールな最適化くらいが限界のような気もします。どうしよーかなー。
ためしにちょっと書いてみます。
alt を押しながら〜のやつは、再帰的に検索ダイアログを出せたりするので、やめた。とりあえず文字列の交換だけ実装。
全置換させたとき、1 回置換するごとに再表示すると遅くてたまりません。なので、256 の倍数回置換するごとに再表示 & Esc 押下(キャンセル)チェックをすることにしてみました。
タブとか改行を含んだ検索ができるように、javascript っぽいエスケープシーケンスを受け付けるようにしてみました。
改行込みの検索を実装しましたが。。。ものすごくベタになってしまった(改行で区切られた文字列ごとにマッチングする)。ギャップバッファとかなら、検索前にギャップを詰めてしまえば改行があろうとなかろうと普通に検索すればいいだけなのですが。変えちゃおうかな。ただ、単純にバッファ全体を単一のギャップバッファで管理するのはちょっとためらわれます。適当なサイズのブロックを双方向リストで持って、ブロック内をギャップで管理とかならわりといいのかも。
いまのところ、大文字小文字の区別(っていう表記が厳密に正しいのかわかりませんが)というのは CharLowerBuffW() した上で比較しているわけですが、やっぱりこの辺は UTR#30 Character Foldings を実装しなければいけないのかな。。。ちょっと見てみましたが。。。ちゃぶ台をひっくり返しそうになりました。
とりあえず正規表現のエンジンとしては、ここのを Unicode 版でコンパイルして使うか、JCL のもの(こちらはもともと Unicode Tuned)に前方参照を追加して使うかにしようと思うのですが。。。前者はともかく、後者のライセンスってどうなってるんだろ?
置換を組んでみました。

置換を開始して、検索文字列がヒットするごとにそれを範囲選択して、こんなダイアログ(モーダル)が出ます。WZEditor と同じような感じです。そいで、見たとおりの動作をするのですが、一応説明すると
という感じです。特に「置換して終了」が(わたしには)便利だと思います。
あと、検索/置換のダイアログ(7/5 に載せたもの)を出しているときに
。。。があればいいんじゃないかなーと妄想。
なんかうちの人が免許をとれとうるさいので、自動車学校に通ってみることにします。。。
でも、わたしにブーブーの運転ができるとはとても思えない。
うーん bm 法、あるいは kmp 法で検索するかどうか。問題は、これらで組んでも、たぶんあまり速くならないんじゃないいのかなーというところです(特に対象が短い場合)。検索対象となる文字列の長さに応じて、単純な検索と bm 法の検索を振り分けるようなハイブリッドな感じにすればいいのでしょうか?
あ、exe のバージョン上げるの忘れてた。
いろいろ実装。また細かいところに凝りすぎといわれそうですが、ほんとうに細かいところに凝りすぎました。
まず、1 行入力コンボボックスからスクリプトを直接実行させるあたりですが。
([0-9]+) +['"](.*) の形式だったときは、\2 を文字列とみなして \1 回キャレット位置に出力します。([0-9]+) +(.*) の形式だったときは、\2 をスクリプト本体とみなして \1 回評価します。エディタ側で行うのは入力された文字列をスクリプトに渡すまでで、スクリプト側(onEvaluate のハンドラ)で形式による振り分けを含めた実際の評価を行います。
次に検索と置換まわりですが。基本的な部分だけ実装。

検索セットというのは、検索する文字列とオプションをひとまとめにして名前を付けておいて、あとから名前で呼び出す。。。という感じのものです(置換セットもあります)。
URL 占いをしてみたら、1 日 6 ヒットサイトらしいです。
あなたのサイトのURLは、一日6ヒットくらいしかしない、ショボショボサイトの可能性が高いです。 単なる自己満足だけのために、サイトを作っていませんか? アクセス向上のためには、訪問者を楽しませる不断の努力が不可欠です。 またたまには自分のエロい写真なんかを載せてみてもいいかもしれません。
痛いところをつかれてしまいました。。。ちなみに最後のスラッシュをはずすと結果が変わって、まぐれ当たりサイトになる。
あなたのサイトは、コンテンツもぶっちゃけヘボいし、文章力も小学生なみ、載せてる絵は幼稚園なみかもしれません。 いえ、たぶんそうでしょう。 でも、その天衣無縫さが人の琴線に触れるのでしょうか、ある日突然、まぐれ当たりする可能性があります。 ラッキーでしたな。
うーん、そうなのか。。。それにしてもひどい言われようです。
そういえばショートカットの解決をしていないので実装。意外にショートカットファイルをドロップしたとき、解決をしてくれないエディタってあるものなんだなあ。
ファイルを開くとき、すでにバッファリストに存在するかをチェックするわけですが、そのときもショートカットの解決済みのファイル名でチェックするよう修正。
ファイル名といえば、Windows のファイルシステムって大文字・小文字を区別しないのは割と一般的ですが、全角のアルファベットについても適用されるんですよね。「ほげほげABC.txt」と「ほげほげabc.txt」は同じディレクトリに存在できない。
その辺って LCMapString() なのか CharLowerBuffW() なのか CompareString() なのか、よくわかりませんねー。とりあえず CharLowerBuffW() した上でチェックするようにしていますが。。。
一応 0.4。0.3 からは上書きでかまいませんが、スクリプト無効モードの場合も拡張子クラス textfile を生成するようにしたので、念のため ini ファイルは一度削除したほうがよいかもしれません。
ぺたぺた。
ぎょえ(仮)がちょっと楽しみだったりします。Lite Editor や 1Editor など、.NET ベースのエディタもけっこう出てきてるんだなあ。うちには .NET Framework を入れていないので、動かせませんが。。。
バッファリストのメソッドに、Replace(Index : integer, FileName)というのを追加。Index 番めのバッファを閉じて、その位置に新しいバッファを追加するというものです。未変更な新規バッファとの置き換えはこれを使うようにしてみました。
それにしても大いにいじったのでバグが心配に。。。
そこはかとなくキャレット行ガイドラインの色を IME の状態によって変えられるようにしてみました。
直線とか点線とかも選択できてもいいかもしれませんが、めどいからいいや。
スクリプトへ制御を渡す、イベントについて考えてみます。キー入力周りは置いといて、それ以外のものとして、
| onInitProp | 拡張子クラスが萌ディタに登録されたときに呼ばれます。ここで、個々のクラスに固有のプロパティを設定したりします。 |
|---|---|
| onCreate | ファイルから中身を読み込む直前に呼ばれます。 |
| onLoad | ファイルから中身を読み込んだ直後に呼ばれます。 |
| onDestroy | バッファが削除される直前に呼ばれます。 |
| onTimer | タイマーにより一定時間ごとに呼ばれます。 |
| onEvaluate | ウィンドウの 1 行バッファにスクリプトが入力されたときに呼ばれます。 |
| onKeyPrintable | 表示可能な文字が入力されるごとに呼ばれます。IME からの確定の場合は、確定した文字列全体につき 1 度だけ呼ばれます。 |
| onActivate | あるビューについて、バッファを切り替えたとき呼ばれます。 |
| onDeactivate | あるビューについて、バッファを切り替える直前に呼ばれます。 |
| onLeftClick | マウスの左ボタンをクリックしたとき呼ばれます。 |
| onMiddleClick | マウスの中ボタンをクリックしたとき呼ばれます。 |
| onRightClick | マウスの右ボタンをクリックしたとき呼ばれます。 |
とだいたいこうなっているのですが、いまいち具体的にどう活用したものかというのをはっきりさせないとここからいじりにくい。
とりあえず、いまいち何に使うか思いつかないのもありますが、ないよりはあったほうがいいのでここから減らすのではなく、何があと必要かを考えてみます。
onLoad と対になる onSave も必要かな。onLoad はロード直後ですが、セーブの場合は、むしろセーブ直前にイベントが走ったほうがいいかも? 景気よく、onSaving(セーブ直前)と onSave(セーブ直後)にしてみます。
onEvaluate と同じ感じで、onFindRequest(検索文字列の入力)、onReplaceRequest(置換文字列の入力)みたいなのも必要ですね。
マウス関連はホイールとかダブルクリック版も必要なのかなあ。もっと詳細な、ボタン押下/移動/ボタン離す に対応するイベントも必要なのかなあ。でもそこまで低レベルな部分をスクリプトにまわすと遅そう。この辺は要望があったらにします。
イベント名でボタンを区別しないで、引数で区別するようにしたほうがいいかも。
アプリケーションの開始時や終了時にもイベントが必要かな? でも終了時はともかく、開始時というのはスクリプトエンジン初期化後になるので厳密には開始時では全然ないなあ。
onActivate/onDeactivate はちょっとあいまいなので(ビューの状態かバッファの状態か、両方か)とりあえず削除します。
ということで、こうしようと思います。
| onInitApp | アプリケーションの起動処理が完了した直後に呼ばれます。拡張子クラスは 'plaintext' 固定。 |
|---|---|
| onQuitApp | アプリケーションの終了処理を開始する直前に呼ばれます。拡張子クラスは 'plaintext' 固定。 |
| onInitProp | 拡張子クラスが萌ディタに登録されたときに呼ばれます。ここで、個々のクラスに固有のプロパティを設定したりします。 |
| onCreate | ファイルから中身を読み込む直前に呼ばれます。 |
| onLoad | ファイルから中身を読み込んだ直後に呼ばれます。 |
| onSaving | ファイルへセーブを行う直前に呼ばれます。 |
| onSave | ファイルへセーブを行った直後に呼ばれます。 |
| onDestroy | バッファが削除される直前に呼ばれます。 |
| onTimer | タイマーにより一定時間ごとに呼ばれます。 |
| onEvaluate | ウィンドウの 1 行バッファにスクリプトが入力されたときに呼ばれます。 |
| onFindRequest | ウィンドウの 1 行バッファに検索文字列が入力されたときに呼ばれます。 |
| onReplaceRequest | ウィンドウの 1 行バッファに置換文字列が入力されたときに呼ばれます。 |
| onKeyPrintable | 表示可能な文字が入力されるごとに呼ばれます。IME からの確定の場合は、確定した文字列全体につき 1 度だけ呼ばれます。 |
| onClick | マウスのボタンをクリックしたとき呼ばれます。ビュー内に限るか? どのコントロールだとしても呼ぶか? exe 内蔵の処理を迂回するために戻り値を返すようにするか? |
onTimer / onClick / onEvaluate / onFindRequest / onReplaceRequest 以外を実装。
アプリの起動処理、終了処理なんかは、もしかしたら拡張子クラスごとにそれぞれすることがあるかもしれないので、'plaintext' 固定はよくないかもしれないです。存在する拡張子クラスそれぞれについて呼ぶか、いわゆる add-hook みたいなものでチェインするしくみをつくる?
どうも本格的にスクリプトを書く段階にならないと、なにが必要かはいまいちはっきりしないのでは。。。という気になってきたので、適当なところで切り上げます。
よくよく考えると、Delphi のネイティブな型 OleVariant に IDispatch を入れておけば、何もわざわざ Invoke するコードを書かなくても
function someScriptSideProcedure() {
App.Notice('スクリプトで定義した関数');
}
var d: OleVariant; begin //IActiveScript::GetScriptDispatch で //取得した IDispatch を OleVariant へ d := MainForm.ScriptEngine.ScriptInterface; //よーしパパ直接呼び出しちゃうぞー d.someScriptSideProcedure(); end;
とか、スクリプト側で定義した名前で直接かけちゃうじゃありませんか(Visual Basic の Variant とほとんど同じ)。引数もそのまま渡せるし。ちょー無駄なことをしていたわたしの立場は。。。