refactor: check core activity using ActivityRecorder

Key-paired activity checking. So, checking only is done once in the same activity. The keys and the activity states will be reseted once the view or the activity have been updated.
This commit is contained in:
kotaindah55 2025-03-17 10:26:53 +02:00
parent 9518006ff1
commit 076ed7d952
9 changed files with 68 additions and 30 deletions

View file

@ -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);
}
}

View file

@ -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,

View file

@ -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
});

View file

@ -1,3 +1,4 @@
export * from "./settingsFacet";
export * from "./appFacet";
export * from "./pluginFacet";
export * from "./pluginFacet";
export * from "./activityFacet";

View file

@ -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;
}
}
}

View file

@ -1 +1,2 @@
export * from "./SelectionObserver";
export * from "./SelectionObserver";
export * from "./ActivityRecorder";

View file

@ -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<TokenLevel, { from: number, initTo: number, changedTo: number }>;
@ -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) {

View file

@ -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;
}
});

View file

@ -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;
}