Diffdown is a real-time collaborative Markdown editor/previewer built on the AT Protocol
diffdown.com
1'use strict';
2
3var javascript$1 = require('@lezer/javascript');
4var language = require('@codemirror/language');
5var state = require('@codemirror/state');
6var view = require('@codemirror/view');
7var autocomplete = require('@codemirror/autocomplete');
8var common = require('@lezer/common');
9
10/**
11A collection of JavaScript-related
12[snippets](https://codemirror.net/6/docs/ref/#autocomplete.snippet).
13*/
14const snippets = [
15 autocomplete.snippetCompletion("function ${name}(${params}) {\n\t${}\n}", {
16 label: "function",
17 detail: "definition",
18 type: "keyword"
19 }),
20 autocomplete.snippetCompletion("for (let ${index} = 0; ${index} < ${bound}; ${index}++) {\n\t${}\n}", {
21 label: "for",
22 detail: "loop",
23 type: "keyword"
24 }),
25 autocomplete.snippetCompletion("for (let ${name} of ${collection}) {\n\t${}\n}", {
26 label: "for",
27 detail: "of loop",
28 type: "keyword"
29 }),
30 autocomplete.snippetCompletion("do {\n\t${}\n} while (${})", {
31 label: "do",
32 detail: "loop",
33 type: "keyword"
34 }),
35 autocomplete.snippetCompletion("while (${}) {\n\t${}\n}", {
36 label: "while",
37 detail: "loop",
38 type: "keyword"
39 }),
40 autocomplete.snippetCompletion("try {\n\t${}\n} catch (${error}) {\n\t${}\n}", {
41 label: "try",
42 detail: "/ catch block",
43 type: "keyword"
44 }),
45 autocomplete.snippetCompletion("if (${}) {\n\t${}\n}", {
46 label: "if",
47 detail: "block",
48 type: "keyword"
49 }),
50 autocomplete.snippetCompletion("if (${}) {\n\t${}\n} else {\n\t${}\n}", {
51 label: "if",
52 detail: "/ else block",
53 type: "keyword"
54 }),
55 autocomplete.snippetCompletion("class ${name} {\n\tconstructor(${params}) {\n\t\t${}\n\t}\n}", {
56 label: "class",
57 detail: "definition",
58 type: "keyword"
59 }),
60 autocomplete.snippetCompletion("import {${names}} from \"${module}\"\n${}", {
61 label: "import",
62 detail: "named",
63 type: "keyword"
64 }),
65 autocomplete.snippetCompletion("import ${name} from \"${module}\"\n${}", {
66 label: "import",
67 detail: "default",
68 type: "keyword"
69 })
70];
71/**
72A collection of snippet completions for TypeScript. Includes the
73JavaScript [snippets](https://codemirror.net/6/docs/ref/#lang-javascript.snippets).
74*/
75const typescriptSnippets = snippets.concat([
76 autocomplete.snippetCompletion("interface ${name} {\n\t${}\n}", {
77 label: "interface",
78 detail: "definition",
79 type: "keyword"
80 }),
81 autocomplete.snippetCompletion("type ${name} = ${type}", {
82 label: "type",
83 detail: "definition",
84 type: "keyword"
85 }),
86 autocomplete.snippetCompletion("enum ${name} {\n\t${}\n}", {
87 label: "enum",
88 detail: "definition",
89 type: "keyword"
90 })
91]);
92
93const cache = new common.NodeWeakMap();
94const ScopeNodes = new Set([
95 "Script", "Block",
96 "FunctionExpression", "FunctionDeclaration", "ArrowFunction", "MethodDeclaration",
97 "ForStatement"
98]);
99function defID(type) {
100 return (node, def) => {
101 let id = node.node.getChild("VariableDefinition");
102 if (id)
103 def(id, type);
104 return true;
105 };
106}
107const functionContext = ["FunctionDeclaration"];
108const gatherCompletions = {
109 FunctionDeclaration: defID("function"),
110 ClassDeclaration: defID("class"),
111 ClassExpression: () => true,
112 EnumDeclaration: defID("constant"),
113 TypeAliasDeclaration: defID("type"),
114 NamespaceDeclaration: defID("namespace"),
115 VariableDefinition(node, def) { if (!node.matchContext(functionContext))
116 def(node, "variable"); },
117 TypeDefinition(node, def) { def(node, "type"); },
118 __proto__: null
119};
120function getScope(doc, node) {
121 let cached = cache.get(node);
122 if (cached)
123 return cached;
124 let completions = [], top = true;
125 function def(node, type) {
126 let name = doc.sliceString(node.from, node.to);
127 completions.push({ label: name, type });
128 }
129 node.cursor(common.IterMode.IncludeAnonymous).iterate(node => {
130 if (top) {
131 top = false;
132 }
133 else if (node.name) {
134 let gather = gatherCompletions[node.name];
135 if (gather && gather(node, def) || ScopeNodes.has(node.name))
136 return false;
137 }
138 else if (node.to - node.from > 8192) {
139 // Allow caching for bigger internal nodes
140 for (let c of getScope(doc, node.node))
141 completions.push(c);
142 return false;
143 }
144 });
145 cache.set(node, completions);
146 return completions;
147}
148const Identifier = /^[\w$\xa1-\uffff][\w$\d\xa1-\uffff]*$/;
149const dontComplete = [
150 "TemplateString", "String", "RegExp",
151 "LineComment", "BlockComment",
152 "VariableDefinition", "TypeDefinition", "Label",
153 "PropertyDefinition", "PropertyName",
154 "PrivatePropertyDefinition", "PrivatePropertyName",
155 "JSXText", "JSXAttributeValue", "JSXOpenTag", "JSXCloseTag", "JSXSelfClosingTag",
156 ".", "?."
157];
158/**
159Completion source that looks up locally defined names in
160JavaScript code.
161*/
162function localCompletionSource(context) {
163 let inner = language.syntaxTree(context.state).resolveInner(context.pos, -1);
164 if (dontComplete.indexOf(inner.name) > -1)
165 return null;
166 let isWord = inner.name == "VariableName" ||
167 inner.to - inner.from < 20 && Identifier.test(context.state.sliceDoc(inner.from, inner.to));
168 if (!isWord && !context.explicit)
169 return null;
170 let options = [];
171 for (let pos = inner; pos; pos = pos.parent) {
172 if (ScopeNodes.has(pos.name))
173 options = options.concat(getScope(context.state.doc, pos));
174 }
175 return {
176 options,
177 from: isWord ? inner.from : context.pos,
178 validFor: Identifier
179 };
180}
181function pathFor(read, member, name) {
182 var _a;
183 let path = [];
184 for (;;) {
185 let obj = member.firstChild, prop;
186 if ((obj === null || obj === void 0 ? void 0 : obj.name) == "VariableName") {
187 path.push(read(obj));
188 return { path: path.reverse(), name };
189 }
190 else if ((obj === null || obj === void 0 ? void 0 : obj.name) == "MemberExpression" && ((_a = (prop = obj.lastChild)) === null || _a === void 0 ? void 0 : _a.name) == "PropertyName") {
191 path.push(read(prop));
192 member = obj;
193 }
194 else {
195 return null;
196 }
197 }
198}
199/**
200Helper function for defining JavaScript completion sources. It
201returns the completable name and object path for a completion
202context, or null if no name/property completion should happen at
203that position. For example, when completing after `a.b.c` it will
204return `{path: ["a", "b"], name: "c"}`. When completing after `x`
205it will return `{path: [], name: "x"}`. When not in a property or
206name, it will return null if `context.explicit` is false, and
207`{path: [], name: ""}` otherwise.
208*/
209function completionPath(context) {
210 let read = (node) => context.state.doc.sliceString(node.from, node.to);
211 let inner = language.syntaxTree(context.state).resolveInner(context.pos, -1);
212 if (inner.name == "PropertyName") {
213 return pathFor(read, inner.parent, read(inner));
214 }
215 else if ((inner.name == "." || inner.name == "?.") && inner.parent.name == "MemberExpression") {
216 return pathFor(read, inner.parent, "");
217 }
218 else if (dontComplete.indexOf(inner.name) > -1) {
219 return null;
220 }
221 else if (inner.name == "VariableName" || inner.to - inner.from < 20 && Identifier.test(read(inner))) {
222 return { path: [], name: read(inner) };
223 }
224 else if (inner.name == "MemberExpression") {
225 return pathFor(read, inner, "");
226 }
227 else {
228 return context.explicit ? { path: [], name: "" } : null;
229 }
230}
231function enumeratePropertyCompletions(obj, top) {
232 let originalObj = obj;
233 let options = [], seen = new Set;
234 for (let depth = 0;; depth++) {
235 for (let name of (Object.getOwnPropertyNames || Object.keys)(obj)) {
236 if (!/^[a-zA-Z_$\xaa-\uffdc][\w$\xaa-\uffdc]*$/.test(name) || seen.has(name))
237 continue;
238 seen.add(name);
239 let value;
240 try {
241 value = originalObj[name];
242 }
243 catch (_) {
244 continue;
245 }
246 options.push({
247 label: name,
248 type: typeof value == "function" ? (/^[A-Z]/.test(name) ? "class" : top ? "function" : "method")
249 : top ? "variable" : "property",
250 boost: -depth
251 });
252 }
253 let next = Object.getPrototypeOf(obj);
254 if (!next)
255 return options;
256 obj = next;
257 }
258}
259/**
260Defines a [completion source](https://codemirror.net/6/docs/ref/#autocomplete.CompletionSource) that
261completes from the given scope object (for example `globalThis`).
262Will enter properties of the object when completing properties on
263a directly-named path.
264*/
265function scopeCompletionSource(scope) {
266 let cache = new Map;
267 return (context) => {
268 let path = completionPath(context);
269 if (!path)
270 return null;
271 let target = scope;
272 for (let step of path.path) {
273 target = target[step];
274 if (!target)
275 return null;
276 }
277 let options = cache.get(target);
278 if (!options)
279 cache.set(target, options = enumeratePropertyCompletions(target, !path.path.length));
280 return {
281 from: context.pos - path.name.length,
282 options,
283 validFor: Identifier
284 };
285 };
286}
287
288/**
289A language provider based on the [Lezer JavaScript
290parser](https://github.com/lezer-parser/javascript), extended with
291highlighting and indentation information.
292*/
293const javascriptLanguage = language.LRLanguage.define({
294 name: "javascript",
295 parser: javascript$1.parser.configure({
296 props: [
297 language.indentNodeProp.add({
298 IfStatement: language.continuedIndent({ except: /^\s*({|else\b)/ }),
299 TryStatement: language.continuedIndent({ except: /^\s*({|catch\b|finally\b)/ }),
300 LabeledStatement: language.flatIndent,
301 SwitchBody: context => {
302 let after = context.textAfter, closed = /^\s*\}/.test(after), isCase = /^\s*(case|default)\b/.test(after);
303 return context.baseIndent + (closed ? 0 : isCase ? 1 : 2) * context.unit;
304 },
305 Block: language.delimitedIndent({ closing: "}" }),
306 ArrowFunction: cx => cx.baseIndent + cx.unit,
307 "TemplateString BlockComment": () => null,
308 "Statement Property": language.continuedIndent({ except: /^\s*{/ }),
309 JSXElement(context) {
310 let closed = /^\s*<\//.test(context.textAfter);
311 return context.lineIndent(context.node.from) + (closed ? 0 : context.unit);
312 },
313 JSXEscape(context) {
314 let closed = /\s*\}/.test(context.textAfter);
315 return context.lineIndent(context.node.from) + (closed ? 0 : context.unit);
316 },
317 "JSXOpenTag JSXSelfClosingTag"(context) {
318 return context.column(context.node.from) + context.unit;
319 }
320 }),
321 language.foldNodeProp.add({
322 "Block ClassBody SwitchBody EnumBody ObjectExpression ArrayExpression ObjectType": language.foldInside,
323 BlockComment(tree) { return { from: tree.from + 2, to: tree.to - 2 }; },
324 JSXElement(tree) {
325 let open = tree.firstChild;
326 if (!open || open.name == "JSXSelfClosingTag")
327 return null;
328 let close = tree.lastChild;
329 return { from: open.to, to: close.type.isError ? tree.to : close.from };
330 },
331 "JSXSelfClosingTag JSXOpenTag"(tree) {
332 var _a;
333 let name = (_a = tree.firstChild) === null || _a === void 0 ? void 0 : _a.nextSibling, close = tree.lastChild;
334 if (!name || name.type.isError)
335 return null;
336 return { from: name.to, to: close.type.isError ? tree.to : close.from };
337 }
338 })
339 ]
340 }),
341 languageData: {
342 closeBrackets: { brackets: ["(", "[", "{", "'", '"', "`"] },
343 commentTokens: { line: "//", block: { open: "/*", close: "*/" } },
344 indentOnInput: /^\s*(?:case |default:|\{|\}|<\/)$/,
345 wordChars: "$"
346 }
347});
348const jsxSublanguage = {
349 test: node => /^JSX/.test(node.name),
350 facet: language.defineLanguageFacet({ commentTokens: { block: { open: "{/*", close: "*/}" } } })
351};
352/**
353A language provider for TypeScript.
354*/
355const typescriptLanguage = javascriptLanguage.configure({ dialect: "ts" }, "typescript");
356/**
357Language provider for JSX.
358*/
359const jsxLanguage = javascriptLanguage.configure({
360 dialect: "jsx",
361 props: [language.sublanguageProp.add(n => n.isTop ? [jsxSublanguage] : undefined)]
362});
363/**
364Language provider for JSX + TypeScript.
365*/
366const tsxLanguage = javascriptLanguage.configure({
367 dialect: "jsx ts",
368 props: [language.sublanguageProp.add(n => n.isTop ? [jsxSublanguage] : undefined)]
369}, "typescript");
370let kwCompletion = (name) => ({ label: name, type: "keyword" });
371const keywords = "break case const continue default delete export extends false finally in instanceof let new return static super switch this throw true typeof var yield".split(" ").map(kwCompletion);
372const typescriptKeywords = keywords.concat(["declare", "implements", "private", "protected", "public"].map(kwCompletion));
373/**
374JavaScript support. Includes [snippet](https://codemirror.net/6/docs/ref/#lang-javascript.snippets)
375and local variable completion.
376*/
377function javascript(config = {}) {
378 let lang = config.jsx ? (config.typescript ? tsxLanguage : jsxLanguage)
379 : config.typescript ? typescriptLanguage : javascriptLanguage;
380 let completions = config.typescript ? typescriptSnippets.concat(typescriptKeywords) : snippets.concat(keywords);
381 return new language.LanguageSupport(lang, [
382 javascriptLanguage.data.of({
383 autocomplete: autocomplete.ifNotIn(dontComplete, autocomplete.completeFromList(completions))
384 }),
385 javascriptLanguage.data.of({
386 autocomplete: localCompletionSource
387 }),
388 config.jsx ? autoCloseTags : [],
389 ]);
390}
391function findOpenTag(node) {
392 for (;;) {
393 if (node.name == "JSXOpenTag" || node.name == "JSXSelfClosingTag" || node.name == "JSXFragmentTag")
394 return node;
395 if (node.name == "JSXEscape" || !node.parent)
396 return null;
397 node = node.parent;
398 }
399}
400function elementName(doc, tree, max = doc.length) {
401 for (let ch = tree === null || tree === void 0 ? void 0 : tree.firstChild; ch; ch = ch.nextSibling) {
402 if (ch.name == "JSXIdentifier" || ch.name == "JSXBuiltin" || ch.name == "JSXNamespacedName" ||
403 ch.name == "JSXMemberExpression")
404 return doc.sliceString(ch.from, Math.min(ch.to, max));
405 }
406 return "";
407}
408const android = typeof navigator == "object" && /Android\b/.test(navigator.userAgent);
409/**
410Extension that will automatically insert JSX close tags when a `>` or
411`/` is typed.
412*/
413const autoCloseTags = view.EditorView.inputHandler.of((view, from, to, text, defaultInsert) => {
414 if ((android ? view.composing : view.compositionStarted) || view.state.readOnly ||
415 from != to || (text != ">" && text != "/") ||
416 !javascriptLanguage.isActiveAt(view.state, from, -1))
417 return false;
418 let base = defaultInsert(), { state: state$1 } = base;
419 let closeTags = state$1.changeByRange(range => {
420 var _a;
421 let { head } = range, around = language.syntaxTree(state$1).resolveInner(head - 1, -1), name;
422 if (around.name == "JSXStartTag")
423 around = around.parent;
424 if (state$1.doc.sliceString(head - 1, head) != text || around.name == "JSXAttributeValue" && around.to > head) ;
425 else if (text == ">" && around.name == "JSXFragmentTag") {
426 return { range, changes: { from: head, insert: `</>` } };
427 }
428 else if (text == "/" && around.name == "JSXStartCloseTag") {
429 let empty = around.parent, base = empty.parent;
430 if (base && empty.from == head - 2 &&
431 ((name = elementName(state$1.doc, base.firstChild, head)) || ((_a = base.firstChild) === null || _a === void 0 ? void 0 : _a.name) == "JSXFragmentTag")) {
432 let insert = `${name}>`;
433 return { range: state.EditorSelection.cursor(head + insert.length, -1), changes: { from: head, insert } };
434 }
435 }
436 else if (text == ">") {
437 let openTag = findOpenTag(around);
438 if (openTag && openTag.name == "JSXOpenTag" &&
439 !/^\/?>|^<\//.test(state$1.doc.sliceString(head, head + 2)) &&
440 (name = elementName(state$1.doc, openTag, head)))
441 return { range, changes: { from: head, insert: `</${name}>` } };
442 }
443 return { range };
444 });
445 if (closeTags.changes.empty)
446 return false;
447 view.dispatch([
448 base,
449 state$1.update(closeTags, { userEvent: "input.complete", scrollIntoView: true })
450 ]);
451 return true;
452});
453
454/**
455Connects an [ESLint](https://eslint.org/) linter to CodeMirror's
456[lint](https://codemirror.net/6/docs/ref/#lint) integration. `eslint` should be an instance of the
457[`Linter`](https://eslint.org/docs/developer-guide/nodejs-api#linter)
458class, and `config` an optional ESLint configuration. The return
459value of this function can be passed to [`linter`](https://codemirror.net/6/docs/ref/#lint.linter)
460to create a JavaScript linting extension.
461
462Note that ESLint targets node, and is tricky to run in the
463browser. The
464[eslint-linter-browserify](https://github.com/UziTech/eslint-linter-browserify)
465package may help with that (see
466[example](https://github.com/UziTech/eslint-linter-browserify/blob/master/example/script.js)).
467*/
468function esLint(eslint, config) {
469 if (!config) {
470 config = {
471 parserOptions: { ecmaVersion: 2019, sourceType: "module" },
472 env: { browser: true, node: true, es6: true, es2015: true, es2017: true, es2020: true },
473 rules: {}
474 };
475 eslint.getRules().forEach((desc, name) => {
476 var _a;
477 if ((_a = desc.meta.docs) === null || _a === void 0 ? void 0 : _a.recommended)
478 config.rules[name] = 2;
479 });
480 }
481 return (view) => {
482 let { state } = view, found = [];
483 for (let { from, to } of javascriptLanguage.findRegions(state)) {
484 let fromLine = state.doc.lineAt(from), offset = { line: fromLine.number - 1, col: from - fromLine.from, pos: from };
485 for (let d of eslint.verify(state.sliceDoc(from, to), config))
486 found.push(translateDiagnostic(d, state.doc, offset));
487 }
488 return found;
489 };
490}
491function mapPos(line, col, doc, offset) {
492 return doc.line(line + offset.line).from + col + (line == 1 ? offset.col - 1 : -1);
493}
494function translateDiagnostic(input, doc, offset) {
495 let start = mapPos(input.line, input.column, doc, offset);
496 let result = {
497 from: start,
498 to: input.endLine != null && input.endColumn != 1 ? mapPos(input.endLine, input.endColumn, doc, offset) : start,
499 message: input.message,
500 source: input.ruleId ? "eslint:" + input.ruleId : "eslint",
501 severity: input.severity == 1 ? "warning" : "error",
502 };
503 if (input.fix) {
504 let { range, text } = input.fix, from = range[0] + offset.pos - start, to = range[1] + offset.pos - start;
505 result.actions = [{
506 name: "fix",
507 apply(view, start) {
508 view.dispatch({ changes: { from: start + from, to: start + to, insert: text }, scrollIntoView: true });
509 }
510 }];
511 }
512 return result;
513}
514
515exports.autoCloseTags = autoCloseTags;
516exports.completionPath = completionPath;
517exports.esLint = esLint;
518exports.javascript = javascript;
519exports.javascriptLanguage = javascriptLanguage;
520exports.jsxLanguage = jsxLanguage;
521exports.localCompletionSource = localCompletionSource;
522exports.scopeCompletionSource = scopeCompletionSource;
523exports.snippets = snippets;
524exports.tsxLanguage = tsxLanguage;
525exports.typescriptLanguage = typescriptLanguage;
526exports.typescriptSnippets = typescriptSnippets;