Console integration

wasavi が何らかのメッセージを出力する場合、いくつかの出口がある:

  • バックログ・コンソール: 出力が複数行に及ぶ時に wasavi の画面全体を覆う形で表示される。モードは backlog_prompt に移行する。これは command や insert/overwrite などと同じレベルに位置する立派な vi のモードの一つだ。lastMessage は最終的にバックログの全内容を代入される
  • ステータスラインの message: 出力が 1 行のみの場合は単にステータスラインにそれを表示し、モードは backlog_prompt へは移行しない。lastMessage は出力される 1 行そのものを代入される
  • ステータスラインの notice: これは message とほとんど同じなのだが、lastMessage を更新する場合としない場合がある:
    • 通常の notice: ステータスラインに何らかの文字列を表示する。lastMessage は更新しない。これを使用するのは例えば、[cci]n[/cci] コマンドなどで現在使用している検索文字列をステータスラインに表示する時など
    • silent な notice: 内部的に何らかのメッセージは生成するが、表示はせず、エラーベルを鳴らす。lastMessage は更新する。これを使用するのは例えば、行頭・行末・テキスト戦闘・テキスト末尾を超える [cci]hjkl[/cci] 各コマンドなど
  • lastMessage これは内部的な変数で、出力先にかかわらず出力した結果を保持する。機能テストの際にこれを参照する

ここで重要なのは、バックログと message は本質的に同じバッファを共有しなければいけないということと、message と notice の使い分けを正しく行わなければならないということなのだが。前者に関して徹底されていなかった(対症療法的な修正がたくさんコミットログに残ってて結構恥ずかしい)のでそのへんを見直して修正した。

後者は明確なルールがあるわけではない。ただ実際に使ってみた際にログが溜まりまくってうぜえ!的なものは notice、エラーメッセージ的なものは message に回すように見直した。

be a developer

現在の常用のブラウザはここのところ Firefox beta だったのだが、例の「拡張は署名済みじゃないと入れさせませんぞー!」という仕様変更に対応するため Firefox Developer Edition に鞍替えすることにした。

ということで入れてみた。プロファイルも別扱いになるので、いくつかの設定はやり直す必要があるものの(フォントとか)、それ以外の設定は Sync 経由で復帰することができる。

若干気づいたこと。

  • 全体的なテーマが黒基調になっているのだけど、黒い背景に微妙に色味の違う黒いテキストボックス…みたいに見難くて、色の選択が下手だ。ちゃんとしたデザイナーを雇ったほうがいいと思います
  • cfx 経由で動かすと、browser.xul のパースエラーで起動しなくなる。起動時の xml パースエラー自体は過去にも存在したバグで、ローカライズド版で発生するらしい。それがまた発生したのか直してなかったのかわからないが、とりあえず [cci]LANG=C firefox[/cci] などとするとパースエラーは出ず起動する。まあ従来の Aurora を置き換えたのが Developer Edition だからね。この程度のバグは普通にたくさん包含しているのでしょう

