UNPKG

@arcgis/coding-components

Version:

Contains components for editing code in different languages. The currently supported languages are html, css, json, TypeScript, JavaScript, and Arcade.

542 lines (541 loc) 16.9 kB
import { Range as x, languages as l, MarkerSeverity as p, Emitter as L } from "monaco-editor"; import "monaco-editor/esm/vs/editor/standalone/browser/standaloneServices.js"; import "monaco-editor/esm/vs/editor/standalone/common/standaloneTheme.js"; import "monaco-editor/esm/vs/editor/common/languages/supports/tokenization.js"; import { ArcadeKeywords as M } from "@arcgis/arcade-languageservice"; import { i as k } from "./monaco-importer.js"; import { InsertTextFormat as y, DiagnosticSeverity as m, CompletionItemKind as o } from "vscode-languageserver-types"; import { debounce as F, Deferred as _ } from "@arcgis/components-utils"; /*! All material copyright Esri, All Rights Reserved, unless otherwise specified. See https://js.arcgis.com/4.32/esri/copyright.txt for details. v4.32.14 */ const S = [ "<=", ">=", "==", "!=", "+", "-", "*", "/", "%", "++", "--", "<<", ">>", ">>>", "&", "|", "^", "!", "~", "&&", "||", "=", "+=", "-=", "*=", "**=", "/=", "%=" ], T = { // the default separators except `@$` wordPattern: /(-?\d*\.\d\w*)|([^`~!#%\^&\*\(\)\-=\+\[\{\]\}\\\|;:'",\.<>\/\?\s]+)/gu, comments: { lineComment: "//", blockComment: ["/*", "*/"] }, brackets: [ ["{", "}"], ["[", "]"], ["(", ")"] ], autoClosingPairs: [ { open: "{", close: "}" }, { open: "[", close: "]" }, { open: "(", close: ")" }, { open: '"', close: '"', notIn: ["string"] }, { open: "'", close: "'", notIn: ["string", "comment"] }, { open: "`", close: "`", notIn: ["string", "comment"] } ], autoCloseBefore: ";:.,=}])` \n ", folding: { markers: { start: /^\s*\/\/\s*#?region\b/u, end: /^\s*\/\/\s*#?endregion\b/u } }, indentationRules: { // ^(.*\*/)?\s*\}.*$ decreaseIndentPattern: /^((?!.*?\/\*).*\*\/)?\s*[\}\]\)].*$/u, // ^.*\{[^}"']*$ increaseIndentPattern: /^((?!\/\/).)*(\{[^}"'`]*|\([^)"'`]*|\[[^\]"'`]*)$/u } }, A = { // Set defaultToken to invalid to see what you do not tokenize yet defaultToken: "invalid", tokenPostfix: ".arcgis", // Arcade is case insensitive ignoreCase: !0, // builtinFunctions: [...arcadeService.FunctionNames], // Arcade keywords. 'from' is a special case as we want to treat it as a keyword in // import statement but as an identifier in var statement. keywords: M, operators: S, constants: ["true", "false", "null"], // we include these common regular expressions symbols: /[=><!~?:&|+\-*\/\^%]+/u, escapes: /\\(?:[abfnrtv\\"']|x[0-9A-Fa-f]{1,4}|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})/u, digits: /\d+(_+\d+)*/u, octaldigits: /[0-7]+(_+[0-7]+)*/u, binarydigits: /[0-1]+(_+[0-1]+)*/u, hexdigits: /[[0-9a-fA-F]+(_+[0-9a-fA-F]+)*/u, regexpctl: /[(){}\[\]\$\^|\-*+?\.]/u, regexpesc: /\\(?:[bBdDfnrstvwWn0\\\/]|@regexpctl|c[A-Z]|x[0-9a-fA-F]{2}|u[0-9a-fA-F]{4})/u, // The main tokenizer for our languages tokenizer: { root: [[/[{}]/u, "delimiter.bracket"], { include: "common" }], common: [ // import statement. import followed by a white space [/import(?=\s)/u, { token: "keyword", next: "@import" }], // Special handling for `for` keyword [/for(?=\s)/u, { token: "keyword", next: "@forLoopInit" }], // identifiers and keywords [ /[a-z_$][\w$]*/u, { cases: { "@constants": "constant", "@keywords": "keyword", "@default": "identifier" } } ], // whitespace { include: "@whitespace" }, // regular expression: ensure it is terminated before beginning (otherwise it is an operator) [ /\/(?=([^\\\/]|\\.)+\/([gimsuy]*)(\s*)(\.|;|\/|,|\)|\]|\}|$))/u, { token: "regexp", bracket: "@open", next: "@regexp" } ], // delimiters and operators [/[()\[\]]/u, "@brackets"], [/[<>](?!@symbols)/u, "@brackets"], [ /@symbols/u, { cases: { "@operators": "delimiter", "@default": "" } } ], // numbers [/(@digits)[eE]([\-+]?(@digits))?/u, "number"], [/(@digits)\.(@digits)([eE][\-+]?(@digits))?/u, "number"], [/0[xX](@hexdigits)/u, "number"], [/0[oO]?(@octaldigits)/u, "number"], [/0[bB](@binarydigits)/u, "number"], [/(@digits)/u, "number"], // delimiter: after number because of .\d floats [/[;,.]/u, "delimiter"], // strings [/"([^"\\]|\\.)*$/u, "string.invalid"], // non-terminated string [/'([^'\\]|\\.)*$/u, "string.invalid"], // non-terminated string [/"/u, "string", "@string_double"], [/'/u, "string", "@string_single"], [/`/u, "string", "@string_backtick"] ], import: [ // import keyword [/import(?=\s)/u, { token: "keyword" }], // whitespace { include: "@whitespace" }, // identifier [/[a-z_$][\w$]*/u, "identifier"], // whitespace { include: "@whitespace" }, // from keyword [/from/u, { token: "keyword", next: "@popall" }] ], // State after `for` keyword: expects '(' forLoopInit: [ { include: "@whitespace" }, [/\(/u, { token: "@brackets", next: "@forLoopVar" }], // If we don't get an '(', just pop back [/.*/u, "", "@pop"] ], // In a for loop after '(' we expect either var/let/const or directly an identifier forLoopVar: [ { include: "@whitespace" }, [/var(?=\s)/u, "keyword", "@forLoopVarName"], [/[a-z_$][\w$]*/u, "identifier", "@forLoopCheckInOf"], [/\)/u, { token: "@brackets", next: "@popall" }] // close the for(...) ], forLoopVarName: [ { include: "@whitespace" }, // The next thing after var/let/const should be an identifier [/[a-z_$][\w$]*/u, "identifier", "@forLoopCheckInOf"], [/\)/u, { token: "@brackets", next: "@popall" }] ], // After we have a variable name, we check if we have `in` or `of` forLoopCheckInOf: [ { include: "@whitespace" }, [/in(?=\s)/u, "keyword", "@popall"], // for-in [/of(?=\s)/u, "keyword", "@popall"], // for-of is highlighted here // If something else occurs, just highlight normally [/[a-z_$][\w$]*/u, "identifier"], [/\)/u, { token: "@brackets", next: "@popall" }] ], whitespace: [ [/[ \t\r\n]+/u, ""], [/\/\*/u, "comment", "@comment"], [/\/\/.*$/u, "comment"] ], comment: [ [/[^\/*]+/u, "comment"], [/\*\//u, "comment", "@pop"], [/[\/*]/u, "comment"] ], // We match regular expression quite precisely regexp: [ [/(\{)(\d+(?:,\d*)?)(\})/u, ["regexp.escape.control", "regexp.escape.control", "regexp.escape.control"]], [ /(\[)(\^?)(?=(?:[^\]\\\/]|\\.)+)/u, ["regexp.escape.control", { token: "regexp.escape.control", next: "@regexrange" }] ], [/(\()(\?:|\?=|\?!)/u, ["regexp.escape.control", "regexp.escape.control"]], [/[()]/u, "regexp.escape.control"], [/@regexpctl/u, "regexp.escape.control"], [/[^\\\/]/u, "regexp"], [/@regexpesc/u, "regexp.escape"], [/\\\./u, "regexp.invalid"], [/(\/)([gimsuy]*)/u, [{ token: "regexp", bracket: "@close", next: "@pop" }, "keyword.other"]] ], regexrange: [ [/-/u, "regexp.escape.control"], [/\^/u, "regexp.invalid"], [/@regexpesc/u, "regexp.escape"], [/[^\]]/u, "regexp"], [/\]/u, { token: "regexp.escape.control", next: "@pop", bracket: "@close" }] ], string_double: [ [/[^\\"]+/u, "string"], [/@escapes/u, "string.escape"], [/\\./u, "string.escape.invalid"], [/"/u, "string", "@pop"] ], string_single: [ [/[^\\']+/u, "string"], [/@escapes/u, "string.escape"], [/\\./u, "string.escape.invalid"], [/'/u, "string", "@pop"] ], string_backtick: [ [/\$\{/u, { token: "delimiter.bracket", next: "@bracketCounting" }], [/[^\\`$]+/u, "string"], [/@escapes/u, "string.escape"], [/\\./u, "string.escape.invalid"], [/`/u, "string", "@pop"] ], bracketCounting: [ [/\{/u, "delimiter.bracket", "@bracketCounting"], [/\}/u, "delimiter.bracket", "@pop"], { include: "common" } ] } }; class P { constructor(e, n, { defaults: c, diagnosticsService: g }) { this._languageId = e, this._worker = n, this._disposables = [], this._modelListeners = /* @__PURE__ */ new Map(), this._diagnosticsService = g, this._defaults = c, k().then((s) => { const u = (i) => { const r = i.getLanguageId(); if (r !== this._languageId) return; const a = F(() => { this._doValidate(i, r).catch((E) => { throw E; }); }), I = i.onDidChangeContent(a), v = i.onDidChangeAttached(a); this._modelListeners.set(i.uri.toString(), [I, v]), this._doValidate(i, r).catch(console.error); }, d = (i) => { const r = i.uri.toString(); s.setModelMarkers(i, this._languageId, []); const a = this._modelListeners.get(r); if (a) { for (; a.length; ) a.pop()?.dispose(); this._modelListeners.delete(r); } }; this._disposables.push(s.onDidCreateModel(u)), this._disposables.push( s.onWillDisposeModel((i) => { d(i); }) ), this._disposables.push( s.onDidChangeModelLanguage((i) => { d(i.model), u(i.model); }) ), this._disposables.push( c.onDidChange(() => { s.getModels().forEach((i) => { i.getLanguageId() === this._languageId && (d(i), u(i)); }); }) ), this._disposables.push( c.onModelContextDidChange((i) => { s.getModels().forEach((r) => { r.getLanguageId() === this._languageId && r.uri.toString() === i && this._doValidate(r, this._languageId).catch(console.error); }); }) ), this._disposables.push({ dispose: () => { this._modelListeners.forEach((i) => i.forEach((r) => r.dispose())), this._modelListeners.clear(); } }), s.getModels().forEach(u); }); } dispose() { this._disposables.forEach((e) => e.dispose()), this._disposables = []; } async _doValidate(e, n) { if (e.isAttachedToEditor()) try { const c = await k(), g = await this._worker(e.uri), s = this._defaults.getApiContextForModel(e.uri), u = await g.doValidation(e.uri.toString(), s), d = u.map((i) => $(e.uri, i)); this._diagnosticsService.fireDiagnosticsChange(e.uri, u), c.setModelMarkers(e, n, d); } catch (c) { console.error(c); } } } function W(t) { switch (t) { case m.Error: return p.Error; case m.Warning: return p.Warning; case m.Information: return p.Info; case m.Hint: return p.Hint; default: return p.Info; } } function $(t, e) { return { severity: W(e.severity), startLineNumber: e.range.start.line + 1, startColumn: e.range.start.character + 1, endLineNumber: e.range.end.line + 1, endColumn: e.range.end.character + 1, message: e.message }; } function D(t) { return { character: t.column - 1, line: t.lineNumber - 1 }; } function h(t) { return new x(t.start.line + 1, t.start.character + 1, t.end.line + 1, t.end.character + 1); } function V(t) { return { range: h(t.range), text: t.newText }; } function R(t) { return typeof t.insert < "u" && typeof t.replace < "u"; } function K(t) { const e = l.CompletionItemKind; switch (t) { case o.Text: return e.Text; case o.Method: return e.Method; case o.Function: return e.Function; case o.Constructor: return e.Constructor; case o.Field: return e.Field; case o.Variable: return e.Variable; case o.Class: return e.Class; case o.Interface: return e.Interface; case o.Module: return e.Module; case o.Property: return e.Property; case o.Unit: return e.Unit; case o.Value: return e.Value; case o.Enum: return e.Enum; case o.Keyword: return e.Keyword; case o.Snippet: return e.Snippet; case o.Color: return e.Color; case o.File: return e.File; case o.Reference: return e.Reference; case o.Folder: return e.Folder; case o.EnumMember: return e.EnumMember; case o.Constant: return e.Constant; case o.Struct: return e.Struct; case o.Event: return e.Event; case o.Operator: return e.Operator; case o.TypeParameter: return e.TypeParameter; default: return e.Property; } } class N { constructor(e, n) { this._worker = e, this._defaults = n, this.triggerCharacters = [".", "("]; } async provideCompletionItems(e, n) { const c = await this._worker(e.uri), g = this._defaults.getApiContextForModel(e.uri), s = await c.doComplete(e.uri.toString(), D(n), g), u = e.getWordUntilPosition(n), d = new x(n.lineNumber, u.startColumn, n.lineNumber, u.endColumn), i = s.items.map((r) => { const a = { label: r.label, insertText: r.insertText || r.label, sortText: r.sortText, filterText: r.filterText, detail: r.detail, range: d, kind: K(r.kind) }; return r.textEdit && (R(r.textEdit) ? a.range = { insert: h(r.textEdit.insert), replace: h(r.textEdit.replace) } : a.range = h(r.textEdit.range), a.insertText = r.textEdit.newText), r.additionalTextEdits && (a.additionalTextEdits = r.additionalTextEdits.map(V)), r.insertTextFormat === y.Snippet && (a.insertTextRules = l.CompletionItemInsertTextRule.InsertAsSnippet), r.documentation && (typeof r.documentation == "string" ? a.documentation = r.documentation : a.documentation = { supportThemeIcons: !1, value: r.documentation.value, supportHtml: !0 }), a; }); return { incomplete: s.isIncomplete, suggestions: i }; } } class z { constructor(e, n) { this._worker = e, this._defaults = n; } async provideDocumentFormattingEdits(e) { const n = await this._worker(e.uri), c = this._defaults.getApiContextForModel(e.uri.toString()); return (await n.doFormat(e.uri.toString(), c)).map((s) => ({ range: h(s.range), text: s.newText })); } } let f = new _(); class b { constructor(e) { this._defaults = e, this._worker = null, this._client = null, this._configChangeListener = this._defaults.onDidChange(() => this.stopWorker()); } dispose() { this._configChangeListener.dispose(), this.stopWorker(); } stopWorker() { this._worker && (this._worker.dispose(), this._worker = null, f = new _()), this._client = null; } /** * Wait for the worker to be ready. * @returns A promise that resolves when the worker is ready. */ static async waitForWorker() { return await f.promise; } async _getClientProxy() { const e = await k(); if (!this._client) { const { languageId: n } = this._defaults; this._worker = e.createWebWorker({ moduleId: "ArcadeWorker", label: n, createData: { languageId: n }, host: this._defaults.workerHost }), f.resolve(this._worker), this._client = this._worker.getProxy(); } return await this._client; } async getLanguageServiceWorker(...e) { const n = await this._getClientProxy(); return await this._worker?.withSyncedResources(e), n; } } let w; async function J(...t) { return await b.waitForWorker(), await new Promise((e, n) => { if (!w) { n(new Error("Arcade not registered!")); return; } e(w(...t)); }); } class O { constructor() { this._onDiagnosticsChange = new L(); } /** * An event to signal changes to the diagnostics. * The event value is the uri string and the diagnostics. */ get onDiagnosticsChange() { return this._onDiagnosticsChange.event; } /** * Fires the diagnostics change event. * @param uri The uri of the model for which the diagnostics changed. * @param diagnostics The diagnostics for the model. */ fireDiagnosticsChange(e, n) { this._onDiagnosticsChange.fire({ uri: e, diagnostics: n }); } } const C = new O(); function Q() { return C; } function Y(t) { const e = new b(t), n = async (...c) => await e.getLanguageServiceWorker(...c); w = n, l.setMonarchTokensProvider(t.languageId, A), l.setLanguageConfiguration(t.languageId, T), l.registerCompletionItemProvider( t.languageId, new N(n, t) ), l.registerDocumentFormattingEditProvider( t.languageId, new z(n, t) ), new P(t.languageId, n, { defaults: t, diagnosticsService: C }); } export { Q as getArcadeDiagnosticService, J as getArcadeWorker, Y as setupMode };