ScrapVim-lite-2/parser
ScrapVim-lite-2で実際に入力されたキーと実行するコマンドとを対応させる機能
import
code:script.js
import {KeyStack} from '/api/code/takker/ScrapVim-lite-2%2FkeyStack/script.js';
import {js2vim} from '/api/code/takker/Vim-keymap-converter/script.js';
import {
move,
insert,
edit,
history,
outline,
commands,
scroll,
} from '/api/code/takker/ScrapVim-lite-2%2Fcommand/script.js';
import {Register} from '/api/code/takker/ScrapVim-lite/register.js';
code:script.js
const modes = [
'normal',
'insert',
];
code:script.js
export class Parser {
constructor({editor, lines, cursorInfo, cursor, cursorBar}) {
this._editor = editor;
this._lines = lines;
this.register = new Register();
this._cursorInfo = cursorInfo;
this._cursorBar = cursorBar;
this._cursor = cursor;
this.stack = new KeyStack();
this._onNormal();
this._isDebug = true;
}
start () {
this._editor.addEventListener('keydown', e => this.handleKeyDown(e));
this._cursor.addEventListener('compositionend', e => this.squashIMEInput(e));
}
get cursorInfo() {
const {target, cursor} = this._cursorInfo();
return cursor;
}
get mode() {
return this._mode;
}
Modeごとにparserを分けたほうがよさそう
modeを切り替えるclassを作って、そこでmodeを管理する
code:script.js
_onInsert() {
this._mode = 'insert';
this._cursorBar.classList.remove('normal-mode');
this._isBubble = keymap => !['<Esc>','<C->'.includes(keymap);
this._log(Moved to ${this.mode} mode.);
}
_onNormal() {
this._mode = 'normal';
this._cursorBar.classList.add('normal-mode');
this._isBubble = keymap => /<F\w+>|<C-v>/.test(keymap);
this._log(Moved to ${this.mode} mode.);
}
handleKeyDown(e) {
const keymap = js2vim(e);
if (this._isBubble(keymap)) return;
if (!e.isTrusted) {
this._log('%o', e);
return;
}
e.preventDefault();
e.stopPropagation();
this._log('Pressed keymap: %o', keymap);
switch(this.mode) {
case 'normal':
this.onNormalMode(keymap);
break;
case 'insert':
this.onInsertMode(keymap);
break;
}
this.stack.push(keymap);
}
Normal Modeのとき
code:script.js
onNormalMode(keymap) {
this._log('Analyze commands as a one of the normal mode.');
switch (keymap) {
case 'h':
move.left({cursor: this.cursorInfo});
break;
case 'j':
move.down();
break;
case 'k':
move.up();
break;
case 'l':
move.right({cursor: this.cursorInfo});
break;
case 'e':
if (this.stack.last === 'g') {
move.backWordEnd({cursor: this.cursorInfo});
} else {
move.goWordEnd({cursor: this.cursorInfo});
}
break;
case 'b':
move.backWordHead({cursor: this.cursorInfo});
break;
case 'w':
move.goWordHead({cursor: this.cursorInfo});
break;
case 'g':
if (this.stack.last !== 'g') return;
move.firstLine({cursor: this.cursorInfo, lines: this._lines});
break;
case 'G':
move.lastLine({cursor: this.cursorInfo, lines: this._lines});
break;
case '^':
case 'H': // takker用cutomize
move.nonBlankCharOfLine({cursor: this.cursorInfo});
break;
case '0':
move.startOfLine({cursor: this.cursorInfo});
break;
case '$':
case 'L': // takker用cutomize
move.endOfLine({cursor: this.cursorInfo});
break;
case 'i':
insert.before({onInsert: () => this._onInsert()});
break;
case 'a':
insert.after({onInsert: () => this._onInsert()});
break;
case 'I':
insert.startOfLine({onInsert: () => this._onInsert(), cursor: this.cursorInfo});
break;
case 'A':
insert.endOfLine({onInsert: () => this._onInsert()});
break;
case 'o':
insert.newLineBelow({onInsert: () => this._onInsert()});
break;
case 'O':
insert.newLineAbove({onInsert: () => this._onInsert()});
break;
case 'x':
edit.cut();
break;
case 'd':
if (this.stack.last !== 'd') return;
edit.deleteLine();
break;
case 'D':
edit.deleteForEnd();
break;
case 'c':
if (this.stack.last !== 'c') return;
edit.changeLine({onInsert: () => this._onInsert()});
break;
case 'C':
edit.changeForEnd({onInsert: () => this._onInsert()});
break;
case '<C-b>':
scroll.up();
break;
case '<C-f>':
scroll.down();
break;
// 不完全
case '<C-u>':
scroll.halfUp();
break;
// 不完全
case '<C-d>':
scroll.halfDown();
break;
case 'u':
history.undo();
break;
case '<C-r>':
history.redo();
break;
// アウトライン編集
case '<C-h>':
outline.deindentLines();
break;
case '<C-j>':
outline.downLines();
break;
case '<C-k>':
outline.upLines();
break;
case '<C-l>':
outline.indentLines();
break;
case '<A-h>':
outline.deindentBlock();
break;
case '<A-j>':
outline.downBlock();
break;
case '<A-k>':
outline.upBlock();
break;
case '<A-l>':
outline.indentBlock();
break;
// timestamp入力
case '<A-t>':
commands.timestamp();
break;
// icon挿入
case '<C-i>':
commands.icon();
break;
default:
break;
}
this.stack.flush();
}
Insert Modeのとき
code:script.js
onInsertMode(keymap) {
this._log('Analyze commands as a one of the insert mode.');
switch(keymap) {
case '<Esc>':
case '<C-[>':
insert.exit({onInsertLeave: () => this._onNormal()});
break;
default:
break;
}
this.stack.flush();
}
IMEを握りつぶす
実際の操作はScrapVim-lite-2/commandに委譲する
from ScrapVim-lite#5fb8c6581280f0000041d123
code:script.js
squashIMEInput(e) {
//this._log(Now IME is being executed. mode = ${this.mode});
if (this.mode === 'insert') return;
// 流し込まれた文字列の長さ
const length = e.data.length;
// 少し待ってから入力された文字を消す
setTimeout(() => edit.backspace({count: length}), 50);
}
debug用
code:script.js
_log(msg, ...objects){
if (!this._isDebug) return;
if (objects.length > 0) {
console.log([scrapVim-lite-2] ${msg}, ...objects);
return;
}
console.log([scrapVim-lite-2] ${msg});
}
}
#2020-11-26 06:34:09
#2020-11-25 10:03:41
#2020-11-24 18:57:47