the latest wasavi

  • vi は :s コマンドに c オプションを付加して対話的に実行した場合、置換する(y)、スキップする(n)、終了する(q) の 3 つの選択肢をユーザーに提示する。vim は、より多くの選択肢を提示する。そのうち、これ以降全て置換する(a) および これを置換して終了(l) を実装した
  • Range Symbols は [cci][({<[/cci] の各文字について、その全角版も認識するようになった
  • 文字の分類に Unicode の Scripts.txt のデータを参照するようになった。これにより、たとえば U+0100 以降のダイアクリティカルマーク付きアルファベットが混ざった単語に対して w コマンドなどが正しく動作するようになった
  • vim の :sort コマンドを実装した。このコマンドはいくつかのオプションを備えているが、そのうち [cci]i[/cci][cci]r[/cci] およびパターンの指定を実装した。また、各行の特定の桁数までスキップした上でソート対象にする場合、vim では正規表現のメタキャラクタで桁数を指定するらしいのだけど、そのかわり [cci]c[/cci] オプションを実装した。[cci]:sort c10[/cci] などとすると各行の 0 オリジンでの 10 桁目以降の文字列を取り出してソート対象にする
  • % コマンドにカウンタ n を与えた場合、vim と同様にバッファの行の n% の位置にジャンプするようにした
  • vi は歴史的に、カーソルが非空白文字の上にある場合に cw コマンドを ce コマンドとして扱う。より正しくその動作をエミュレートするように修正した
  • magic オプションというものがあり、正規表現中のメタキャラクタの表し方を制御する。このオプション、とても強力すぎる。magic の値によってほとんどすべての ex コマンドが影響を受けるのである。そんなわけで magic の効果を除去した
  • vim の *、# コマンドを実装した。ただし、vi での正規表現 [cci]\<[/cci][cci]\>[/cci] に相当するメタキャラクタを javascript のそれは持っていないのでそのへんはちょっと不完全
  • vim の ga コマンドを実装した
  • set コマンドに与えるオプションの中には、値の設定の完了を非同期的に待たないといけないものがある。たとえば fullscreen オプションはコンテントスクリプト間のメッセージングを伴うので非同期だ。この完了を正しく待つようにした。これは [cci]:set fs tw=80 cul[/cci] などと並べて書いた場合も同様で、正しく逐一実行される

最近のだいたい大まかなハイライトはこんな感じ。

a bug

ひとつ難しいバグを見つけた。

ex コマンド [cci]wq[/cci] は、write-and-quit の略であり、まさにそのように動作する。これは実質的に [cci]write|quit[/cci] のシノニムであり、全く同じ働きをする。同じ働きというのは、エラー処理も含まれる。つまりこのとき、もし [cci]write[/cci] が何らかのエラーを返したなら、後続の [cci]quit[/cci] コマンドは実行されない。また、保存に失敗したわけなので、もし保存前にダーティフラグが立っていたならば、それは変化しない。ダーティーフラグというのは、最後に保存したテキストの状態から編集が行われているかということを示すものであり、今回のバグの主役はそれだ。

一方で、wasavi での [cci]write[/cci] コマンドの実装は若干特殊である。というのは、write コマンドで直接対象に書き込むわけではなくバッファリングを行うだけで処理を終えるからだ。そういうわけで write の実行結果は必ずしも書き込みが成功したかを示すようにはなってない。が、前述の通り wq は本来 write の実行結果に依存した処理を行わなければならないので、現状だと write エラーの場合に正しく動作していない。現状ではバッファリングが成功した時点でダーティフラグも降ろしているが、本来なら保存処理が完全に成功したことを確認した後じゃないといけない。

ここで、じゃあバッファリングしなければいいじゃんというふうにはならない。特に dropbox などの外部ストレージサービスに書き込む場合にその処理が完了するまではけっこうな間がある。つまりバッファリングが必要な場合としてはいけない場合があるということだ。具体的には ex コマンド列の最後の要素が write コマンドの場合のみバッファリングを許可し、そうではない場合は即書き込み動作に入ってその結果を待つようにしないといけない。

そういうわけで、状況によって細かい場合分けをする必要がある。

write wq xit
write 動作 コマンド列の最後 バッファリングする 即書き込み、完了を待つ 編集済みであれば、即書き込み、完了を待つ
最後ではない 即書き込み、完了を待つ
quit 動作 編集済み write 完了後、write エラーでなければ quit write 完了後、write エラーでなければ quit
編集済みでない xit コマンドのコンテキスト内で即 quit

ところであんまり関係ないんだけど、ex コマンド xit って使ったことない。位置づけとしては、タイムスタンプを不必要に更新しない wq ということなのだが。その機能ってむしろ write が標準で持っていてもいいと思うのです。write で更新されてる時だけ書き込み、write! なら常に書き込み動作みたいな。

yakumono

fFtT コマンド群に対して拡張を行った。似た仕組みを Range Symbols にも取り入れることができる。

たとえば [cci]yi([/cci] はカーソル位置が属する括弧の内容をヤンクする。このときの [cci]([/cci] が、U+0028 そのもの以外に U+FF08 も同一視するようになれば、全角の括弧に対しても Range Symbols の操作を行えるようになる。これは便利かもしれない。同じ拡張を [cci]({[[/cci] の 3 種の括弧について施すことができる。

ただ、これだけではまだ中途半端だ。というのも、特にアジアの東の果ての野郎どもがこれら以外にやたらめったら括弧類を使うからだ。Range Symbols で例えば [cci]【悲報】[/cci] みたいな文字列の括弧の内外を取り扱うにはどのシンボルを割り当てたものだろうか。

また、括弧類以外に、引用符についても拡張の余地がある。あるのだけど考え始めるとなかなかこれもめんどくさい。Range Symbols が認識する引用符は [cci]”‘`[/cci] の 3 種だが Unicode で定義されている引用符はたとえば “ (U+201C) と ” (U+201D) のように開き引用符と閉じ引用符が別である。さらに、困ったことに、Wikipedia によれば、同じコードポイントでも言語によって開き引用符として使われたり閉じ引用符として使われたりするようなのだ。え…なにそれ。

そういうわけで、そういうのにそれなりに対応するにはけっこうというか既存の引用符に関する処理はかなり書き直さないといけないと思う。でもそうなるとこの機能の使用頻度とプログラミングの作業量のバランスがとても良くない気がする…。

expand wasavi beyond a textarea

今まで wasavi を起動させる要素は、textarea か特定の input 要素か、あるいは contentEditable 属性がオンになってる要素であった。つまるところ、編集できるのはあくまで実質的な木構造の終端の要素であった。

試験的にこれに document.body を含めてみたい。そうするとどうなるのかといえば、つまり何かテキストを編集しているわけではない状態で [cci]Ctrl+Enter[/cci] を押せば wasavi を起動させられるようになる。この時の編集対象要素が document.body ということだ。しかしこれは実質的な終端の要素ではないので、その内容を編集・上書きできるというわけではない(もし上書き可能にすると、とても危険だ。wasavi 上で編集するものはあくまでプレーンテキストなので、その上書きによって失われるかもしれない子要素があまりに多すぎる)。

一方で、save 動作に対する load 動作も特殊だ。document.body の内容全体をプレーンテキストに変換したとしてもあまり意味があるとは思えない。したがって初期内容はページのサマリの体裁が良いと思う。つまり

[title 要素か最初の h1 要素の内容]
[URL]
[meta[name="description"] 要素があれば、その content 属性の内容]
[wasavi 起動直前に範囲選択されていれば、その内容]

という感じだ。イメージとしてはこんな感じにページ全体に覆いかぶさるように起動する。
wasavi-on-a-page

このサマリ自体は、いかにも twitter とか、あるいはソーシャルブックマークに登録されるそれっぽい感じだ。であれば、まさにその用途に使うようにすればよいのではないか? つまりユーザが任意のページを開いて、これを SBM に登録したいと考える。その過程でちょっとしたコメントなんかも追記したい(しかも、vi のインターフェースで)。この一連の作業を wasavi が面倒を見ればよいのではないかということだ。

しかし、SBM サービスというのは非常にたくさんあるわけで全てに対応するのは無理だ。また、登録処理自体はテキストエディタの仕事の範疇から外れているし、やるとしたらエディタ内蔵のプラグインやスクリプティング機構が受け持つべきだけど、wasavi はまだどちらも持ち合わせていない。

とりあえず document.body 上でも起動できるようにして、それをどうするかは懸案事項としておこう。

fftt, again

fFtT コマンドについて、従来からある拡張をしていた。カタカナ・ひらがな・漢字のローマ字読みのデータを wasavi 内部で持って、Latin-1 の文字でそれらを指定できるというものだ。つまり「カ」にカーソルをジャンプするには [cci]fk[/cci] すればよい。

今回この対象をカタカナ・ひらがな・漢字以外にも広げたい。特に、Latin-1 ではないラテン文字に対する拡張が中心になる。

元となるデータは、UnicodeData.txt の Decomposition_Mapping である。たとえば U+00C0 (À) のそれは
U+0041 U+0300
という形式に分解される。U+0041 はつまり単に A であり、それに U+0300 が付随している書記素クラスタということだ。このデータの中から最初の Latin-1 内の文字を抜き出して fFtT の代替表現とする。

前述の通りこの拡張はラテン文字が中心なのだが、日本語に関してもいくつか拡張される。

  • U+330D (㌍) のようなアレについて、最初のカナに対応するローマ字の読みでジャンプできるようになる
  • U+FF01 から始まる全角形の文字群について、対応する半角形でジャンプできるようになる
  • 半角カナについても同様

一方、気になる点もいくつか出てきた。

  • Decomposition_Type = <fraction> である文字、例えば U+00BC (¼) は分解すると 1/4 となって最初の Latin-1 は 1 である。しかしそうすると 1/4 だろうが 1/2 だろうが 1/3 だろうがその Latin-1 代替表現は全部 1 だ。これは直感的なんだろうか?
  • Ⅰ Ⅱ Ⅲ Ⅳ といった文字についても、単純に Decomposition_Mapping に従うとその Latin-1 代替表現は全部 I(Latin-1 のアイ)。これは対応する 1 〜 4 のほうが良いのではないか?(そうすると U+2169 (Ⅹ) 以降に何を対応させるかという別の問題が出てくるが)
  • U+2469 (⑩) から始まる 10 〜 20 の囲み数値についても何を代替表現にするかという問題が。全部 1 でいいのか?

* * *

とりあえず何の変更も加えないことにした。文句が上がってきたらその時考える。

ちなみに、どのコードポイントがどの Latin-1 の文字に代替されるかは github 上の wikiに書いてある。

Surrounding the world #9

ひと通り実装が終わった。

ただし以下のものは surround.vim から移植していない。また、surround.vim を読んでそういう機能があると把握してないものがあればそれも当然移植されていない。

  • [cci]/[/cci] を包囲文字列に指定すると C コメントが対象になる機能
  • 包囲文字列に数値を前置すると、カウンタに乗算される機能
  • [cci]l[/cci] などを包囲文字列に指定すると、TeX の [cci]\begin[/cci] と [cci]\end[/cci] が上手いこと付加される機能
  • [cci]f[/cci] などを包囲文字列に指定すると、[cci]関数名([/cci] と [cci])[/cci] が上手いこと付加される機能
  • [cci]surround_[/cci] で始まる各種変数で動作をカスタマイズする機能
  • input モード中における [cci]^S[/cci]、[cci]^GS[/cci]、[cci]^Gs[/cci] マッピング

とりあえず作業はテスト漬けへ移る。

Tagging a text

Text Object の [cci]it[/cci] および [cci]at[/cci] を Range Symbols へ持ってくる。

これは何かといえば、カーソル位置の前後のタグを認識する処理だ。つまり単純に考えれば、[cci]<[/cci] で始まり [cci]>[/cci] で終わっているのがタグである。そして 2 文字目が [cci]/[/cci] であれば終了タグであり、そうでなければ開始タグだ。これを利用しつつ、開始タグと内容と終了タグの全体を選択するのが [cci]at[/cci] であり、内容だけを選択するのが [cci]it[/cci] だ。

もちろん実際にはそう単純ではなく、

  • カウントが指定された場合は、カウントの分だけいわゆる parentNode を辿った先の要素のタグを対象にする
  • タグの内部は改行も含まれることを意識しないといけない
  • vim は短縮タグ([cci]
    [/cci] みたいなの)は、タグとみなさない
  • 対応する終了タグが見つからない開始タグは、タグとみなさない。これはなかなかヒューリスティックに働く。vim の該当処理は XML 宣言も SGML の注釈宣言も知らないが、このルールにより結果的になんとなくうまく動く。
  • 走査の結果出た選択領域が、走査前と同一であった場合は親のノードを対象に再度やり直すといったつじつま合わせが必要。この処理は visual モードで連続して [cci]it[/cci] した場合に実行される

といった感じのことを念頭に置く必要がある。

念の為書いておくが、毎度書いてる気がするが、こういったことは本来 SGML なり XML なり HTML のパーサが保持しているノードの木構造を参照すべきことなのであって、テキストベースでカーソル位置の前後をチラ見してどうこうする vim の手法は根本的に間違っているのだが、まあ仕方がないのです。

* * *

作った。

Surround the world #8

Range Symbol [cci]t[/cci] を vim から移植する。

前にも書いたがこの Range Symbol は本来ちゃんとした SGML パーサーが文書を解析した上での結果を利用すべきものであって、Range Symbol 内にその簡易版を持つのは無駄なのだが。しかしタグ周りを実装しないと surround.vim の便利さ半減なのでまあ仕方がないのであります。

とりあえず vim の search.c を読む。current_tagblock() あたりを読めばいいんだよねきっと。