2017/03/02 5:04 am
Increment and Decrement
Uncategorized, , ,

issue #145

これは、vimの ^A および ^X が欲しいよという要望だ。これらは、カーソルの下にある数値らしきものを指定したカウンタの分だけ増減させる。^A は増加させ、^X は減少させる。

オプション nrformats の値によって動作をカスタマイズできる。これはカンマ区切りで文字列を格納する前提になっていて、以下のコンポーネントが意味を持つ:

  • alpha – \b[a-zA-Z]\b をアルファベットのリストアイテムと認識する。大文字か小文字かで分類し、それぞれ [a-z] と [A-Z] をローテーションする
  • bin – 0b[01]+ を2進数表記と認識する
  • hex – 0[xX][0-9a-fA-F]+ を16進数表記と認識する。大文字か小文字かどちらかを取るかは、マッチした文字列の最も右端に位置するアルファベットに依存する。アルファベットがない場合は、2文字目が x か X かに依存する。
  • octal – 0[0-9]* にマッチし、かつ、0[0-7]* にもマッチする部分を8進数表記と認識する。なぜこれだけ判定がややこしいのかというと、00018 のような文字列を 0001 と 8 に分割されてしまうのを避けるため。またこのコンポーネントを有効にすると、どうでもいいことだが C 言語と同様 0 は8進数の0と認識される

これらのコンポーネントに対するマッチの最後に、必ず -?[0-9]+ を10進数表記と認識する処理が入る。で、カーソル行に対してマッチした文字列群のうち、カーソルの桁位置を内包するものが処理対象になり、指定したカウンタの分だけ値を増減する。基本的には以上だ。

いくつか気を付けるべきポイントがある。

  • 0 で始まる、10進数以外の数値文字列の場合、値を増減してもできるだけ元の桁数を維持するよう適宜左に 0 をパディングする
  • 10進数と認識される数値文字列(これには、0で始まるが [89] を含む数値文字列も含む)の場合は、逆にいかなる場合も先行する 0 をすべて削除する
  • vim の仕様だと、-0001 みたいなのは octal を含んだ nrformats の場合に若干不自然な動作になる。つまり負の符号が先行しているが、正の8進数文字列と認識される。これはいいのか?
  • Selenium のテストで、あるテスト中に複数のアサーションを行う時、”#1-1″ とか “#1-2” とかラベルをつけている(そもそも複数のアサーションを詰め込むべきでないとか、もっと自己記述的なラベルにすべきとかはこの際置いといて)このラベルの右端の数値を増減させたい場合、ハイフンが邪魔になり動作が反転してしまう。これはなんとかならないのか。つまり負の符号ではなくハイフン、あるいは減算の演算子であるかを判別できるといいんだけど
  • vim では、nrformats の型は文字列だが、実質的にはカンマ区切りの配列構造として扱うことになる。これは身近な例で言えば DOM における className と classList の関係に似ている。全体をまるごと書き換えるのでなければ、classList を通して操作するほうが圧倒的に便利だ。同じことが vim にも言えて、set nrformats+=foo とか、set nrformats-=bar のような演算子が用意されている(vim の、というか vi の set 構文はいわゆる普通のプログラミング言語とは若干趣が異なるので演算子と言っていいのかはわからないが)。これを wasavi でもサポートすべきか?

2017/02/26 2:02 pm
implicit addressing
Uncategorized, , ,

たとえば

foo
bar
foo
bar
foo
bar
foo
bar
foo
bar

という 10 行のテキストがあり、カーソルは最終行にあったとき、テキスト全体をコピーして末尾にペーストしたいとする。

vi コマンドであれば、ggyGGp で 6 手である。ex コマンドにおいても :1,t.<enter> と 6 手。個人的には行単位のコピーは ex コマンドを使うほうが多い。

