@arcgis/coding-components
Version:
ArcGIS Coding Components
579 lines (578 loc) • 17.9 kB
JavaScript
/* 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
};