ScrapVim-lite-2/command
import
code:script.js
import {KeyboardEmulator} from '/api/code/takker/scrapbox-keyboard-emulation/script.js';
import {getLinkIncludingCursor} from '/api/code/takker/Scrapboxでcursor下のリンクを取得する/script.js';
import {getCursorInfo} from '/api/code/takker/scrapbox-cursor-position/getCursorInfo.js';
import {Register} from '/api/code/takker/ScrapVim-lite/register.js';
import {jumpToLF, jumpToChar} from '/api/code/takker/scrapbox-cursor-jumper/script.js';
Utilites
code:script.js
const emulator = new KeyboardEmulator();
debug用
先に宣言しないとだめみたい
code:script.js
const isDebug = true;
function _log(msg, ...objects){
if (!isDebug) return;
if (objects.length > 0) {
console.log([scrapVim-lite-2] ${msg}, ...objects);
return;
}
console.log([scrapVim-lite-2] ${msg});
}
parametor
code:ts
interface Props {
count: number; // commandのrepeat数
visual: boolean; // visual modeか否か
lines: HTMLDivElement; // div.lines
cursor: {
line: HTMLDivElement; // cursorの現在行
index: number; // cursorがいる文字番号
};
onInsert: () => void; // insert modeに移るときに実行する関数
onInsertLeave: () => void; // insert modeからnormal modeに戻るときに実行する関数
}
motion系は移動範囲を返却できるようにしておくとよさそう
そのままoperatorに範囲を渡せる
こんなinterfaceでどうだろう?
code:ts
interface Selections {
start: {
row: number;
column: number;
},// 選択範囲の開始
end: {
row: number;
column: number;
}, // 選択範囲の終わり
text: string,// 選択範囲に含まれる文字列
}
e.g.
code:ts
left: (...) => {
const oldCursor = ...;
for (...) {
...
}
const cursor = cursorInfo
return {start: oldCursor, end: cursor, text: '...'};
}
cursorの移動
←と→は行をまたげないようにした
code:script.js
export const move = {
left: ({count = 1, visual = false, cursor: {line, index}} = {}) => {
for (const _ of range(count)) {
if (index === 0) break;
emulator.press('ArrowLeft', {shiftKey: visual});
}
},
up: ({count = 1, visual = false} = {}) => {
for (const _ of range(count)) {
emulator.press('ArrowUp', {shiftKey: visual});
}
},
down: ({count = 1, visual = false} = {}) => {
for (const _ of range(count)) {
emulator.press('ArrowDown', {shiftKey: visual});
}
},
right: ({count = 1, visual = false, cursor: {line, index}} = {}) => {
//_log(move right: line.length = ${line.textContent.length}, cursor index = ${index});
for (const _ of range(count)) {
if (line.textContent.length - 1 <= index) break;
emulator.press('ArrowRight', {shiftKey: visual});
}
},
code:script.js
nonBlankCharOfLine: ({visual = false, cursor: {line}} = {}) => {
// 先頭の空白を取得
const headSpaces = line.textContent.match(/\s+|\S+\s*/ug)?.0; const index = /^\s+$/.test(headSpaces) ? headSpaces.length : 0;
_log(move to ${index} at %o, line);
jumpToChar({id: line.id, index: index, shiftKey: visual});
},
startOfLine: ({visual = false} = {}) => {
emulator.press('Home', {shiftKey: visual});
emulator.press('Home', {shiftKey: visual});
},
endOfLine: ({visual = false} = {}) => {
emulator.press('End', {shiftKey: visual});
},
本家Vimとは挙動が違う
常にLFに飛ぶようになっている
必要であれば直す
code:script.js
firstLine: ({visual = false, lines} = {}) => {
jumpToLF({id: lines.firstElementChild.id, shiftKey: visual});
},
lastLine: ({visual = false, lines} = {}) => {
jumpToLF({id: lines.lastElementChild.id, shiftKey: visual});
},
goLine: ({count, visual = false, lines} = {}) => {
const id = getLineId(count) ?? lines.lastElementChild.id;
jumpToLF({id: id, shiftKey: visual});
},
単語移動
キーでないとリンクやcode blockの先頭内を移動できない
performanceは落ちるが仕方ない。
2020-12-18 05:39:00 戻すの忘れてた
後方のWordsの先頭に移動する
code:script.js
goWordHead: ({count = 1, visual = false, cursor: {line, index}} = {}) => {
//後方検索
_log('splitted words: %o, cursor.line: %o, cursor.index: %o',
splitWords(line.textContent), line, index);
let match = splitWords(line.textContent)
.find(word => word.index > index);
// 単語の先頭がこれ以上なければ、次行に進む
if (!match) {
//先頭に進むのは確実なので、End+→で飛ぶ
_log('Go to the next line.');
emulator.press('End', {shiftKey: visual});
emulator.press('ArrowRight', {shiftKey: visual});
return;
}
_log('the present line: %o', line);
_log('the next word: %o', match);
jumpToChar({id: line.id, index: match.index, shiftKey: visual});
},
前方のWordsの先頭に移動する
code:script.js
backWordHead: ({count = 1, visual = false, cursor: {line, index}} = {}) => {
// 前方検索
_log('splitted words: %o, cursor.line: %o, cursor.index: %o',
splitWords(line.textContent), line, index);
let match = splitWords(line.textContent)
.filter(word => word.index < index)?.pop();
let pressNum = 0;
if (!match) {
// なければ前の行に入る
_log('Go to the previous line.');
const prevLine = line.previousElementSibling;
if (!prevLine) return; // 先頭行だったら何もしない
match = splitWords(prevLine.textContent)?.pop();
_log('the previous line: %o', prevLine);
_log('the next word: %o', match);
jumpToLF({id: prevLine.id, shiftKey: visual});
jumpToChar({id: prevLine.id, index: match.index, shiftKey: visual});
return;
}
_log('the present line: %o', line);
_log('the next word: %o', match);
jumpToChar({id: line.id, index: match.index, shiftKey: visual});
},
後方のWordsの末尾に移動する
code:script.js
goWordEnd: ({count = 1, visual = false, cursor: {line, index}} = {}) => {
//後方検索
_log('splitted words: %o, cursor.line: %o, cursor.index: %o',
splitWords(line.textContent), line, index);
let match = splitWords(line.textContent)
.find(word => word.index + (word.length - 1) > index);
if (!match) {
// なければ次の行に入る
_log('Go to the next line.');
const nextLine = line.nextElementSibling;
if (!nextLine) return; // 最後の行だったら何もしない
match = splitWords(line.textContent)0; _log('the next line: %o', nextLine);
_log('the next word: %o', match);
jumpToLF({id: nextLine.id, shiftKey: visual});
jumpToChar({id: nextLine.id, index: match.index + match.length - 1, shiftKey: visual});
return;
}
_log('the present line: %o', line);
_log('the next word: %o', match);
jumpToChar({id: line.id, index: match.index + match.length - 1, shiftKey: visual});
},
前方のWordsの末尾に移動する
code:script.js
backWordEnd: ({count = 1, visual = false, cursor: {line, index}} = {}) => {
// 前方検索
_log('splitted words: %o, cursor.line: %o, cursor.index: %o',
splitWords(line.textContent), line, index);
let match = splitWords(line.textContent)
.filter(word => word.index + (word.length - 1) < index)?.pop();
// 単語の末尾が前方になければ前の行に移動する
if (!match) {
// 行末に移動することがわかっているので、Home + ←を使う
_log('Go to the previous line.');
emulator.press('Home', {shiftKey: visual});
emulator.press('ArrowLeft', {shiftKey: visual});
emulator.press('ArrowLeft', {shiftKey: visual});
return;
}
_log('the present line: %o', line);
_log('the next word: %o', match);
jumpToChar({id: line.id, index: match.index + match.length - 1, shiftKey: visual});
},
code:script.js
};
scroll
状態がほしいな……
現在の画面に表示されている行数を知りたい
code:script.js
export const scroll = {
up: ({count = 1, visual = false, /*linesParPage*/} = {}) => {
for (const _ of range(count)) {
emulator.press('PageUp', {shiftKey: visual});
}
},
down: ({count = 1, visual = false} = {}) => {
for (const _ of range(count)) {
emulator.press('PageDown', {shiftKey: visual});
}
},
halfUp: ({count = 1, visual = false} = {}) => {
for (const _ of range(count)) {
emulator.press('PageUp', {shiftKey: visual});
}
},
halfDown: ({count = 1, visual = false} = {}) => {
for (const _ of range(count)) {
emulator.press('PageDown', {shiftKey: visual});
}
},
};
code:script.js
export const outline = {
indentLines: ({count = 1} = {}) => {
for (const _ of range(count)) {
emulator.press('ArrowRight',{ctrlKey: true});
}
},
deindentLines: ({count = 1} = {}) => {
for (const _ of range(count)) {
emulator.press('ArrowLeft',{ctrlKey: true});
}
},
indentBlock: ({count = 1} = {}) => {
for (const _ of range(count)) {
emulator.press('ArrowRight',{altKey: true});
}
},
deindentBlock: ({count = 1} = {}) => {
for (const _ of range(count)) {
emulator.press('ArrowLeft',{altKey: true});
}
},
upLines: ({count = 1} = {}) => {
for (const _ of range(count)) {
emulator.press('ArrowUp',{ctrlKey: true});
}
},
downLines: ({count = 1} = {}) => {
for (const _ of range(count)) {
emulator.press('ArrowDown',{ctrlKey: true});
}
},
upBlock: ({count = 1} = {}) => {
for (const _ of range(count)) {
emulator.press('ArrowUp',{altKey: true});
}
},
downBlock: ({count = 1} = {}) => {
for (const _ of range(count)) {
emulator.press('ArrowDown',{altKey: true});
}
},
};
insert mode
onInsert: insert modeに移行する処理
cursor: {line}
line: div.cursor-line
code:script.js
export const insert = {
before: ({onInsert}) => {
onInsert();
},
after: ({onInsert}) => {
emulator.press('ArrowRight');
onInsert();
},
startOfLine: ({onInsert, cursor}) => {
move.nonBlankCharOfLine({cursor: cursor})
onInsert();
},
endOfLine: ({onInsert}) => {
emulator.press('End');
onInsert();
},
Shift+Enterは使えないみたい
代替方法を使う
Indentありでいいか。
code:script.js
newLineBelow: ({onInsert}) => {
emulator.press('End');
emulator.press('Enter');
// 余計な空白を消す
//move.startOfLine();
//emulator.press('End', {shiftKey: true});
//emulator.press('Delete');
onInsert();
},
newLineAbove: ({onInsert}) => {
move.startOfLine();
emulator.press('Enter');
emulator.press('ArrowUp');
onInsert();
},
exit: ({onInsertLeave}) => onInsertLeave(),
};
operator
後々、operatorと範囲指定を分離したい
先に選択範囲を出し、それに対してoperatorを適用できるようにしたい
code:script.js
export const operator = {
};
edit
code:script.js
export const edit = {
cut: ({count = 1} = {}) => {
for (const _ of range(count)) {
emulator.press('Delete');
}
},
backspace: ({count = 1} = {}) => {
for (const _ of range(count)) {
emulator.press('Backspace');
}
},
deleteForEnd: () => {
emulator.press('End', {shiftKey: true});
emulator.press('Delete');
},
deleteLine: ({count = 1} = {}) => {
move.startOfLine();
for (const _ of range(count)) {
emulator.press('End', {shiftKey: true});
emulator.press('ArrowRight', {shiftKey: true});
}
emulator.press('Delete');
},
changeForEnd: ({onInsert}) => {
edit.deleteForEnd();
onInsert();
},
changeLine: ({count = 1, onInsert} = {}) => {
move.startOfLine();
for (const _ of range(count)) {
emulator.press('ArrowRight', {shiftKey: true});
emulator.press('End', {shiftKey: true});
}
emulator.press('Delete');
onInsert();
},
};
undo/redo
code:script.js
export const history = {
undo: ({count = 1} = {}) => {
for (const _ of range(count)) {
emulator.press('z', {ctrlKey: true});
}
},
redo: ({count = 1} = {}) => {
for (const _ of range(count)) {
emulator.press('Z', {ctrlKey: true, shiftKey: true});
}
},
};
その他command
code:script.js
export const commands = {
timestamp: ({count = 1} = {}) => {
for (const _ of range(count)) {
emulator.press('t',{altKey: true});
}
},
icon: ({count = 1} = {}) => {
for (const _ of range(count)) {
emulator.press('i',{ctrlKey: true});
}
},
};