ところで :%t.<enter> のほうがさらに 1 手少ないが、別に vi/ex ゴルフがこの記事の主題ではない。上記の ex コマンド中、t はコピー命令であり、その直前までが行の範囲を示している。開始行は 1 で、カンマが続き、そして終了行は省略している。省略するとカーソル行が割り当てられる。その処理を wasavi に入れ忘れていたので、入れた。

2017/02/24 9:48 am
migrating Dropbox API v1 to v2
Uncategorized, , , ,

Dropbox側の修正を終えたのでテストしてみたところGoogle Driveに対する読み書きでコケる。なんで?

OAuth2による認証が成功した直後、自身のプロファイル情報を得るAPIを呼び、認証に成功したIDと一致することを確認している。Google Driveの場合は、

https://www.googleapis.com/oauth2/v1/userinfo

に対してPOSTして返ってくるjsonを参照している。このエンドポイントが404を返すようになった。11日前にwasaviをリリースした際にはもちろんすべてのテストが通ったことを確認してあるので、その間になんか変わったようだ。

Googleが提供するAPIの包括的なリファレンスというのは、どこにあるのかよくわからない。しかしAPI Explorerというものがあり、実質的にここでAPIの仕様を確認できる。ということで Google OAuth2 API v2 を見てみると確かにuserinfoはない。その代わりoauth2.userinfo.v2.me.getというものがあるのでこれがどうも最新のようだ。エンドポイントは

https://www.googleapis.com/userinfo/v2/me

だ。これにどのフィールドを参照するかのクエリ文字列(今回の場合は?fields=id)を付加して、GETで取得すると結果が返ってくる。

API Explorerで参照できるAPI名らしきものとエンドポイントのURLの構造が微妙に似ているような似てないような感じになっているのは何なんだろう。わからん。それと、この最新のAPIを使ったとしても、POSTだとやはり404が返される。ちゃんと書いてあるとおりにGETじゃないといけない。

2017/02/20 3:52 am
migrating Dropbox API v1 to v2
Uncategorized, , , ,

去年からさんざんアナウンスされつつも、夏休みの宿題のノリでまあ切羽詰まったらやればいいだろう…と思っていた、Dropbox の API が v1 から v2 に移行するというトピックにそろそろ対応したい。

移行のガイドとしてはこんな文書がある。そのとおりに進めればいいはずだ。たぶんきっと。

幸いなことに、wasaviがDropboxを始めとしたオンラインストレージとやり取りする際に使用するAPIはそれほど多くはない。

  • OAuth2で認証する
  • 任意のディレクトリの直下のファイル・ディレクトリを列挙する
  • 任意のパスからテキストを読み込む
  • 任意のパスへテキストを書き込む

てな感じなので順番に片付けていこう。まず認証に関しては単にエンドポイントが変わるだけなのでこれには問題はない。そして残りは全部面倒くさい。

ディレクトリの列挙。v1では、任意のディレクトリに対してmetadataAPIをlistオプション付きで呼ぶと、そのディレクトリ自身のメタデータと併せて、一緒に直接の子供のメタデータのリストも得ることができた。ガイドによればこのAPIのv2における代替はfiles/get_metadataなのだが。しかしlistオプション付きの場合はそれではまったく代替になり得なく、複数のAPIを組み合わせて使わなければならない。困ったことにこの辺はガイドにはさっぱり解説されていない。というわけでv1のlistオプション付きmetadata APIの代替になる処理を自前で書かざるを得ない。

まず任意のディレクトリ自身のメタデータを読み込むために、files/get_metadataを呼ぶ。それが完了したら(ここは別に並列でもいいが)files/list_folderを呼び子供のメタデータを得る。また、このAPIは一度に返される結果セットのサイズが決まっているようで、その呼び出しが結果セット全体の最後かどうかは不定だ。そのかわり呼び出しのたび最後の結果かを示すフラグ has_more を返すので、つまりこの後呼び出し側が責任を持ってfiles/list_folder/continueを複数回呼び出す必要がある。それから列挙対象のパスなどを与える方法が若干面倒でContent-Typeをapplication/x-www-form-urlencodedではなくapplication/jsonにした上でJSONをPOSTしなければいけない。

