@kurrawongai/codemirror-lang-turtle12 0.2.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/index.ts CHANGED
@@ -14,10 +14,13 @@ import { parser as rawParser } from './turtle.grammar';
14
14
  import { turtleHighlighting } from './highlight';
15
15
  import { turtleCompletionSource } from './complete';
16
16
  import type { CompletionOptions } from './complete';
17
+ import { parseErrorLinter } from './lint';
17
18
 
18
19
  export type { CompletionOptions, PrefixMap, PrefixSource } from './complete';
19
20
  export { documentPrefixes, knownPrefixes, turtleCompletionSource } from './complete';
20
21
  export { turtleHighlighting } from './highlight';
22
+ export { parseErrorLinter, parseErrors } from './lint';
23
+ export type { ParseError } from './lint';
21
24
 
22
25
  /** The unconfigured grammar, for callers that want their own `top` or props. */
23
26
  export const parser: LRParser = rawParser;
@@ -46,7 +49,20 @@ const statementFold = (node: SyntaxNode, state: EditorState) => {
46
49
  };
47
50
 
48
51
  /**
49
- * Highlighting, folding and indentation, shared by all four dialects.
52
+ * Fold an RDF Patch transaction from the end of its `TX .` row to the start of
53
+ * its `TC .` or `TA .` row, so both rows stay visible.
54
+ */
55
+ const transactionFold = (node: SyntaxNode, state: EditorState) => {
56
+ const begin = node.getChild('TxBegin');
57
+ const end = node.getChild('TxCommit') ?? node.getChild('TxAbort');
58
+ if (!begin || !end) return null;
59
+ const from = state.doc.lineAt(begin.to).to;
60
+ const to = state.doc.lineAt(end.from).from - 1;
61
+ return to > from ? { from, to } : null;
62
+ };
63
+
64
+ /**
65
+ * Highlighting, folding and indentation, shared by all five dialects.
50
66
  *
51
67
  * Exported so a consumer can re-configure the parser with props of their own —
52
68
  * extra `styleTags`, say, to tag the three RDF 1.2 constructs' regions apart
@@ -64,6 +80,7 @@ export const turtleProps = [
64
80
  ReifiedTriple: delimitedIndent({ closing: '>>' }),
65
81
  }),
66
82
  foldNodeProp.add({
83
+ Transaction: transactionFold,
67
84
  BlankNodePropertyList: foldInside,
68
85
  Collection: foldInside,
69
86
  WrappedGraph: foldInside,
@@ -78,9 +95,17 @@ const languageData = {
78
95
  indentOnInput: /^\s*[\]})]$/,
79
96
  };
80
97
 
98
+ const LANGUAGE_NAMES: Record<string, string> = {
99
+ TurtleDoc: 'turtle',
100
+ TrigDoc: 'trig',
101
+ NTriplesDoc: 'ntriples',
102
+ NQuadsDoc: 'nquads',
103
+ RdfPatchDoc: 'rdfpatch',
104
+ };
105
+
81
106
  function define(top: string) {
82
107
  return LRLanguage.define({
83
- name: top === 'TurtleDoc' ? 'turtle' : top === 'TrigDoc' ? 'trig' : top === 'NTriplesDoc' ? 'ntriples' : 'nquads',
108
+ name: LANGUAGE_NAMES[top],
84
109
  parser: rawParser.configure({ top, props: turtleProps }),
85
110
  languageData,
86
111
  });
@@ -90,10 +115,12 @@ export const turtleLanguage = define('TurtleDoc');
90
115
  export const trigLanguage = define('TrigDoc');
91
116
  export const ntriplesLanguage = define('NTriplesDoc');
92
117
  export const nquadsLanguage = define('NQuadsDoc');
118
+ export const rdfPatchLanguage = define('RdfPatchDoc');
93
119
 
94
120
  function support(language: LRLanguage, options: CompletionOptions, dialect: 'turtle' | 'trig') {
95
121
  return new LanguageSupport(language, [
96
122
  language.data.of({ autocomplete: turtleCompletionSource(options, dialect) }),
123
+ parseErrorLinter(),
97
124
  ]);
98
125
  }
99
126
 
@@ -115,10 +142,21 @@ export function trig(options: CompletionOptions = {}): LanguageSupport {
115
142
  * an editor that colours it as valid is lying about the file it will write.
116
143
  */
