external-completion-3/completionObserver-2
from /programming-notes/external-completion-3/completionObserver-2
dependencies
scrapbox-dom-accessor
scrapbox-cursor-position-6
scrapbox-insert-text-2
scrapbox-motion-emulation
scrapbox-keyboard-emulation-2
code:script.js
import {scrapboxDOM} from '../scrapbox-dom-accessor/script.js';
import {position} from '../scrapbox-cursor-position-6/script.js';
import {insertText} from '../scrapbox-insert-text-2/script.js';
import {goHead, moveRight} from '../scrapbox-motion-emulation/script.js';
import {press} from '../scrapbox-keyboard-emulation-2/script.js';
export class CompletionObserver {
register({test, oncompletionstart, oncompletionupdate, oncompletionend} = {}) {
if (!test) throw Error('Function "test" is not specified.');
const id = this._idCounter++;
this._triggers.push({id, test, oncompletionstart, oncompletionupdate, oncompletionend});
return id;
}
unregister(triggerId) {
this._triggers = this._triggers.filter(({id}) => id !== triggerId);
}
async start() {
const pos = position();
if (!pos.char) return; // 更新がなかったとみなす
for (const trigger of this._triggers) {
if (!trigger.test(pos)) continue;
this._trigger = trigger;
return;
}
this.end();
}
end() { this._trigger = {} };
constructor({verbose = false} = {}) {
this._triggers = [];
this._idCounter = 0;
this._prevLineId = '';
this._verbose = verbose;
this._presentTrigger = {}; // 現在起動している補完
// cursor行を監視し、条件に一致次第補完を開始する
const observer = new MutationObserver(() => {
const id = scrapboxDOM.cursorLine?.id?.slice(1);
// focusが外れただけの場合は何もしない
if (!id) return;
// 前回と違う行に飛んだ場合は補完終了
if (this._prevLineId !== id) {
this._prevLineId = id;
this.end();
return;
}
// this._preventChangeがtrueで無い限り実行する
if (this._preventChange) {
this._preventChange = false;
return;
}
this.start();
});
observer.observe(scrapboxDOM.lines, {childList: true, subtree: true});
}
get completing() {
return this._trigger.id !== undefined;
}
// 現在起動している補完
get _trigger() { return this._presentTrigger; }
set _trigger(trigger) {
const pos = position();
if (this._presentTrigger.id !== trigger.id) {
this._presentTrigger.oncompletionend?.(pos, (...params) => this._replace(...params));
this._presentTrigger = trigger;
this._presentTrigger.oncompletionstart?.(pos, (...params) => this._replace(...params));
} else {
this._presentTrigger.oncompletionupdate?.(pos, (...params) => this._replace(...params));
}
}
async _replace(text, index, length) {
scrapboxDOM.textInput.focus();
goHead();
moveRight(index);
for (let i = 0; i < length; i++) {
press('ArrowRight', {shiftKey: true});
}
this._preventChange = true;
this._log('_replace', 'replace text: ', {text, index, length});
await insertText(text);
this.end();
}
_log(functionName, ...messages) {
if (!this._verbose) return;
console.log([${functionName}@external-completion-3/completionObserver-2], ...messages);
}
}
#2021-04-09 14:08:03
#2021-04-08 17:11:56