diff --git a/src/editor-mode/decorator/builder/DecorationBuilder.ts b/src/editor-mode/decorator/builder/DecorationBuilder.ts index b90b9ce..5fcb48b 100644 --- a/src/editor-mode/decorator/builder/DecorationBuilder.ts +++ b/src/editor-mode/decorator/builder/DecorationBuilder.ts @@ -12,6 +12,7 @@ import { trimTag } from "src/utils"; import { SelectionObserver } from "src/editor-mode/observer"; import { editorLivePreviewField } from "obsidian"; import { refresherAnnot } from "src/editor-mode/annotations"; +import { activityFacet } from "src/editor-mode/facets"; export class DecorationBuilder { readonly parser: Parser; @@ -77,14 +78,13 @@ export class DecorationBuilder { onViewUpdate(update: ViewUpdate) { let state = update.state, view = update.view, - isLivePreview = state.field(editorLivePreviewField); - if (this.parser.isReparsing || this.parser.isInitializing || update.viewportMoved) { + isLivePreview = state.field(editorLivePreviewField), + activityRecorder = state.facet(activityFacet); + if (activityRecorder.verify("builder-view-update", "parse", true) || update.viewportMoved) { this.buildMain(view, state); - this.parser.isReparsing = this.parser.isInitializing = false; } - if (this.selectionObserver.isObserving || update.viewportMoved) { + if (activityRecorder.verify("builder-view-update", "observe", true) || update.viewportMoved) { this.buildSupplementary(isLivePreview); - this.selectionObserver.isObserving = false; } } /** @@ -102,8 +102,9 @@ export class DecorationBuilder { let state = transaction.state, isLivePreview = state.field(editorLivePreviewField), isRefreshed = transaction.annotation(refresherAnnot), - isModeChanged = isEditorModeChanged(state, transaction.startState); - if (this.parser.isReparsing || this.parser.isInitializing) { + isModeChanged = isEditorModeChanged(state, transaction.startState), + activityRecorder = state.facet(activityFacet); + if (activityRecorder.verify("builder-state-update", "parse")) { this.replaceLineBreaks(transaction.newDoc, transaction.changes); } if (isModeChanged || isRefreshed) { @@ -112,7 +113,7 @@ export class DecorationBuilder { } if (!isLivePreview) { this.removeOmitter(); - } else if (this.selectionObserver.isObserving || isModeChanged) { + } else if (activityRecorder.verify("builder-state-update", "observe") || isModeChanged) { this.omitFencedDivOpening(transaction.changes); } } diff --git a/src/editor-mode/extensions/index.ts b/src/editor-mode/extensions/index.ts index 790a705..b369a4c 100644 --- a/src/editor-mode/extensions/index.ts +++ b/src/editor-mode/extensions/index.ts @@ -2,10 +2,12 @@ import { Extension, RangeSet } from "@codemirror/state"; import { EditorView, ViewPlugin } from "@codemirror/view"; import { builderField, decoSetField, parserField, selectionObserverField } from "src/editor-mode/state-fields"; import { EditorPlugin } from "src/editor-mode/view-plugin"; +import { activityFacet } from "src/editor-mode/facets"; export const editorPlugin = ViewPlugin.fromClass(EditorPlugin); export const editorExtendedSyntax: Extension = [ + activityFacet.of(null), parserField, selectionObserverField, builderField, diff --git a/src/editor-mode/facets/activityFacet.ts b/src/editor-mode/facets/activityFacet.ts new file mode 100644 index 0000000..2218d9f --- /dev/null +++ b/src/editor-mode/facets/activityFacet.ts @@ -0,0 +1,9 @@ +import { Facet } from "@codemirror/state"; +import { ActivityRecorder } from "src/editor-mode/observer"; + +export const activityFacet = Facet.define({ + combine() { + return new ActivityRecorder(); + }, + static: true +}); \ No newline at end of file diff --git a/src/editor-mode/facets/index.ts b/src/editor-mode/facets/index.ts index cceab65..b80bf29 100644 --- a/src/editor-mode/facets/index.ts +++ b/src/editor-mode/facets/index.ts @@ -1,3 +1,4 @@ export * from "./settingsFacet"; export * from "./appFacet"; -export * from "./pluginFacet"; \ No newline at end of file +export * from "./pluginFacet"; +export * from "./activityFacet"; \ No newline at end of file diff --git a/src/editor-mode/observer/ActivityRecorder.ts b/src/editor-mode/observer/ActivityRecorder.ts new file mode 100644 index 0000000..ce4c2f1 --- /dev/null +++ b/src/editor-mode/observer/ActivityRecorder.ts @@ -0,0 +1,24 @@ +export class ActivityRecorder { + private isParsing: boolean = false; + private isObserving: boolean = false; + private verifier = { parse: new Set(), observe: new Set() }; + enter(entry: { isParsing?: boolean, isObserving?: boolean }) { + if (entry.isParsing) { this.isParsing = entry.isParsing } + if (entry.isObserving) { this.isObserving = entry.isObserving } + } + verify(key: unknown, activity: "parse" | "observe", reset = false) { + if (this.verifier[activity].has(key)) { return null } + let acted = activity == "parse" ? this.isParsing : this.isObserving; + if (reset) { this.reset(activity) } + else { this.verifier[activity].add(key) } + return acted; + } + private reset(activity: "parse" | "observe") { + this.verifier[activity].clear(); + if (activity == "parse") { + this.isParsing = false; + } else { + this.isObserving = false; + } + } +} \ No newline at end of file diff --git a/src/editor-mode/observer/index.ts b/src/editor-mode/observer/index.ts index 3fac454..a927935 100644 --- a/src/editor-mode/observer/index.ts +++ b/src/editor-mode/observer/index.ts @@ -1 +1,2 @@ -export * from "./SelectionObserver"; \ No newline at end of file +export * from "./SelectionObserver"; +export * from "./ActivityRecorder"; \ No newline at end of file diff --git a/src/editor-mode/parser/Parser.ts b/src/editor-mode/parser/Parser.ts index 888f6b2..05f4ab2 100644 --- a/src/editor-mode/parser/Parser.ts +++ b/src/editor-mode/parser/Parser.ts @@ -12,8 +12,6 @@ import { colorTag, customSpanTag, fencedDivTag } from "src/editor-mode/parser/to export class Parser { private state: ParserState; private queue: TokenQueue = new TokenQueue(); - isInitializing: boolean; - isReparsing: boolean; inlineTokens: TokenGroup = []; blockTokens: TokenGroup = []; reparsedRanges: Record; @@ -41,16 +39,12 @@ export class Parser { this.blockTokens = []; this.defineState({ doc, tree, offset: 0, settings: this.settings }); this.streamParse(); - this.isInitializing = true; - this.isReparsing = false; this.reparsedRanges = { [TokenLevel.INLINE]: { from: 0, initTo: 0, changedTo: this.inlineTokens.length }, [TokenLevel.BLOCK]: { from: 0, initTo: 0, changedTo: this.blockTokens.length } } } applyChange(doc: Text, tree: Tree, oldTree: Tree, changes: ChangeSet) { - this.isReparsing = true; - this.isInitializing = false; let changedRange = composeChanges(changes), offset = Math.min(oldTree.length, tree.length) + 1; if (changedRange) { diff --git a/src/editor-mode/state-fields/parserField.ts b/src/editor-mode/state-fields/parserField.ts index 9b6e34f..9f1495f 100644 --- a/src/editor-mode/state-fields/parserField.ts +++ b/src/editor-mode/state-fields/parserField.ts @@ -4,6 +4,7 @@ import { ParseContext, syntaxTree } from "@codemirror/language"; import { Tree } from "@lezer/common"; import { settingsFacet } from "src/editor-mode/facets"; import { refresherAnnot } from "src/editor-mode/annotations"; +import { activityFacet } from "../facets/activityFacet"; /** * In the context of extending syntax extension, this field should be the @@ -11,14 +12,17 @@ import { refresherAnnot } from "src/editor-mode/annotations"; */ export const parserField = StateField.define({ create(state) { - let parser = new Parser(state.facet(settingsFacet.reader)); + let parser = new Parser(state.facet(settingsFacet.reader)), + activityRecorder = state.facet(activityFacet); parser.initParse(state.doc, syntaxTree(state)); + activityRecorder.enter({ isParsing: true }); return parser; }, update(parser, transaction) { let oldTree = syntaxTree(transaction.startState), newTree = syntaxTree(transaction.state), - isRefreshed = transaction.annotation(refresherAnnot); + isRefreshed = transaction.annotation(refresherAnnot), + activityRecorder = transaction.state.facet(activityFacet); // Sometimes, there is an effect containing ParseContext and the most // updated Tree along with the state update. If any, we should use it // instead of that was taken from the state. @@ -30,16 +34,14 @@ export const parserField = StateField.define({ }); if (isRefreshed) { parser.initParse(transaction.newDoc, newTree); + activityRecorder.enter({ isParsing: true }); return parser; - } else { - parser.isInitializing = false; } // Also, do reparse when the Tree got to be reparsed. if (transaction.docChanged || oldTree.length != newTree.length) { parser.applyChange(transaction.newDoc, newTree, oldTree, transaction.changes); - } else { - parser.isReparsing = false; - } + activityRecorder.enter({ isParsing: true }); + } return parser; } }); \ No newline at end of file diff --git a/src/editor-mode/state-fields/selectionObserverField.ts b/src/editor-mode/state-fields/selectionObserverField.ts index fd01bd5..d6d7aef 100644 --- a/src/editor-mode/state-fields/selectionObserverField.ts +++ b/src/editor-mode/state-fields/selectionObserverField.ts @@ -1,22 +1,26 @@ import { StateField } from "@codemirror/state"; import { SelectionObserver } from "src/editor-mode/observer"; import { parserField } from "src/editor-mode/state-fields"; +import { activityFacet } from "src/editor-mode/facets"; export const selectionObserverField = StateField.define({ create(state) { - let observer = new SelectionObserver(state.field(parserField)); - observer.startObserve(state.selection, true); + let observer = new SelectionObserver(state.field(parserField)), + activityRecorder = state.facet(activityFacet); + observer.observe(state.selection, true); + activityRecorder.enter({ isObserving: true }); return observer; }, update(observer, transaction) { - // Start observer only when the parser has run or the selection has been moved. + // Start observer only when the parser has run or the selection has been + // moved. let selectionMoved = !(transaction.selection && transaction.startState.selection.eq(transaction.selection)), - isParsing = observer.parser.isReparsing || observer.parser.isInitializing; + activityRecorder = transaction.state.facet(activityFacet), + isParsing = activityRecorder.verify(observer, "parse"); transaction.isUserEvent("select"); if (isParsing || selectionMoved) { - observer.startObserve(transaction.newSelection, isParsing); - } else { - observer.isObserving = false; + observer.observe(transaction.newSelection, isParsing ?? false); + activityRecorder.enter({ isObserving: true }); } return observer; }