generated at
(WIP) Vim mode for scrapbox
scrapboxVimのように操作できるUserScriptを作る
まだ構想段階
12月までに完成すればVim Advent Calendarのネタにできそう

実装
Mousetrapを使うと意外と簡単に実装できそうな気がしてきた
cdnjsからのimport方法:/takker/Mousetrap#5fa7f5751280f000000b710e
Mousetrapの使用方法をこっちに書いたほうがよさそうだなtakker
KeyboardEventを捕捉していろいろやる
この辺を参考にする
文字入力や移動の方法は/takker/external-completionで使ったcodeを流用できる
モードどうやって表示したらいいだろう
右下の通信ステータスの隣に出すとか?
この辺は好みかなあ
とりあえず <div> でmode表示欄を作っておいて、配置や見た目は各自でCSS書いて調節する感じで
単語単位の移動が難しいかも
Prioritize Outline-edit key bindings をoffにしてもらえばなんとかなるか
もしくは split などを使って単語区切りを計算する
W とかは、行からblankを抽出し、その座標を計算する必要が出てくる
検索系も難しそう
browser標準の検索で代替可能
まあそこまで厳密に再現しなくてもって気はするkuuote
モードと少しのオペレータ、モーションがあればそこそこ満足しそう
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のマッピングシステム知ってるもんで、つい合わせようと…すみませんkuuote
本当に合わせようとすると noremap nmap のちがいとかも実装しないといけなくなるのですが、ぶっちゃけ nmap の用途って <Plug>(...) の割当くらいしかないし、別に実装しなくてもいいよね?と思ってやめました
めっちゃわかるkuuote
自由度は下がるけど、evil (Emacs)みたいにやると楽そうな気はする
キーストロークから関数へのマップだけを提供するスタイル
下の例に合わせるなら、 ScrapVim.bind({type: 'normal', key: 'j', function: scrapVimDown}) 的なのしか受け付けないイメージ2020/11/8
あれこれだと noremap map の挙動が混ざって直感と合わない?
↓のときは jj <Plug>(scroll-top) が割り当てられるようにしたいけど
nmap の挙動
javascript
ScrapVim.bind({type: 'normal', before: '<ESC>', after: '<Plug>(scroll-top)'}); ScrapVim.bind({type: 'normal', before: 'jj', after: '<ESC>'});
↓のときは <c-j> j 二回押下、つまりスクロール2つ分の操作に割り当てたい
nnoremap の挙動
javascript
ScrapVim.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.js
ScrapVim.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.js
ScrapVim.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.js
function 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)) '
おおすごい!
String.prototype.match()でそういうことできるんですね
なんかのプログラミング言語の実装講座でこれのクソ長いやつで字句解析してた覚えがある
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
なんかやれたら面白そう
他の方法 (ボツ)
puppeteerで強引に書き込む
かなり処理が重いので現実的でない
Scrapboxクライアントを作るとか?
アプリだと誰かやってた人がいたはず
本当の内部を触ることになるので色々大変そう
これならブラウザじゃなくてもできるというメリットはある

Reference
結構要望が多い
vim-jpでもVimのキーバインドが使えないのが無理という話を聞いたことがあるkuuote
操作法の違いで使われないのはもったいないよなって気持ちがある
本当にもったいないtakker
正直SlackでチャットするよりScrapboxでチャットするほうがメリットが多い
わかる、意外とやりやすくて驚いたkuuote
体感的にSlackより安定してるし(これは使用人数の差とかもありそう)
5人くらいなら問題ないですtakker
10人以上になるとどうだろう?
この前 #vim-jp ででていたchannel名のprefixがどうのこうのなんていうことに時間を費やさずにする
ある意味channelを横断して会話するということが容易にできる
mentionをそのまま流用できないのは欠点かな
Slackはどうなんだろうか
多分Vimのキーバインド使えないよね
使えないですね、独自の使いにくいよくわからんキーバインドを強要されます
あー、あの動いたり動かなかったりする妙なキーバインドのことですね
cursorのfocusがあたっている場所によってキーバインドの中身が変わる
しかもどこにfocusがあたっているのかよくわからない
それなkuuote
Qutebrowserで生きようとした時に一番の障害になるくらいUIが妙

水をさしてしまうかもしれないけど....
QutebrowserでScrapboxを使ってますが、キーボードで完結できてますよdnin
矩形選択や単語単位の移動などは、やっぱり厳しくてArrow Keyが必要です
前無理だったようなと思って再び試してみましたが、マウスで一度フォーカスしたらそこそこいけますねkuuote
問題はScrapboxを編集するためだけにわざわざQutebrowserを立ち上げる必要が出てしまうことです
(WIP) Vim mode for scrapbox#5fa718b34daa9200007b3b4dも同じ問題を孕んでいます
Scrapbox内部APIにおけるクライアントとして振る舞うということのつもりで書きました
なのでvimをクライアントにすることもできるのかなと(現実的に実装できるかはともかく
ああ内部を触るというのはそういうことですね。本当の内部を触ることになるので色々大変そう
Scrpaboxのindex.jsをhookして処理を割り込ませるとか?
確かに現実的ではない
Scrapboxの公式クライアントが行う通信を解析して模倣するということも理論的にはできそう
うーん、vimのbackgroundでselenium/puppeteerを動かせばなんとか行けるかも知れません
performanceがかなり心配ですが