@arcgis/coding-components
Version:
ArcGIS Coding Components
374 lines (373 loc) • 10.4 kB
JavaScript
/* COPYRIGHT Esri - https://js.arcgis.com/5.1/LICENSE.txt */
import { Emitter as T, languages as h } 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/platform/hover/browser/hover.js";
import "monaco-editor/esm/vs/editor/common/languages/supports/tokenization.js";
import "monaco-editor/esm/vs/base/browser/dom.js";
import "monaco-editor/esm/vs/editor/browser/editorDom.js";
import "monaco-editor/esm/vs/editor/browser/controller/mouseTarget.js";
import { i as E } from "./monaco-importer.js";
import { a as d } from "./sql-expr-defaults.js";
import { debounce as S } from "@arcgis/toolkit/function";
import { rethrowError as g } from "@arcgis/toolkit/log";
import { g as f } from "./sql-expr-service-accessors.js";
let p = Promise.withResolvers();
class _ {
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, p = 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 p.promise;
}
async _getClientProxy() {
const e = await E();
if (!this._client) {
const { languageId: t } = this._defaults, i = window.MonacoEnvironment?.getWorker;
if (!i)
throw new Error(
"MonacoEnvironment.getWorker is not configured. Call setupMonacoEnvironment(...) before creating SQL Expression workers."
);
this._worker = e.createWebWorker({
worker: i("SqlExprWorker", t),
host: this._defaults.workerHost
}), p.resolve(this._worker), this._client = this._worker.getProxy();
}
return await this._client;
}
async getLanguageServiceWorker(...e) {
const t = await this._getClientProxy();
return await this._worker?.withSyncedResources(e), t;
}
}
const N = [
"AND",
"AS",
"BOTH",
"CASE",
"CAST",
"CURRENT_DATE",
"CURRENT_TIME",
"CURRENT_TIMESTAMP",
"DATE",
"DAY",
"DOY",
"DOW",
"ELSE",
"END",
"ESCAPE",
"FALSE",
"FLOAT",
"FOR",
"FROM",
"HOUR",
"IN",
"INT",
"INTEGER",
"INTERVAL",
"LEADING",
"LIKE",
"MINUTE",
"MONTH",
"POSITION",
"REAL",
"SECOND",
"SMALLINT",
"THEN",
"TIME",
"TIMESTAMP",
"TIMEZONE_HOUR",
"TIMEZONE_MINUTE",
"TO",
"TRAILING",
"TRIM",
"TRUE",
"VARCHAR",
"WEEK",
"WHEN",
"WITH",
"YEAR",
"ZONE"
], A = [
// Logical
"AND",
"BETWEEN",
"LIKE",
"NOT",
"OR",
// Predicates
"IS",
"NULL"
], L = [
// Conversion
"CAST",
// Datetime
"EXTRACT",
"CURRENT_DATE",
"CURRENT_TIME",
"CURRENT_TIMESTAMP",
"CURRENT_USER",
// Mathematical
"ABS",
"ACOS",
"ASIN",
"ATAN",
"CEILING",
"COS",
"FLOOR",
"LOG",
"LOG10",
"MOD",
"NULLIF",
"POWER",
"ROUND",
"SIGN",
"SIN",
"TAN",
"TRUNCATE",
// String
"CHAR_LENGTH",
"COALESCE",
"CONCAT",
"LOWER",
"POSITION",
"SUBSTRING",
"TRIM",
"UPPER"
], R = {
comments: {
lineComment: "--",
blockComment: ["/*", "*/"]
},
brackets: [
["{", "}"],
["[", "]"],
["(", ")"]
],
autoClosingPairs: [
{ open: "{", close: "}" },
{ open: "[", close: "]" },
{ open: "(", close: ")" },
{ open: '"', close: '"' },
{ open: "'", close: "'" }
],
surroundingPairs: [
{ open: "{", close: "}" },
{ open: "[", close: "]" },
{ open: "(", close: ")" },
{ open: '"', close: '"' },
{ open: "'", close: "'" }
]
}, x = {
defaultToken: "",
tokenPostfix: ".arcgis",
ignoreCase: !0,
brackets: [
{ open: "[", close: "]", token: "delimiter.square" },
{ open: "(", close: ")", token: "delimiter.parenthesis" }
],
keywords: N,
operators: A,
builtinFunctions: L,
builtinVariables: [],
tokenizer: {
root: [
{ include: "@comments" },
{ include: "@whitespace" },
{ include: "@numbers" },
{ include: "@strings" },
{ include: "@complexIdentifiers" },
{ include: "@scopes" },
[/[;,.]/, "delimiter"],
[/[()]/, "@brackets"],
[
/[\w@#$]+/,
{
cases: {
"@operators": "operator",
"@builtinVariables": "predefined",
"@builtinFunctions": "predefined",
"@keywords": "keyword",
"@default": "identifier"
}
}
],
[/[<>=!%&+\-*/|~^]/, "operator"]
],
whitespace: [[/\s+/, "white"]],
comments: [
[/--+.*/, "comment"],
[/\/\*/, { token: "comment.quote", next: "@comment" }]
],
comment: [
[/[^*/]+/, "comment"],
// Not supporting nested comments, as nested comments seem to not be standard?
// i.e. http://stackoverflow.com/questions/728172/are-there-multiline-comment-delimiters-in-sql-that-are-vendor-agnostic
// [/\/\*/, { token: 'comment.quote', next: '@push' }], // nested comment not allowed :-(
[/\*\//, { token: "comment.quote", next: "@pop" }],
[/./, "comment"]
],
numbers: [
[/0[xX][0-9a-fA-F]*/, "number"],
[/[$][+-]*\d*(\.\d*)?/, "number"],
[/((\d+(\.\d*)?)|(\.\d+))([eE][\-+]?\d+)?/, "number"]
],
strings: [
[/N'/, { token: "string", next: "@string" }],
[/'/, { token: "string", next: "@string" }]
],
string: [
[/[^']+/, "string"],
[/''/, "string"],
[/'/, { token: "string", next: "@pop" }]
],
complexIdentifiers: [[/"/, { token: "identifier.quote", next: "@quotedIdentifier" }]],
quotedIdentifier: [
[/[^"]+/, "identifier"],
[/""/, "identifier"],
[/"/, { token: "identifier.quote", next: "@pop" }]
],
scopes: [
[/(BEGIN|CASE)\b/i, { token: "keyword.block" }],
[/END\b/i, { token: "keyword.block" }],
[/WHEN\b/i, { token: "keyword.choice" }],
[/THEN\b/i, { token: "keyword.choice" }]
]
}
};
class M {
constructor(e, t) {
this._worker = e, this._defaults = t;
}
async provideCompletionItems(e, t) {
try {
const i = await this._worker(e.uri), n = e.getWordUntilPosition(t), s = {
startLineNumber: t.lineNumber,
endLineNumber: t.lineNumber,
startColumn: n.startColumn,
endColumn: n.endColumn
}, c = this._defaults.getApiContextForModel(e.uri);
return await i.doComplete(e.uri.toString(), s, t, c);
} catch (i) {
return g("SqlExprCompletionProvider")(i), { suggestions: [] };
}
}
}
class b {
constructor(e, t, {
defaults: i,
diagnosticService: n
}) {
this._languageId = e, this._worker = t, this._disposables = [], this._modelListeners = /* @__PURE__ */ new Map(), this._diagnosticsService = n, this._defaults = i, E().then((s) => {
const c = (o) => {
const r = o.getLanguageId();
if (r !== this._languageId)
return;
const l = S(() => {
this._doValidate(o, r).catch((I) => {
throw I;
});
}), w = o.onDidChangeContent(l), C = o.onDidChangeAttached(l);
this._modelListeners.set(o.uri.toString(), [w, C]), this._doValidate(o, r).catch(g("SqlExprDiagnosticsAdapter"));
}, u = (o) => {
const r = o.uri.toString();
s.setModelMarkers(o, this._languageId, []);
const l = this._modelListeners.get(r);
if (l) {
for (; l.length; )
l.pop()?.dispose();
this._modelListeners.delete(r);
}
};
this._disposables.push(s.onDidCreateModel(c)), this._disposables.push(s.onWillDisposeModel((o) => u(o))), this._disposables.push(
s.onDidChangeModelLanguage((o) => {
u(o.model), c(o.model);
})
), this._disposables.push(
i.onDidChange(() => {
s.getModels().forEach((o) => {
o.getLanguageId() === this._languageId && (u(o), c(o));
});
})
), this._disposables.push(
i.onModelContextDidChange((o) => {
s.getModels().forEach((r) => {
r.getLanguageId() === this._languageId && r.uri.toString() === o && this._doValidate(r, this._languageId).catch(g("SqlExprDiagnosticsAdapter"));
});
})
), this._disposables.push({
dispose: () => {
this._modelListeners.forEach((o) => o.forEach((r) => r.dispose())), this._modelListeners.clear();
}
}), s.getModels().forEach(c);
});
}
async _doValidate(e, t) {
const i = await E();
if (e.isAttachedToEditor())
try {
const n = await this._worker(e.uri), s = this._defaults.getApiContextForModel(e.uri), c = await n.doValidation(e.uri.toString(), s);
this._diagnosticsService.fireDiagnosticsChange(e.uri, c), i.setModelMarkers(e, t, c);
} catch (n) {
g("SqlExprDiagnosticsAdapter")(n);
}
}
}
let m;
async function Y(...a) {
return await _.waitForWorker(), await new Promise((e, t) => {
if (!m) {
t(new Error("sql expression worker not registered!"));
return;
}
e(m(...a));
});
}
class D {
constructor() {
this._onDiagnosticsChange = new T();
}
/**
* 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, t) {
this._onDiagnosticsChange.fire({ uri: e, diagnostics: t });
}
}
const k = new D();
async function Z() {
return await f(), k;
}
function X(a) {
const e = new _(a), t = async (...n) => await e.getLanguageServiceWorker(...n);
m = t, h.setMonarchTokensProvider(d.languageId, x), h.setLanguageConfiguration(d.languageId, R);
const i = new M(t, d);
h.registerCompletionItemProvider(d.languageId, i), new b(a.languageId, t, {
defaults: a,
diagnosticService: k
});
}
export {
Z as getSqlExprDiagnosticService,
Y as getSqlExprWorker,
X as setupMode
};