@elastic/monaco-esql
Version:
Monaco editor Monarch language syntax definitions for ES|QL
217 lines (216 loc) • 9.59 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.create = void 0;
const create = (deps = {}) => {
const { headerCommands = [], sourceCommands = [], processingCommands = [], options = [], literals = [], functions = [], delimiters = [], temporalUnits = [], } = deps;
const timeUnits = withLowercaseVariants(temporalUnits.flat()).sort((a, b) => a > b ? -1 : 1);
return {
// Uncomment when developing.
// defaultToken: "invalid",
// ES|QL is case-insensitive.
ignoreCase: false,
// Lists of known language keywords and built-ins.
headerCommands: withLowercaseVariants(headerCommands),
sourceCommands: withLowercaseVariants(sourceCommands),
processingCommands: withLowercaseVariants(processingCommands),
processingCommandsOnlyUppercase: processingCommands,
options: withLowercaseVariants(options),
literals: withLowercaseVariants(literals),
functions: withLowercaseVariants(functions),
delimiters,
namedOperators: withLowercaseVariants([
...(deps.operators?.named?.binary ?? []),
...(deps.operators?.named?.other ?? []),
]),
// Pre-defined regular expressions.
escapes: /\\(?:[abfnrtv\\"']|x[0-9A-Fa-f]{1,4}|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})/,
digits: /\d+(_+\d+)*/,
symbols: /[=><!~:&|+\-*/^%.,]+/,
columnIdentifier: /[a-zA-Z0-9_*-]+/,
brackets: [
{ open: "[", close: "]", token: "delimiter.square" },
{ open: "(", close: ")", token: "delimiter.parenthesis" },
{ open: "{", close: "}", token: "delimiter.curly" },
{ open: "<", close: ">", token: "delimiter.angle" },
],
tokenizer: {
root: [
{ include: "@whitespace" },
// Keywords
[
/@?[a-zA-Z_$][\w$]*(?![.\-:a-zA-Z_0-9])/,
{
cases: {
"@headerCommands": { token: "keyword.command.header.$0" },
"@sourceCommands": { token: "keyword.command.source.$0" },
"@processingCommandsOnlyUppercase": {
token: "keyword.command.processing.$0",
},
"@options": { token: "keyword.option.$0" },
"@literals": { token: "keyword.literal.$0" },
"@functions": { token: "identifier.function.$0" },
"@namedOperators": { token: "keyword.operator.$0" },
"\\@{1}timestamp": "identifier.timestamp",
"@default": "identifier",
},
},
],
{ include: "@expression" },
{ include: "@processingCommand" },
[/\[|\(|\)|\]/, "@brackets"],
[
/@symbols/,
{
cases: {
"@delimiters": "delimiter",
"@default": "",
},
},
],
],
// --------------------------------- Hidden channel: whitespace and comments
whitespace: [
[/[ \t\r\n]+/, ""],
[/\/\*\*(?!\/)/, "comment.doc", "@doc"],
[/\/\*/, "comment", "@comment"],
[/\/\/.*$/, "comment"],
],
comment: [
[/[^/*]+/, "comment"],
[/\*\//, "comment", "@pop"],
[/[/*]/, "comment"],
],
doc: [
[/[^/*]+/, "comment.doc"],
[/\*\//, "comment.doc", "@pop"],
[/[/*]/, "comment.doc"],
],
// ---------------------------------------------------------------- Commands
// This code block allows to color commands when they are followed by a pipe
// character "|", this way all new processing commands are color even if
// they are not part of the command name list.
processingCommand: [
[
/\|/,
{ token: "delimiter.pipe", switchTo: "@beforeMnemonicWhitespace" },
],
[
/\(/,
{
token: "delimiter.parenthesis",
switchTo: "@firstCommandNameInSubQuery",
},
],
],
beforeMnemonicWhitespace: [
{ include: "@whitespace" },
["", { token: "", switchTo: "@commandName" }],
],
exactCommandName: [
[
withLowercaseVariants(headerCommands).join("|"),
{ token: "keyword.command.header.$0", switchTo: "@root" },
],
[
withLowercaseVariants(sourceCommands).join("|"),
{ token: "keyword.command.source.$0", switchTo: "@root" },
],
[
withLowercaseVariants(processingCommands).join("|"),
{ token: "keyword.command.processing.$0", switchTo: "@root" },
],
],
firstCommandNameInSubQuery: [
{ include: "@whitespace" },
// Try to match an exact command name
{ include: "@exactCommandName" },
// If not matched, go to root
{ include: "@root" },
],
// Matches *command name*, i.e. the mnemonic.
commandName: [
// First tries to match all known command names.
{ include: "@exactCommandName" },
// If command name is not well known, just matches the first word.
[
/\w+\b/,
{ token: "keyword.command.processing.$0", switchTo: "@root" },
],
],
// ------------------------------------------------------------- Expressions
expression: [
{ include: "@whitespace" },
{ include: "@literal" },
// Basic ES|QL columns (fields), e.g.: "field", "nested.field", etc.
[/(@columnIdentifier)(\.(@columnIdentifier))*/, "identifier.column"],
// Inline casts: 1.3::INTEGER
[/::\w+\b/, "type"],
// strings
[/"""/, "string.triple", "@string_triple"],
[/"([^"\\]|\\.)*$/, "string.invalid"], // non-terminated string
[/'([^'\\]|\\.)*$/, "string.invalid"], // non-terminated string
[/"/, "string", "@string"],
// Escaped column parts: nested.`escaped`.column
[/`/, "string", "@column_escape_part"],
],
literal: [
{ include: "@timeInterval" },
{ include: "@number" },
// Params
[
/\?{1,9}(([a-zA-Z_][a-zA-Z_0-9]+)|[0-9]+)?/,
{
cases: {
"\\?{1,9}": "variable.name.unnamed",
"\\?{1,9}([a-zA-Z_][a-zA-Z_0-9]+)?": "variable.name.named",
"\\?{1,9}([0-9]+)?": "variable.name.positional",
},
},
],
],
timeInterval: [[`(@digits)\\s*(${timeUnits.join("|")})`, "number.time"]],
number: [
[/(@digits)[eE]([-+]?(@digits))?/, "number.float"],
[/(@digits)?\.(@digits)([eE][-+]?(@digits))?/, "number.float"],
[/(@digits)/, "number"],
],
// Single double-quote strings: "str"
string: [
[/[^\\"]+/, "string"],
[/@escapes/, "string.escape"],
[/\\./, "string.escape.invalid"],
[/"/, "string", "@pop"],
],
// Triple double-quoted strings: """str"""
string_triple: [
// Needed to avoid poping on these cases: | WHERE KQL("""log.level:"warning""""),
// without this it will pop on warning""", leaving ") as invalid tokens.
[/"(?=""")/, "string.triple"],
// End string_triple when """ is found
[/"""/, "string.triple", "@pop"],
// The following two rules match the string content, contemplating the apearance of isolated quotes.
[/[^"]+/, "string.triple"],
[/"/, "string.triple"],
],
// Backtick quoted "strings". ES|QL does not have back-tick "strings"
// *per se*, but column parts can have backtick-escaped parts.
column_escape_part: [
[/[^`]+/, "string"],
[/@escapes/, "string.escape"],
[/\\./, "string.escape.invalid"],
[/`/, "string", "@pop"],
],
},
};
};
exports.create = create;
/**
* Given a list of strings, returns a new list with both the original and their lowercase versions (no duplicates).
*/
function withLowercaseVariants(list) {
const set = new Set(list);
for (const item of list) {
set.add(item.toLowerCase());
}
return Array.from(set);
}