なんだこれは。バージョンが進んだのにクライアント側でやらないといけない仕事が格段に増えている。ここまでの遷移を見てみると、個々のAPIができることがより制限され、かつその代替がよく解説されないという状況にしか見えない。どうにもいまいちだと言わざるを得ない。

ファイルの読み込み。files/download を呼ぶのだが、読み込み対象のパスなどを与える方法が若干面倒。POST のボディではなく、Dropbox-API-Argなるヘッダに、JSONを文字列化したものを与える必要がある。それができない場合は、クエリ文字列として?arg=<stringified JSON parameter>てな感じで与える必要がある。XHRの場合何でも好き勝手なヘッダを追加することはできないのでクエリ文字列を使用することになる。で、読み込み結果はレスポンスボディとして帰ってくるのだが、メタデータはDropbox-API-ResultなるヘッダにこれもやはりJSON形式で帰ってくる。

ファイルの書き込みにおける変更点は読み込みと同じ。v1では書き込みの際はPUTメソッドを使用した。これはこれで筋が通っている仕様だと思うけど、v2ではPOSTに変更されている。

というわけでAPIの使い勝手としてはv1のほうが勝ってたんじゃないかなあ…と思わなくもない。

それから、v1では多くのパラメータに汎用的にlocaleを付加することができたがv2ではHTTPのヘッダ(Dropbox-Locale)に含めるように変更になったとか、全てのアクセスはPOSTメソッドでないと「いけない」(GETも受け付けるとかではない)などの細かな変更もある。メソッドはともかく、ロケールは難しい。前述の通りXHRでは任意のリクエストヘッダを送出できるわけではないからだ。X-Dropbox-Locale とかなら送れるんだけどなー。まあロケールは主にエラーメッセージとかに影響するはずなのでそれほど困るわけではないのだけど。

2017/02/13 6:13 pm
escape
Uncategorized, , ,

sコマンド中にバックスラッシュによるエスケープシーケンスを含んだコンポーネントを含めると正しく引数が解析されないのを修正。

つまり、:%s/$/ \\/g みたいなのが正しく動作しなかった。

そろそろ各ブラウザの公式エクステンションストアに置いてるwasaviをアップデートする頃合いだ。

 * * *

アップデートした。今年はこまめにストア版もアップデートするようにがんばるぞい。

2017/02/11 9:34 am
Extend the “writeas” effect
Uncategorized, , ,

wasaviはtextareaの他にcontenteditable属性の付いた要素も編集できるわけだが、そういう要素にどのように中身を埋めていくかはサイトによって異なり、統一されたものはない。そういうわけで、writeasというオプションを用意している。これには以下の値を割り当てられる:

  • html — バッファの内容を markdown とみなし、html を構築する(デフォルト)
  • div — 各行につき div 要素を生成する
  • p — 各行につき p 要素を生成する
  • textAndBreak — 各行につきテキストノードを生成し、改行として br 要素を追加する
  • plaintext — バッファ全体を単体のテキストノードにする

issue #156でこの割当をサイトごとにできないかという要望があった。なるほど確かにそのほうがいいだろう。同様にサイトごとに動作を割り当てられる設定としては、wasaviの起動をさせないためのブラックリストがすでにある。こちらはさらにサイトの下にCSSセレクタを指定できるようにしてあり、個別の要素のレベルで制御が可能だ。writeasもそのようにすべきだろう。

具体的にはwriteasに対して上記の値の他に、JSONとしてパース可能な文字列を与えることを許す。このJSONは例えば:

{
  "http://example.com/*": "div",
  "http://example.net/*": [
    {
      "selector": "#element-id",
      "writeas": "textAndBreak"
    }
  ]
}

と言った感じの連想配列になり、URLに対するwriteasの値、あるいはURLおよびセレクタに合致する箇所のwriteasの値が取られる。

