(WIP) Vim mode for scrapbox
まだ構想段階
12月までに完成すればVim Advent Calendarのネタにできそう
実装
KeyboardEventを捕捉していろいろやる
この辺を参考にする
モードどうやって表示したらいいだろう
この辺は好みかなあ
とりあえず <div>
でmode表示欄を作っておいて、配置や見た目は各自でCSS書いて調節する感じで
単語単位の移動が難しいかも
Prioritize Outline-edit key bindings
をoffにしてもらえばなんとかなるか
もしくは split
などを使って単語区切りを計算する
W
とかは、行からblankを抽出し、その座標を計算する必要が出てくる
検索系も難しそう
browser標準の検索で代替可能
まあそこまで厳密に再現しなくてもって気はする
モードと少しのオペレータ、モーションがあればそこそこ満足しそう
APIの設計
その1
Vimにわかが考えたオレオレAPI
勝手に変えちゃって下さい
重箱の隅をつつくようであれだけど、Plug内のよく見る記法が foo-bar
スタイルなので変えました
それは知らなかったです。てかよくよく思い出せば確かにどのpluginもそのスタイルだったような
mock.js// <Plug>(enter-normal)をNormal modeに移動するコマンドにしてみた
ScrapVim.bind({type: ['input','visual'], before: ['<ESC>', '<c-[>'], after: '<Plug>(enter-normal)'});
// ↑複数のkey mapに同じcommandを割り当てるときはlistを使う
ScrapVim.bind({type: 'input', before: 'jj', after: '<ESC>'});
標準のkey bindを予め定義しておく
Plugはユーザーに好き勝手マッピングさせるがデフォルトマップは提供しないための物なので、本体(?)側は用意しなくてもいい気がする
統一したほうがsystem作りやすいかなと思ったのですが、止めたほうがいいかなあ
あー、Vimのマッピングシステム知ってるもんで、つい合わせようと…すみません
本当に合わせようとすると noremap
と nmap
のちがいとかも実装しないといけなくなるのですが、ぶっちゃけ nmap
の用途って <Plug>(...)
の割当くらいしかないし、別に実装しなくてもいいよね?と思ってやめました
めっちゃわかる
キーストロークから関数へのマップだけを提供するスタイル
下の例に合わせるなら、 ScrapVim.bind({type: 'normal', key: 'j', function: scrapVimDown})
的なのしか受け付けないイメージ2020/11/8
あれこれだと noremap
と map
の挙動が混ざって直感と合わない?
↓のときは jj
に <Plug>(scroll-top)
が割り当てられるようにしたいけど
nmap
の挙動
javascriptScrapVim.bind({type: 'normal', before: '<ESC>', after: '<Plug>(scroll-top)'});
ScrapVim.bind({type: 'normal', before: 'jj', after: '<ESC>'});
↓のときは <c-j>
を j
二回押下、つまりスクロール2つ分の操作に割り当てたい
nnoremap
の挙動
javascriptScrapVim.bind({type: 'normal', before: '<c-j>', after: 'jj'});
だがどちらも
after
で指定してしまっている。違う挙動をするのに同じ引数layoutを使うのは
精神衛生上よくなさそう?
とすると標準動作は全部内部的に定義して、userには公開しないほうがいいかなあ
Vim自体もそんな感じになってますね
Cで実装されていて内部でディスパッチしてます
本家もそういう実装なのか
キーバインドから関数へマップされる層の上に入力を再帰的(or noremapの場合は直接下層に向けて)に解決するマッピング層が重なってる感じ
こうかなあ
そうそう、そんな感じです
(ddは厳密には違うけど、一旦は考えないほうがよさげ)
あ、dはoperatorか (vimわからない)
dw
de
d$
と同類?
yes
d等を押すとオペレータ待機モードとかいうのに入って、そこでもう一度同じオペレータを押すと行に対して発動します
なるほど。繰り返しで行に対して発動と。
cc
yy
も同類か
gg
は……流石に別ですかね
これはノーマルモードのコマンドですね
なるほど
vim-jpのbotに聞いてみましたが、関連のなさそうなcommandばかり出てきました。
確かに無関係のcommandだ
じゃあひとまずこれでいくか
mock.jsScrapVim.bind({type: 'normal', before: 'gg', after: '<Plug>(scroll-top)'});
ScrapVim.bind({type: 'normal', before: 'G', after: '<Plug>(scroll-bottom)'});
ScrapVim.bind({type: 'normal', before: '/', after: '<c-f>'});
ScrapVim.bind({type: 'normal', before: 'dd', after: '<Plug>(delete-line)'});
ScrapVim.bind({type: 'normal', before: 'v', after: '<Plug>(enter-visual)'});
ScrapVim.bind({type: 'normal', before: 'V', after: '<Plug>(enter-visual-line)'});
// 実装できるかわからないので保留
//ScrapVim.bind({type: 'normal', before: '<C-v>', after: '<Plug>(enter-visual-block)'});
ScrapVim.bind({type: 'normal', before: 'w', after: '<Plug>(forward-word-head)'});
ScrapVim.bind({type: 'normal', before: 'e', after: '<Plug>(forward-word-end)'});
設計案その2
mock2.js//toに関数を投げるとバインドを作成
ScrapVim.bind({type: 'normal', from: ['<Esc>', '<C-[>'], to: ScrapVim.enterNormal});
そうか文字列以外にも函数を引数に渡せるんだった
ScrapVim.map
を別に用意する方が分かりやすいかもしれません
悩んだ挙句に怠惰さ発現した結果こうなった
ScrapVim.bind
に統一でいいと思います。函数overload.
mock2.js//toに文字列を投げるとマッピングを作成
ScrapVim.bind({type: 'normal', from: 'jj', to: '<Esc>'});
ScrapVim.bind({type: 'normal', from: '<Plug>(insert-hoge)', to: 'ihoge<Esc>'});
noremapデフォルトで、remapしたい場合はフラグを立てる方式でよさそう
mock2.jsScrapVim.bind({type: 'normal', from: '<Space>hoge', to: '<Plug>(insert-hoge)', remap: true});
こんな感じでどうでしょうか
after
が before
に割り当てられるという形式でしょうか?
ですね
この並びなら from,to
にでもしておけばよかったですね
じゃあそうしよう
remap
のdefaultを false
にしても良いんじゃないでしょうか?
どう考えたって普段よく使うのは noremap
の方でしょうし
ですよね
これのプラグインが生まれるとはあまり考えられないなあ
これの上にplugin作るくらいなら直接UserScriptいじってほしい
mappingのparser
parser-mock2.jsfunction parse(mapping) {
if (/^<Plug>/.test(mapping)) return mapping;
return mapping.match(/<[^>]+?>|./g);
}
あー、 <Plug>
の先は解析しなくてもいいですね
Vimに合わせるのが一番シンプルです
<Plug>
にマッチすると人間が入力できない文字に置換するだけ
確かに解析する必要なかった
正規表現、即席でできなさそう
少し考えるか
目標
'<Space>hoge'
→ ['<Space>','h','o','g','e']
これどうでしょうか
console.log('<Space>hoge'.match(/<[^>]+?>|./g))
'
おおすごい!
なんかのプログラミング言語の実装講座でこれのクソ長いやつで字句解析してた覚えがある
JSは普段使いじゃないからよく知らなかったけどMDN見てたらできそうだったので
String.prototype.matchAll()
にはよくお世話になったけどmatchはほとんど使ったことなかったです
matchAll逆に知らなかった
Mode
Normal mode
cursorをeditorにfocusさせるが、全ての文字入力を握りつぶす
cursorの形を変えてわかりやすくしておく
Input mode
通常状態
Visual mode
v
などで開始
または選択範囲を表すDOMが表示されたときにも切り替える
以下実装は後回し
Command mode
editorにfocusがあたっていない状態
Normal modeから :
でも切り替えられる
Terminal mode
なんかやれたら面白そう
他の方法 (ボツ)
かなり処理が重いので現実的でない
Scrapboxクライアントを作るとか?
アプリだと誰かやってた人がいたはず
本当の内部を触ることになるので色々大変そう
これならブラウザじゃなくてもできるというメリットはある
Reference
結構要望が多い
vim-jpでもVimのキーバインドが使えないのが無理という話を聞いたことがある
操作法の違いで使われないのはもったいないよなって気持ちがある
本当にもったいない
正直SlackでチャットするよりScrapboxで
チャットするほうがメリットが多い
わかる、意外とやりやすくて驚いた
体感的にSlackより安定してるし(これは使用人数の差とかもありそう)
5人くらいなら問題ないです
10人以上になるとどうだろう?
この前 #vim-jp
ででていたchannel名のprefixがどうのこうのなんていうことに時間を費やさずにする
ある意味channelを横断して会話するということが容易にできる
mentionをそのまま流用できないのは欠点かな
Slackはどうなんだろうか
多分Vimのキーバインド使えないよね
使えないですね、独自の使いにくいよくわからんキーバインドを強要されます
あー、あの動いたり動かなかったりする妙なキーバインドのことですね
cursorのfocusがあたっている場所によってキーバインドの中身が変わる
しかもどこにfocusがあたっているのかよくわからない
それな
Qutebrowserで生きようとした時に一番の障害になるくらいUIが妙
水をさしてしまうかもしれないけど....
矩形選択や単語単位の移動などは、やっぱり厳しくてArrow Keyが必要です
前無理だったようなと思って再び試してみましたが、マウスで一度フォーカスしたらそこそこいけますね
Scrapbox内部APIにおけるクライアントとして振る舞うということのつもりで書きました
なのでvimをクライアントにすることもできるのかなと(現実的に実装できるかはともかく
ああ内部を触るというのはそういうことですね。本当の内部を触ることになるので色々大変そう
Scrpaboxのindex.jsをhookして処理を割り込ませるとか?
確かに現実的ではない
Scrapboxの公式クライアントが行う通信を解析して模倣するということも理論的にはできそう
うーん、vimのbackgroundでselenium/puppeteerを動かせばなんとか行けるかも知れません
performanceがかなり心配ですが