takker99/ScrapVim
ScrapVimの実装
https://github.com/takker99/ScrapVim
設計
event loop
keydown、compositionstart、compositionendを同じpromiseで購読する
async-libを使ってループをつくってぐるぐる回す
2024-05-24 21:48:49 思いついたinterfaceがあるのでメモする
code:js
setup({
// キーにmode nameを入れる
"page": {
// default value, key bindingsで指定されたコマンドのショートカットキー以外は、defaultのものをそのまま呼び出す
// disableNative: false,
"i": changeInsert,
// valueに関数を指定する
// preventdefaultさせるかなど、細かく制御できるようにしてもいいかも
},
"normal": {
// 指定した物以外preventDefault()する
disableNative: true,
"i": changeInsert,
"<Esc>": changePage,
// default動作を呼び出す
// (この場合は貼り付け)
"<C-v>": NATIVE,
},
"insert": {
"<C-[>": changeNormal,
"<Esc>": changeNormal,
},
"visual": {
disableNative: true,
"<Esc>": changeNormal,
},
});
Vim key notation
2023-11-06 15:53:44
JSの文字列のiterator消費挙動テスト
やりたいこと
insert mode時の文字流し込みを効率化する
現状だとinsertText()で一文字ずつ流し込んでいる
awaitしていないからラグは生じないだろうけど、大量にeventを発火させるのはperformanceによろしくなさそう
まあ問題が起きたら考えるか?
Event.preventDefault()とEvent.stopPropagation()で握りつぶさないキーのリストを設定できるようにする
<F12>とかを握りつぶさないようにする
2022-03-14
16:45:44 終了
https://gyazo.com/755b5aee7c4e962a5d1d9387ce6f7b4a
15:08:52 もう少し汎用的な形でテストしてみる
https://scrapbox-bundler.vercel.app?url=https://scrapbox.io/api/code/takker/takker99%252FScrapVim/test.ts&bundle&minify&run
$ deno check -r=https://scrapbox.io --remote https://scrapbox.io/api/code/takker/takker99%2FScrapVim/test.ts
code:test.ts
import { useStatusBar, textInput, takeCursor, insertText } from "../scrapbox-userscript-std/dom.ts";
import { commandWatcher } from "https://raw.githubusercontent.com/takker99/ScrapVim/fix-event/commandWatcher.ts";
import { execNormal } from "./normal.ts";
import { execInsert } from "./insert.ts";
import type { Mode, State } from "./types.ts";
let state: State = {
keys: [],
mode: "normal",
};
let timer: number | undefined;
const log = useStatusBar();
const { render, dispose } = useStatusBar();
render({ type: "text", text: state.mode });
for await (const key of commandWatcher(textInput()!)) {
clearTimeout(timer);
state.keys.push(key);
switch (state.mode) {
case "normal":
state = await execNormal(state);
break;
case "insert":
state = await execInsert(state);
break;
}
log.render({ type: "text", text: state.keys.join("") });
render({ type: "text", text: state.mode });
if (state.keys.length === 0) continue;
timer = setTimeout(() => {
if (state.mode === "insert") {
insertText(state.keys.join(""));
}
state.keys = [];
log.render({ type: "text", text: state.keys.join("") });
render({ type: "text", text: state.mode });
}, 1500);
}
log.dispose();
dispose();
code:types.ts
import type { takeCursor } from "../scrapbox-userscript-std/dom.ts";
export type Cursor = ReturnType<typeof takeCursor>;
export type Mode = "normal" | "insert";
export interface State {
keys: string[];
mode: Mode;
}
code:normal.ts
import {
takeCursor, undo, redo, scrollHalfUp, scrollHalfDown, press, caret, getText, getIndentCount,
} from "../scrapbox-userscript-std/dom.ts";
import type { Cursor, Mode, State } from "./types.ts";
const keymap: Record<string, (cursor: Cursor, mode: Mode) => Mode | Promise<Mode>> = {
h: (cursor: Cursor, mode) => (cursor.goByAction("go-left"), mode),
j: (cursor: Cursor, mode) => (cursor.goByAction("go-down"), mode),
k: (cursor: Cursor, mode) => (cursor.goByAction("go-up"), mode),
l: (cursor: Cursor, mode) => (cursor.goByAction("go-right"), mode),
gg: (cursor: Cursor, mode) => (cursor.goByAction("go-top"), mode),
G: (cursor: Cursor, mode) => (cursor.goByAction("go-bottom"), mode),
"$": (cursor: Cursor, mode) => (cursor.goByAction("go-line-tail"), mode),
"^": (cursor: Cursor, mode) => {
const position = caret().position;
const indent = getIndentCount(position.line) ?? 0;
cursor.setPosition({ line: position.line, char: indent });
return mode;
},
0: (cursor: Cursor, mode) => {
const position = caret().position;
cursor.setPosition({ line: position.line, char: 0 });
return mode;
},
b: (cursor, mode) => (cursor.goByAction("go-word-head"), mode),
w: (cursor, mode) => (cursor.goByAction("go-word-tail"), mode),
"<C-F>": (cursor, mode) => (cursor.goByAction("go-pagedown"), mode),
"<C-B>": (cursor, mode) => (cursor.goByAction("go-pageup"), mode),
"<C-D>": (cursor, mode) => (scrollHalfDown(), mode),
"<C-U>": (cursor, mode) => (scrollHalfUp(), mode),
u: (_, mode) => (undo(), mode),
"<C-R>": (_, mode) => (redo(), mode),
x: (_, mode) => (press("Delete"), mode),
dd: (cursor, mode) => {
cursor.goByAction("go-line-head");
cursor.goByAction("go-line-head");
press("End", { shiftKey: true });
press("ArrowRight", { shiftKey: true });
press("Delete");
return mode;
},
D: (cursor, mode) => {
press("End", { shiftKey: true });
press("Delete");
return mode;
},
i: (_, mode) => "insert",
I: (cursor, mode) => {
const position = caret().position;
const indent = getIndentCount(position.line) ?? 0;
cursor.setPosition({ line: position.line, char: indent });
return "insert";
},
a: (cursor, mode) => (cursor.goByAction("go-right"),"insert"),
A: (cursor, mode) => (cursor.goByAction("go-line-tail"),"insert"),
o: (cursor, mode) => {
cursor.goByAction("go-line-tail");
press("Enter");
if (!getText(caret().position.line)) return "insert";
cursor.goByAction("go-line-head");
cursor.goByAction("go-line-head");
press("End", { shiftKey: true });
press("Delete");
return "insert";
},
O: (cursor: Cursor, mode) => {
cursor.goByAction("go-line-head");
press("Enter");
cursor.goByAction("go-up");
return "insert";
},
cc: (cursor, mode) => {
cursor.goByAction("go-line-head");
cursor.goByAction("go-line-head");
press("End", { shiftKey: true });
press("ArrowRight", { shiftKey: true });
press("Delete");
return "insert";
},
C: (_, mode) => {
press("End", { shiftKey: true });
press("Delete");
return "insert";
},
"<C-H>": (cursor: Cursor, mode) => (press("ArrowLeft", { ctrlKey: true }), mode),
"<C-J>": (cursor: Cursor, mode) => (press("ArrowDown", { ctrlKey: true }), mode),
"<C-K>": (cursor: Cursor, mode) => (press("ArrowUp", { ctrlKey: true }), mode),
"<C-L>": (cursor: Cursor, mode) => (press("ArrowRight", { ctrlKey: true }), mode),
"<A-H>": (cursor: Cursor, mode) => (press("ArrowLeft", { altKey: true }), mode),
"<A-J>": (cursor: Cursor, mode) => (press("ArrowDown", { altKey: true }), mode),
"<A-K>": (cursor: Cursor, mode) => (press("ArrowUp", { altKey: true }), mode),
"<A-L>": (cursor: Cursor, mode) => (press("ArrowRight", { altKey: true }), mode),
};
export const execNormal = async (state: State): Promise<State> => {
const command = state.keys.join("");
const candidates = Object.keys(keymap).filter((key) => key.startsWith(command));
if (candidates.length === 0) return {
keys: [],
mode: state.mode,
}
if (candidates.length === 1 && candidates0 === command) {
const cursor = takeCursor();
const result = keymapcommand!(cursor, state.mode);
return {
keys: [],
mode: result instanceof Promise ? await result: result,
};
}
return state;
};
code:insert.ts
import { insertText, takeCursor, press } from "../scrapbox-userscript-std/dom.ts";
import type { Cursor, Mode, State } from "./types.ts";
const keymap: Record<string, (cursor: Cursor, mode: Mode) => Mode | Promise<Mode>> = {
"<Esc>": (_, mode) => "normal",
"<C-[>": (_, mode) => "normal",
"<BS>": (_, mode) => (press("Backspace"), mode),
"<Del>": (_, mode) => (press("Delete"), mode),
"<Space>": async (_, mode) => { await insertText(" "); return mode; },
"<Enter>": (_, mode) => (press("Enter"), mode),
"<Tab>": (_, mode) => (press("Tab"), mode),
"<S-Tab>": (_, mode) => (press("Tab", { shiftKey: true }), mode),
jj: (_, mode) => "normal",
};
export const execInsert = async (state: State): Promise<State> => {
const command = state.keys.join("");
const candidates = Object.keys(keymap).filter((key) => key.startsWith(command));
if (candidates.length === 0) {
insertText(command);
return { keys: [], mode: state.mode };
}
if (candidates.length === 1 && candidates0 === command) {
const cursor = takeCursor();
const result = keymapcommand!(cursor, state.mode);
return {
keys: [],
mode: result instanceof Promise ? await result: result,
};
}
return state;
};
13:16:02 作業ログ | 全てのKeyboardEventとCompositionEventを握りつぶす
11:13:39 まだできていない
ここまででやったこと
Vim-keymap-converter相当の実装
マウスはまだ
テストも書いた
commandWatcher()
キーイベントとIME確定を監視して、Vim key notationを1つずつ返す関数
結構丁寧に作ると時間かかるんだな
目標は達成できていないが、ここで終わりにする
丁寧に作っていたらなんだかんだで衝動がなくなった
満足したみたい
これ以上やると診察に間に合わなくなる
10:20:20 完全に衝動が抑えられなくなっている
とても悪いことなのだが、作ってしまうことにしてしまった
とりあえずnormal modeとinput modeの最小限のキーだけ実装する
key sequenceを貯めてstatus-barに表示してみるテストを参考にする
Gitでつくる
こうすればスマホでだらだらcodingすることを防げる
laptopを開かないと開発できなくなるので
#2024-05-24 21:54:17
#2023-11-06 15:57:51
#2022-05-07 19:56:23
#2022-04-25 14:44:50
#2022-03-14 10:19:07