ところで、ブラックリストはwasaviのオプションではなくwasaviの起動前の設定である。この区別はとても重要だ。本質的にwasaviの設定はwasaviのsetコマンドから操作できるのが望ましい。そういうわけに行かないものだけが起動前設定として独立されるべきである。起動前の設定というのは他にexrcや、起動ショートカットや、起動時の効果音などだがこれはまさにwasaviが起動する前に処理される事柄なのでwasaviの設定からは「止むを得ず」独立させてあるということだ。それ以外の設定、つまりwasaviが起動した後に参照される設定はすべてwasaviのオプションにしてある。とこのように厳密に区別しているのである。さて今回の変更を施す場合、URLごとのリストという点ではブラックリストと似たものになるのだが、しかしいつ参照されるのか? という点で考えるとwriteasは依然としてwasaviのオプションだ。そういうわけで独立した設定にはしない。

ということは、上記のようなJSONをwriteasに代入する際は

set writeas='{ \
  "http://example.com/*": "div", \
  "http://example.net/*": [ \
    { \
      "selector": "#element-id", \
      "writeas": "textAndBreak" \
    } \
  ] \
}'

という感じのexコマンドになるのだが、これはこれで改行のエスケープがすごく冗長だ。これはちょっと何とかしたい…。

2017/02/04 5:00 pm
Showing a newline mark
Uncategorized, , ,

viにはlistオプションというものがあり、それをオンにすると

  • 行の末尾に $ が表示されるようになる
  • タブ文字が ^I で代替表示されるようになる

となる。この内、行末尾の表示について実装したい。残念ながら、タブ文字の代替表示は難しい。

ここで行末尾を示すために文字 $ が用いられるというのは、vi が端末上のテキストエディタだからで、wasavi の場合はもうちょっと表現力があるので別の記号にしたい。

行末尾、つまり改行を示すための記号というとまあだいたいこんな感じだと思う。特に日本産のエディタでこれじゃない奴はないのではなかろうか。あと、MS Word。海外産だと行をパラグラフとみなして、Pilcrowが多いかもしれない。まあどちらでもいいけれど、とりあえず前者を使うことにしよう。

ところで、改行マークを表示することに意味があるとは特に思えない。それが役に立つのは、行末に余計な空白文字を残している、いわばダーティというか、非正規な状態の行を目視で確認したい場合くらいだろう。そういう行を正規化したいのなら目視確認なんてせずに正規表現による全置換すればいいだけの話で、改行マークを表示することに意味はない。また、正規化済みの行は空白ではないグリフの直後に改行があることが自明なわけで改行マークを表示する必要はなく、非正規な状態の行だけその旨を表示するようにすればいいのだが、非正規な行の場合でも本来強調表示すべきは行末の余計な空白文字であって、改行マークではない(vimの場合で言えばlistcharsに対してeolではなくtailだけ設定した状態)。従っていずれの場合でも改行マークを表示しなければならない積極的な理由はないように思える。

と、そう考えればそうなのだが、実は去年はあんまりwasaviの開発を進められなかったのでいまいち内部の構造を忘れかけているのであった。そのリハビリのためにあえて実装してみる。

ということでこうなった。

2017/01/26 3:11 am
The Space
Uncategorized, , , ,

qeema 内でデグレードしてた箇所があったのを修正。Chrome で、IME がオンの状態でスペースキーを押すと(設定によっては)いわゆる全角スペース(U+3000、IDEOGRAPHIC SPACE)が直接入力されるわけだが、それが wasavi に正しく伝達されなくなっていた。

この全角スペースの直接入力に対して Chrome が発生させるキーボードイベントは実におかしい。

  • まず keydown イベントが発生する。この時、keyCode は 229 で入力されつつある文字が全角スペースだという判断には使えない
  • 各種 Composition Events は一切発生しない
  • input イベントが発生する。このイベントは単に通知のためのもので、何が入力されたかという情報は与えてくれない

