UNPKG

@arcgis/coding-components

Version:
579 lines (578 loc) • 17.9 kB
/* COPYRIGHT Esri - https://js.arcgis.com/5.1/LICENSE.txt */ import { i as f } from "./monaco-importer.js"; import { ArcadeKeywords as M } from "@arcgis/arcade-languageservice"; import { InsertTextFormat as L, CompletionItemKind as o, DiagnosticSeverity as h } from "vscode-languageserver-types"; import { debounce as S } from "@arcgis/toolkit/function"; import { rethrowError as w } from "@arcgis/toolkit/log"; import { Range as b, languages as l, MarkerSeverity as p, Emitter as F } from "monaco-editor"; const P = [ "<=", ">=", "==", "!=", "+", "-", "*", "/", "%", "++", "--", "<<", ">>", ">>>", "&", "|", "^", "!", "~", "&&", "||", "=", "+=", "-=", "*=", "**=", "/=", "%=" ], 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 } }, W = { // 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: P, 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(?=\s)/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 $ { constructor(e, r, { defaults: s, diagnosticsService: u }) { this._languageId = e, this._worker = r, this._disposables = [], this._modelListeners = /* @__PURE__ */ new Map(), this._diagnosticsService = u, this._defaults = s, f().then((a) => { const d = (i) => { const n = i.getLanguageId(); if (n !== this._languageId) return; const c = S(() => { this._doValidate(i, n).catch((y) => { throw y; }); }), I = i.onDidChangeContent(c), E = i.onDidChangeAttached(c); this._modelListeners.set(i.uri.toString(), [I, E]), this._doValidate(i, n).catch(w("DiagnosticsAdapter")); }, g = (i) => { const n = i.uri.toString(); a.setModelMarkers(i, this._languageId, []); const c = this._modelListeners.get(n); if (c) { for (; c.length; ) c.pop()?.dispose(); this._modelListeners.delete(n); } }; this._disposables.push(a.onDidCreateModel(d)), this._disposables.push( a.onWillDisposeModel((i) => { g(i); }) ), this._disposables.push( a.onDidChangeModelLanguage((i) => { g(i.model), d(i.model); }) ), this._disposables.push( s.onDidChange(() => { a.getModels().forEach((i) => { i.getLanguageId() === this._languageId && (g(i), d(i)); }); }) ), this._disposables.push( s.onModelContextDidChange((i) => { a.getModels().forEach((n) => { n.getLanguageId() === this._languageId && n.uri.toString() === i && this._doValidate(n, this._languageId).catch(w("DiagnosticsAdapter")); }); }) ), this._disposables.push({ dispose: () => { this._modelListeners.forEach((i) => i.forEach((n) => n.dispose())), this._modelListeners.clear(); } }), a.getModels().forEach(d); }); } dispose() { this._disposables.forEach((e) => e.dispose()), this._disposables = []; } async _doValidate(e, r) { if (e.isAttachedToEditor()) try { const s = await f(), u = await this._worker(e.uri), a = this._defaults.getApiContextForModel(e.uri), d = await u.doValidation(e.uri.toString(), a), g = d.map((i) => R(e.uri, i)); this._diagnosticsService.fireDiagnosticsChange(e.uri, d), s.setModelMarkers(e, r, g); } catch (s) { w("DiagnosticsAdapter")(s); } } } function D(t) { switch (t) { case h.Error: return p.Error; case h.Warning: return p.Warning; case h.Information: return p.Info; case h.Hint: return p.Hint; default: return p.Info; } } function R(t, e) { return { severity: D(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 V(t) { return { character: t.column - 1, line: t.lineNumber - 1 }; } function m(t) { return new b(t.start.line + 1, t.start.character + 1, t.end.line + 1, t.end.character + 1); } function K(t) { return { range: m(t.range), text: t.newText }; } function N(t) { return typeof t.insert < "u" && typeof t.replace < "u"; } function z(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 O { constructor(e, r) { this._worker = e, this._defaults = r, this.triggerCharacters = [".", "("]; } async provideCompletionItems(e, r) { const s = await this._worker(e.uri), u = this._defaults.getApiContextForModel(e.uri), a = await s.doComplete(e.uri.toString(), V(r), u), d = e.getWordUntilPosition(r), g = new b(r.lineNumber, d.startColumn, r.lineNumber, d.endColumn), i = a.items.map((n) => { const c = { label: n.label, insertText: n.insertText || n.label, sortText: n.sortText, filterText: n.filterText, detail: n.detail, range: g, kind: z(n.kind) }; return n.textEdit && (N(n.textEdit) ? c.range = { insert: m(n.textEdit.insert), replace: m(n.textEdit.replace) } : c.range = m(n.textEdit.range), c.insertText = n.textEdit.newText), n.additionalTextEdits && (c.additionalTextEdits = n.additionalTextEdits.map(K)), n.insertTextFormat === L.Snippet && (c.insertTextRules = l.CompletionItemInsertTextRule.InsertAsSnippet), n.documentation && (typeof n.documentation == "string" ? c.documentation = n.documentation : c.documentation = { supportThemeIcons: !1, value: n.documentation.value, supportHtml: !0 }), c; }); return { incomplete: a.isIncomplete, suggestions: i }; } } class B { constructor(e, r) { this._worker = e, this._defaults = r; } async provideDocumentFormattingEdits(e) { const r = await this._worker(e.uri), s = this._defaults.getApiContextForModel(e.uri.toString()); return (await r.doFormat(e.uri.toString(), s)).map((a) => ({ range: m(a.range), text: a.newText })); } } let x = Promise.withResolvers(); class v { 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, x = Promise.withResolvers()), 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 x.promise; } async _getClientProxy() { const e = await f(); if (!this._client) { const { languageId: r } = this._defaults, s = window.MonacoEnvironment?.getWorker; if (!s) throw new Error( "MonacoEnvironment.getWorker is not configured. Call setupMonacoEnvironment(...) before creating Arcade workers." ); this._worker = e.createWebWorker({ worker: s("ArcadeWorker", r), host: this._defaults.workerHost }), x.resolve(this._worker), this._client = this._worker.getProxy(); } return await this._client; } async getLanguageServiceWorker(...e) { const r = await this._getClientProxy(); return await this._worker?.withSyncedResources(e), r; } } let C; const H = "quickfix", U = "arcgis.arcade-editor.ask-ai", k = /* @__PURE__ */ new Map(); let _; function te(t, e) { const r = typeof t == "string" ? t : t.uri.toString(); if (!e) { k.delete(r); return; } k.set(r, e); } async function q() { return _ || (_ = (async () => (await f()).addCommand({ id: U, run: (...r) => { const [, s, u] = r; if (typeof s != "string") return; const a = typeof u == "string" ? u : void 0; k.get(s)?.run(a); } }))()), await _; } class Q { async provideCodeActions(e, r, s, u) { const a = k.get(e.uri.toString()); return { actions: a ? await a.provideCodeActions({ model: e, range: r, context: s, token: u }) : [], // Monaco always disposes CodeActionList results, but these actions are plain data with no request-scoped resources. dispose: () => { } }; } } async function re(...t) { return await v.waitForWorker(), await new Promise((e, r) => { if (!C) { r(new Error("Arcade not registered!")); return; } e(C(...t)); }); } class X { constructor() { this._onDiagnosticsChange = new F(); } /** * 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, r) { this._onDiagnosticsChange.fire({ uri: e, diagnostics: r }); } } const A = new X(); function ne() { return A; } function ie(t) { q(); const e = new v(t), r = async (...s) => await e.getLanguageServiceWorker(...s); C = r, l.setMonarchTokensProvider(t.languageId, W), l.setLanguageConfiguration(t.languageId, T), l.registerCompletionItemProvider( t.languageId, new O(r, t) ), l.registerDocumentFormattingEditProvider( t.languageId, new B(r, t) ), l.registerCodeActionProvider(t.languageId, new Q(), { providedCodeActionKinds: [H] }), new $(t.languageId, r, { defaults: t, diagnosticsService: A }); } export { U as arcadeAskAiCommandId, H as arcadeQuickFixKind, ne as getArcadeDiagnosticService, re as getArcadeWorker, te as setArcadeAskAiBinding, ie as setupMode };