@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
JavaScript
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
};