という感じに実におかしい。Firefox であれば正しく Composition Events が発生する。IME まわりは Firefox の方が Chrome よりずっと真っ当だ。

対応策として、input 内でなんとかするしかなく、実際そうしている。keydown でその時点での対象の要素の値を保存しておき、input で差分を取ってイベントを生成させる。デグレードしたのはその処理が正しく呼ばれていなかった。

2017/01/21 7:52 pm
Testing with selenium javascript binding #5
Uncategorized, , , ,

ところで Opera ではどうなのだろうか。Blink Opera でも operadriver というものを経由してテストすることになっている。これはつまりchromedriver のフォークだ。じゃあ、だいたい Chrome のように動くと考えていいのかな。

と思ってやってみたらまるでさっぱり動かない。Opera 自体は起動するものの、

exception: Error: ECONNREFUSED connect ECONNREFUSED 127.0.0.1:46266

なる例外が発生してテストが始まらない。

Selenium のライブラリと *driver との接続は http による REST だ。つまり、operadriver 側でリモートコントロールのための接続の listen をしてくれないということなのか?

とそういうわけで現在のところの各ブラウザの selenium 対応は Chrome、Firefox、Opera の順で甲乙丙のようだ。

2017/01/18 6:12 pm
Testing with selenium javascript binding #4
Uncategorized, , , ,

そういうわけで Linux 上では WebExtensions 版の wasavi の、Selenium による、Firefox 上の機能テストを一通り通せるようになったのだが、結局これを Windows 上ではできていない。過去の記事で何度か書いているが Windows では既存のプロファイルでもって Firefox を起動させることができず、かつ新規プロファイルに WebExtensions ベースの拡張を登録させつつ Firefox を起動させることもできない。両方できないと身動きが取れない。

実を言うと Linux 上でもテストが問題なく滞りなくできてるわけではない。例えば:

  • なにかが leak してますよ〜的なログがいっぱい出る。
    (node) warning: possible EventEmitter memory leak detected. 11 listeners added.

    云々。このエラー自体は書いてあるとおりのそのままの意味なんだろうけど、Chrome でテストする際は出ないので Selenium 内の Firefox とのやり取りをするコードにおける問題だと思う

  • wasavi でテストする際は当然ながら様々なキーストロークを発生させる必要がある。で、selenium.Key.chord() というものがあり、chord(Key.CONTROL, 'c') などと呼ぶと ctrl+C を押されたとブラウザに振る舞わせるような擬似的なキーストロークを生成する。Key[なんとか] は、型自体は普通の string で、Unicode の Private Use Area 内の文字である。chord 自体は単に引数を全部 join(”) したものに Key.NULL をくっつけたものを返す。したがって擬似的なキーストロークもまた単純な文字列である。さてそういうルールのもとに構成された chord を送出したあと、geckodriver かあるいは Firefox 本体が例えば前述の例だとそれを ctrl+C としてデコードしてくれないと困るわけなのだが。困ったことに、誰もデコードしてくれない。そのまんま PUA が入力され、テストは失敗する。ひどい
  • テスト中 Web Content なるプロセスが生成されて、これが60〜80%の CPU パワーを消費する。同時に Firefox 自体のプロセスが残りの CPU パワーを消費する。そんなわけでテスト中は待ってる以外にほとんど何もできないし、そもそも端末以外のアプリケーションを全て落とした状態じゃないとテスト自体がうまく始まらない。Chrome を起動しながらとか成功した試しがないのである。そして CPU パワーはめちゃくちゃ食いまくる割にテストの進行は Chrome でのそれより 2 割増しくらいで遅い

前述のプロファイル関連の不具合と合わせて、どーにもこーにも…… Firefox が絡む Selenium のテスト環境はいつも質が悪い、質が低いと言わざるを得ない。どうなってんのかなあこれ……。この辺が快適になってくれないと、Firefox 向けの拡張を作るという事自体にくじけてしまいそうなのだけど。

アーカイブ