117
144
  export function ntriples(): LanguageSupport {
118
- return new LanguageSupport(ntriplesLanguage);
145
+ return new LanguageSupport(ntriplesLanguage, parseErrorLinter());
119
146
  }
120
147
 
121
148
  /** RDF 1.2 N-Quads. */
122
149
  export function nquads(): LanguageSupport {
123
- return new LanguageSupport(nquadsLanguage);
150
+ return new LanguageSupport(nquadsLanguage, parseErrorLinter());
151
+ }
152
+
153
+ /**
154
+ * RDF Patch (https://afs.github.io/rdf-delta/rdf-patch.html).
155
+ *
156
+ * Rows take N-Quads terms. `A` and `PA` rows are tagged `inserted`, and `D` and
157
+ * `PD` rows `deleted`, so a style can show a patch as a diff. A transaction
158
+ * folds between its `TX .` and `TC .` rows.
159
+ */
160
+ export function rdfPatch(): LanguageSupport {
161
+ return new LanguageSupport(rdfPatchLanguage, parseErrorLinter());
124
162
  }
package/src/lint.ts ADDED
@@ -0,0 +1,53 @@
1
+ import { ensureSyntaxTree } from '@codemirror/language';
2
+ import { linter } from '@codemirror/lint';
3
+ import type { EditorState, Extension } from '@codemirror/state';
4
+
5
+ export interface ParseError {
6
+ from: number;
7
+ to: number;
8
+ message: string;
9
+ }
10
+
11
+ /**
12
+ * How long to spend completing the parse before reporting errors. A partial
13
+ * tree ends in error nodes that are not in the document, so nothing is
14
+ * reported until the parse reaches the end.
15
+ */
16
+ const PARSE_TIMEOUT_MS = 1000;
17
+
18
+ /** Whitespace and `#` comments: what may separate error nodes in one run. */
19
+ const GAP = /^(?:\s|#.*)*$/;
20
+
21
+ /**
22
+ * Every parse error in the document.
23
+ *
24
+ * The parser recovers by skipping one token at a time, so `this is a test`
25
+ * produces four error nodes. Nodes separated only by whitespace or comments are
26
+ * merged into one error.
27
+ */
28
+ export function parseErrors(state: EditorState): ParseError[] {
29
+ const tree = ensureSyntaxTree(state, state.doc.length, PARSE_TIMEOUT_MS);
30
+ if (!tree) return [];
31
+ const runs: { from: number; to: number }[] = [];
32
+ tree.iterate({
33
+ enter: (node) => {
34
+ if (!node.type.isError) return;
35
+ const last = runs[runs.length - 1];
36
+ if (last && (node.from <= last.to || GAP.test(state.sliceDoc(last.to, node.from))))
37
+ last.to = Math.max(last.to, node.to);
38
+ else runs.push({ from: node.from, to: node.to });
39
+ },
40
+ });
41
+ return runs.map(({ from, to }) => ({ from, to, message: errorMessage(state.sliceDoc(from, to)) }));
42
+ }
43
+
44
+ function errorMessage(text: string): string {
45
+ const shown = text.replace(/\s+/g, ' ').trim();
46
+ if (!shown) return 'Syntax error.';
47
+ return `Unexpected “${shown.length > 32 ? `${shown.slice(0, 32)}…` : shown}”.`;
48
+ }
49
+
50
+ /** Underline every parse error. Included in `turtle()`, `trig()`, `ntriples()`, `nquads()` and `rdfPatch()`. */
51
+ export function parseErrorLinter(): Extension {
52
+ return linter((view) => parseErrors(view.state).map((error) => ({ ...error, severity: 'error' })));
53
+ }
Binary file