Skip to content

Commit 4d8141e

Browse files
committed
better code block highlighting and reorganized settings
1 parent 07dc99e commit 4d8141e

File tree

15 files changed

+484
-391
lines changed

15 files changed

+484
-391
lines changed

exampleVault/Other Note.md

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,14 @@ This note is to test syncing to another note.
1616
### Select
1717
Select
1818
```meta-bind
19-
INPUT[select(
20-
option(option a),
21-
option(option b),
22-
option(option c),
23-
option(option d)
24-
):select]
19+
INPUT[
20+
select(
21+
option(option a),
22+
option(option b),
23+
option(option c),
24+
option(option d)
25+
):select
26+
]
2527
```
2628

2729
Select with title

src/api/IAPI.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { type InputFieldAPI } from './InputFieldAPI';
66
import { type InputFieldFactory } from '../fields/inputFields/InputFieldFactory';
77
import { type ButtonActionRunner } from '../fields/button/ButtonActionRunner';
88
import { type ButtonManager } from '../fields/button/ButtonManager';
9+
import { type SyntaxHighlightingAPI } from './SyntaxHighlightingAPI';
910

1011
export interface IAPI {
1112
readonly plugin: IPlugin;
@@ -31,4 +32,6 @@ export interface IAPI {
3132

3233
readonly buttonActionRunner: ButtonActionRunner;
3334
readonly buttonManager: ButtonManager;
35+
36+
readonly syntaxHighlighting: SyntaxHighlightingAPI;
3437
}

src/api/SyntaxHighlightingAPI.ts

Lines changed: 62 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
1-
import { type ParsingRange } from '@lemons_dev/parsinom/lib/HelperTypes';
2-
import { type API } from './API';
3-
import type MetaBindPlugin from '../main';
1+
import { type ParsingPosition, type ParsingRange } from '@lemons_dev/parsinom/lib/HelperTypes';
42
import { ParsingError, runParser } from '../parsers/ParsingError';
53
import {
4+
BIND_TARGET_HLP,
65
INLINE_BUTTON_DECLARATION_HLP,
76
INPUT_FIELD_DECLARATION_HLP,
87
VIEW_FIELD_DECLARATION_HLP,
98
} from '../parsers/HLPUtils';
109
import { InlineMDRCType } from '../utils/InlineMDRCUtils';
10+
import { type IPlugin } from '../IPlugin';
11+
import { type IAPI } from './IAPI';
12+
import { P_UTILS } from '@lemons_dev/parsinom/lib/ParserUtils';
13+
import { type Parser } from '@lemons_dev/parsinom/lib/Parser';
1114

1215
export class Highlight {
1316
range: ParsingRange;
@@ -20,28 +23,42 @@ export class Highlight {
2023
}
2124

2225
export class SyntaxHighlighting {
26+
str: string;
2327
highlights: Highlight[];
2428
parsingError?: ParsingError;
2529

26-
constructor(highlights: Highlight[], parsingError?: ParsingError) {
27-
this.highlights = highlights;
30+
constructor(str: string, highlights: Highlight[], parsingError?: ParsingError) {
31+
this.str = str;
32+
this.highlights = highlights.filter(x => x.range.from.index !== x.range.to.index);
2833
this.parsingError = parsingError;
2934
}
3035

3136
getHighlights(): Highlight[] {
3237
if (this.parsingError === undefined) {
33-
return this.highlights.filter(x => x.range.from.index !== x.range.to.index);
38+
return this.highlights;
39+
}
40+
41+
let errorTo: ParsingPosition;
42+
43+
if (this.str[this.parsingError.parseFailure.furthest.index] === '\n') {
44+
errorTo = {
45+
index: this.parsingError.parseFailure.furthest.index + 1,
46+
column: 1,
47+
line: this.parsingError.parseFailure.furthest.line + 1,
48+
};
49+
} else {
50+
errorTo = {
51+
index: this.parsingError.parseFailure.furthest.index + 1,
52+
column: this.parsingError.parseFailure.furthest.column + 1,
53+
line: this.parsingError.parseFailure.furthest.line,
54+
};
3455
}
3556

3657
return [
3758
new Highlight(
3859
{
3960
from: this.parsingError.parseFailure.furthest,
40-
to: {
41-
line: this.parsingError.parseFailure.furthest.line,
42-
column: this.parsingError.parseFailure.furthest.column + 1,
43-
index: this.parsingError.parseFailure.furthest.index + 1,
44-
},
61+
to: errorTo,
4562
},
4663
'error',
4764
),
@@ -50,62 +67,56 @@ export class SyntaxHighlighting {
5067
}
5168

5269
export class SyntaxHighlightingAPI {
53-
public readonly api: API;
54-
public readonly plugin: MetaBindPlugin;
70+
public readonly api: IAPI;
71+
public readonly plugin: IPlugin;
5572

56-
constructor(plugin: MetaBindPlugin) {
73+
constructor(plugin: IPlugin) {
5774
this.plugin = plugin;
5875
this.api = plugin.api;
5976
}
6077

61-
highlightInputFieldDeclaration(str: string): SyntaxHighlighting {
62-
try {
63-
return new SyntaxHighlighting(runParser(INPUT_FIELD_DECLARATION_HLP, str));
64-
} catch (e) {
65-
if (e instanceof ParsingError) {
66-
return new SyntaxHighlighting([], e);
67-
} else {
68-
console.error(e);
69-
return new SyntaxHighlighting([]);
70-
}
71-
}
78+
highlightInputFieldDeclaration(str: string, trimWhiteSpace: boolean): SyntaxHighlighting {
79+
return this.highlightWithParser(str, trimWhiteSpace, INPUT_FIELD_DECLARATION_HLP);
7280
}
7381

74-
highlightViewFieldDeclaration(str: string): SyntaxHighlighting {
75-
try {
76-
return new SyntaxHighlighting(runParser(VIEW_FIELD_DECLARATION_HLP, str));
77-
} catch (e) {
78-
if (e instanceof ParsingError) {
79-
return new SyntaxHighlighting([], e);
80-
} else {
81-
console.error(e);
82-
return new SyntaxHighlighting([]);
83-
}
84-
}
82+
highlightViewFieldDeclaration(str: string, trimWhiteSpace: boolean): SyntaxHighlighting {
83+
return this.highlightWithParser(str, trimWhiteSpace, VIEW_FIELD_DECLARATION_HLP);
8584
}
8685

87-
highlightInlineButtonDeclaration(str: string): SyntaxHighlighting {
88-
try {
89-
return new SyntaxHighlighting(runParser(INLINE_BUTTON_DECLARATION_HLP, str));
90-
} catch (e) {
91-
if (e instanceof ParsingError) {
92-
return new SyntaxHighlighting([], e);
93-
} else {
94-
console.error(e);
95-
return new SyntaxHighlighting([]);
96-
}
97-
}
86+
highlightInlineButtonDeclaration(str: string, trimWhiteSpace: boolean): SyntaxHighlighting {
87+
return this.highlightWithParser(str, trimWhiteSpace, INLINE_BUTTON_DECLARATION_HLP);
9888
}
9989

100-
highlight(str: string, mdrcType: InlineMDRCType): SyntaxHighlighting {
90+
highlight(str: string, mdrcType: InlineMDRCType, trimWhiteSpace: boolean): SyntaxHighlighting {
10191
if (mdrcType === InlineMDRCType.INPUT_FIELD) {
102-
return this.highlightInputFieldDeclaration(str);
92+
return this.highlightInputFieldDeclaration(str, trimWhiteSpace);
10393
} else if (mdrcType === InlineMDRCType.VIEW_FIELD) {
104-
return this.highlightViewFieldDeclaration(str);
94+
return this.highlightViewFieldDeclaration(str, trimWhiteSpace);
10595
} else if (mdrcType === InlineMDRCType.BUTTON) {
106-
return this.highlightInlineButtonDeclaration(str);
96+
return this.highlightInlineButtonDeclaration(str, trimWhiteSpace);
10797
}
10898

10999
throw new Error(`Unknown MDRCType ${mdrcType}`);
110100
}
101+
102+
highlightBindTarget(str: string, trimWhiteSpace: boolean): SyntaxHighlighting {
103+
return this.highlightWithParser(str, trimWhiteSpace, BIND_TARGET_HLP);
104+
}
105+
106+
private highlightWithParser(str: string, trimWhiteSpace: boolean, parser: Parser<Highlight[]>): SyntaxHighlighting {
107+
try {
108+
if (trimWhiteSpace) {
109+
return new SyntaxHighlighting(str, runParser(parser.trim(P_UTILS.optionalWhitespace()).thenEof(), str));
110+
} else {
111+
return new SyntaxHighlighting(str, runParser(parser.thenEof(), str));
112+
}
113+
} catch (e) {
114+
if (e instanceof ParsingError) {
115+
return new SyntaxHighlighting(str, [], e);
116+
} else {
117+
console.error(e);
118+
return new SyntaxHighlighting(str, []);
119+
}
120+
}
121+
}
111122
}

src/cm6/Cm5_Modes.ts

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import { type Mode, type StringStream } from 'codemirror';
2+
import { yaml } from '@codemirror/legacy-modes/mode/yaml';
3+
import { SyntaxHighlighting } from '../api/SyntaxHighlightingAPI';
4+
import { type InlineMDRCType, InlineMDRCUtils } from '../utils/InlineMDRCUtils';
5+
import type MetaBindPlugin from '../main';
6+
7+
export function registerCm5HLModes(plugin: MetaBindPlugin): void {
8+
/* eslint-disable */
9+
10+
if (!plugin.settings.enableSyntaxHighlighting) {
11+
return;
12+
}
13+
14+
window.CodeMirror.defineMode('meta-bind-button', _config => {
15+
const mode: Mode<any> = {
16+
startState: () => {
17+
return yaml.startState?.(4);
18+
},
19+
blankLine: (state: any) => {
20+
return yaml.blankLine?.(state, 4);
21+
},
22+
copyState: (_state: any) => {
23+
return yaml.startState?.(4);
24+
},
25+
token: (stream: any, state: any) => {
26+
return `line-HyperMD-codeblock ${yaml.token?.(stream, state)}`;
27+
},
28+
};
29+
30+
return mode;
31+
});
32+
33+
const codeBlockEndRegexp = /^\s*(```+|~~~+)/;
34+
35+
type MBModeState = {
36+
str: string;
37+
mdrcType: InlineMDRCType;
38+
highlights: SyntaxHighlighting;
39+
line: number;
40+
};
41+
42+
window.CodeMirror.defineMode('meta-bind', _config => {
43+
const mode: Mode<any> = {
44+
startState: () => {
45+
return {
46+
str: undefined,
47+
mdrcType: undefined,
48+
highlights: undefined,
49+
line: 1,
50+
};
51+
},
52+
53+
token: (stream: StringStream, state: MBModeState) => {
54+
// the idea is that we get the whole content of the code block at the beginning
55+
// then parse it and save the generated highlights
56+
// then the stream parser can simply look up the highlights for the current line and column
57+
if (state.str === undefined) {
58+
let lines = [stream.string];
59+
let i = 1;
60+
let lookAhead = stream.lookAhead(i);
61+
62+
while (lookAhead !== undefined && !codeBlockEndRegexp.test(lookAhead)) {
63+
lines.push(lookAhead);
64+
i += 1;
65+
lookAhead = stream.lookAhead(i);
66+
67+
// fail-safe, if we miss the end of the code block
68+
if (i > 100) break;
69+
}
70+
71+
state.str = lines.filter(x => x.trim() !== '').join('\n');
72+
73+
let mdrcType = InlineMDRCUtils.isDeclarationAndGetMDRCType(state.str.trim());
74+
if (mdrcType === undefined) {
75+
state.highlights = new SyntaxHighlighting(state.str, []);
76+
} else {
77+
state.mdrcType = mdrcType;
78+
state.highlights = plugin.api.syntaxHighlighting.highlight(state.str, state.mdrcType, true);
79+
}
80+
81+
console.log(state.str, state.highlights.getHighlights());
82+
}
83+
84+
const lineHighlights = state.highlights.getHighlights().filter(h => h.range.from.line === state.line);
85+
const highlight = lineHighlights.find(h => h.range.from.column === stream.pos + 1);
86+
87+
console.log(state.line, stream.pos, stream.peek(), highlight);
88+
89+
if (highlight === undefined) {
90+
stream.next();
91+
if (stream.eol()) {
92+
state.line += 1;
93+
}
94+
return `line-HyperMD-codeblock`;
95+
}
96+
97+
if (!stream.eatWhile(() => stream.pos + 1 < highlight.range.to.column)) {
98+
stream.next();
99+
}
100+
if (stream.eol()) {
101+
state.line += 1;
102+
}
103+
return `line-HyperMD-codeblock mb-highlight-${highlight.tokenClass}`;
104+
},
105+
};
106+
107+
return mode;
108+
});
109+
}

src/cm6/Cm6_ViewPlugin.ts

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -47,18 +47,20 @@ export function createMarkdownRenderChildWidgetEditorPlugin(plugin: MetaBindPlug
4747
* @param view
4848
*/
4949
updateWidgets(view: EditorView): void {
50-
// remove all decorations that are not in the viewport and call unload manually
50+
// remove all decorations that are not visible and call unload manually
5151
this.decorations = this.decorations.update({
5252
filter: (fromA, toA, decoration) => {
5353
const inVisibleRange = summary.anyMatch(view.visibleRanges, range =>
5454
Cm6_Util.checkRangeOverlap(fromA, toA, range.from, range.to),
5555
);
5656

57-
if (!inVisibleRange) {
58-
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
59-
decoration.spec.mb_unload?.();
57+
if (inVisibleRange) {
58+
return true;
6059
}
61-
return inVisibleRange;
60+
61+
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
62+
decoration.spec.mb_unload?.();
63+
return false;
6264
},
6365
});
6466

@@ -105,19 +107,25 @@ export function createMarkdownRenderChildWidgetEditorPlugin(plugin: MetaBindPlug
105107
* Removes all decorations at a given node.
106108
*
107109
* @param node
108-
* @param widgetType if specified, decorations of this type are kept
110+
* @param widgetTypeToKeep if specified, decorations of this type are kept
109111
*/
110-
removeDecoration(node: SyntaxNode, widgetType?: MB_WidgetType): void {
112+
removeDecoration(node: SyntaxNode, widgetTypeToKeep?: MB_WidgetType): void {
111113
this.decorations.between(node.from - 1, node.to + 1, (from, to, _) => {
112114
this.decorations = this.decorations.update({
113115
filterFrom: from,
114116
filterTo: to,
115117
filter: (_from, _to, decoration) => {
116-
if (widgetType) {
117-
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
118-
return decoration.spec.mb_widgetType === widgetType;
118+
if (widgetTypeToKeep) {
119+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment
120+
const widgetType = decoration.spec.mb_widgetType;
121+
122+
if (widgetType === widgetTypeToKeep) {
123+
return true;
124+
}
119125
}
120126

127+
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
128+
decoration.spec.mb_unload?.();
121129
return false;
122130
},
123131
});
@@ -202,7 +210,8 @@ export function createMarkdownRenderChildWidgetEditorPlugin(plugin: MetaBindPlug
202210

203211
return {
204212
shouldRender: !hasSelectionOverlap && isLivePreview,
205-
shouldHighlight: hasSelectionOverlap || !isLivePreview,
213+
shouldHighlight:
214+
(hasSelectionOverlap || !isLivePreview) && plugin.settings.enableSyntaxHighlighting,
206215
content: content.content,
207216
widgetType: content.widgetType,
208217
};
@@ -324,7 +333,7 @@ export function createMarkdownRenderChildWidgetEditorPlugin(plugin: MetaBindPlug
324333
},
325334
}).range(node.from - 1, node.to + 1);
326335
} else {
327-
const highlight = plugin.api.syntaxHighlighting.highlight(content, mdrcType);
336+
const highlight = plugin.api.syntaxHighlighting.highlight(content, mdrcType, false);
328337

329338
return highlight.getHighlights().map(h => {
330339
// console.log(h);

0 commit comments

Comments
 (0)