122 lines
4.2 KiB
TypeScript
122 lines
4.2 KiB
TypeScript
|
|
/**
|
||
|
|
* RobotFramework language support for Monaco.
|
||
|
|
*
|
||
|
|
* Monaco does not ship a RobotFramework definition, so `.robot` and
|
||
|
|
* `.resource` files fell back to plaintext in the Coding Mode editor.
|
||
|
|
* This module registers a Monarch tokenizer covering section headers,
|
||
|
|
* settings, control-flow keywords, variables, tags and comments.
|
||
|
|
*
|
||
|
|
* Registered once from `monacoSetup.ts` (side-effect free by itself;
|
||
|
|
* call `registerRobotFramework` with a Monaco instance).
|
||
|
|
*/
|
||
|
|
|
||
|
|
import type * as Monaco from "monaco-editor";
|
||
|
|
|
||
|
|
export const ROBOT_LANGUAGE_ID = "robotframework";
|
||
|
|
|
||
|
|
export const conf: Monaco.languages.LanguageConfiguration = {
|
||
|
|
comments: { lineComment: "#" },
|
||
|
|
brackets: [
|
||
|
|
["{", "}"],
|
||
|
|
["[", "]"],
|
||
|
|
["(", ")"],
|
||
|
|
],
|
||
|
|
autoClosingPairs: [
|
||
|
|
{ open: "{", close: "}" },
|
||
|
|
{ open: "[", close: "]" },
|
||
|
|
{ open: "(", close: ")" },
|
||
|
|
],
|
||
|
|
surroundingPairs: [
|
||
|
|
{ open: "{", close: "}" },
|
||
|
|
{ open: "[", close: "]" },
|
||
|
|
{ open: "(", close: ")" },
|
||
|
|
],
|
||
|
|
};
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Monarch grammar.
|
||
|
|
*
|
||
|
|
* RobotFramework separates cells with 2+ spaces or 1+ tabs, so keyword
|
||
|
|
* rules assert a cell boundary via `(?= {2,}|\t+|[ \t]*$)`.
|
||
|
|
* Token names reuse Monaco's built-in theme colors (keyword, comment,
|
||
|
|
* variable, tag, type, number) so both `vs` and `vs-dark` work.
|
||
|
|
*/
|
||
|
|
export const language: Monaco.languages.IMonarchLanguage = {
|
||
|
|
defaultToken: "",
|
||
|
|
ignoreCase: true,
|
||
|
|
tokenizer: {
|
||
|
|
root: [
|
||
|
|
// Section headers: *** Settings ***, *** Test Cases *** …
|
||
|
|
[
|
||
|
|
/^\*+[ \t]*(settings?|variables?|test cases?|tasks?|keywords?|comments?)\b[ \t*]*$/,
|
||
|
|
"keyword.section",
|
||
|
|
],
|
||
|
|
// Line continuation marker
|
||
|
|
[/^[ \t]*\.\.\.(?=[ \t]|$)/, "delimiter"],
|
||
|
|
// Control-flow structures at cell start (FOR / IF / TRY …)
|
||
|
|
[
|
||
|
|
/^[ \t]*(FOR|END|IF|ELSE IF|ELSE|WHILE|TRY|EXCEPT|FINALLY|RETURN|BREAK|CONTINUE|VAR|GROUP)(?= {2,}|\t+|[ \t]*$)/,
|
||
|
|
"keyword.control",
|
||
|
|
],
|
||
|
|
// Gherkin-style prefixes in keyword calls
|
||
|
|
[/^[ \t]+(given|when|then|and|but)(?=[ \t])/, "keyword.control"],
|
||
|
|
// Settings-section directives at column 0
|
||
|
|
[
|
||
|
|
/^(library|resource|variables|documentation|metadata|name|suite setup|suite teardown|test setup|test teardown|test template|test timeout|test tags|task setup|task teardown|task template|task timeout|task tags|force tags|default tags|keyword tags)(?= {2,}|\t+|[ \t]*$)/,
|
||
|
|
"keyword",
|
||
|
|
],
|
||
|
|
// Test case / task / keyword definition name at column 0
|
||
|
|
[/^[^ \t#*$@&%\\[].*$/, "type"],
|
||
|
|
// [Tags] / [Arguments] … bracket settings
|
||
|
|
[
|
||
|
|
/\[(documentation|tags|arguments|setup|teardown|template|timeout|return|precondition|postcondition)\]/,
|
||
|
|
"tag",
|
||
|
|
],
|
||
|
|
// Escaped characters (\# is not a comment, \${ not a variable)
|
||
|
|
[/\\./, "string.escape"],
|
||
|
|
// Comments in the first cell or after a cell separator
|
||
|
|
[/^[ \t]*#.*$/, "comment"],
|
||
|
|
[/(?: {2,}|\t+)[ \t]*#.*$/, "comment"],
|
||
|
|
// Variables: ${scalar} @{list} &{dict} %{env}
|
||
|
|
[/[$@&%]\{/, { token: "variable", next: "@variable" }],
|
||
|
|
// FOR loop separators as standalone cells
|
||
|
|
[
|
||
|
|
/\b(IN RANGE|IN ENUMERATE|IN ZIP|IN)(?= {2,}|\t+|[ \t]*$)/,
|
||
|
|
"keyword.control",
|
||
|
|
],
|
||
|
|
// Numbers
|
||
|
|
[/\b\d+(\.\d+)?\b/, "number"],
|
||
|
|
// Cell separators & plain text
|
||
|
|
[/[ \t]+/, "white"],
|
||
|
|
[/[^ \t$@&%#\\]+/, ""],
|
||
|
|
[/./, ""],
|
||
|
|
],
|
||
|
|
// Inside ${ … } — supports nesting like ${outer_${inner}}
|
||
|
|
variable: [
|
||
|
|
[/[$@&%]\{/, { token: "variable", next: "@push" }],
|
||
|
|
[/\}/, { token: "variable", next: "@pop" }],
|
||
|
|
[/[^$@&%{}]+/, "variable"],
|
||
|
|
[/./, "variable"],
|
||
|
|
],
|
||
|
|
},
|
||
|
|
};
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Register RobotFramework with the given Monaco instance.
|
||
|
|
* Safe to call multiple times — skips if already registered.
|
||
|
|
*/
|
||
|
|
export function registerRobotFramework(monaco: typeof Monaco): void {
|
||
|
|
const alreadyRegistered = monaco.languages
|
||
|
|
.getLanguages()
|
||
|
|
.some((lang) => lang.id === ROBOT_LANGUAGE_ID);
|
||
|
|
if (alreadyRegistered) return;
|
||
|
|
|
||
|
|
monaco.languages.register({
|
||
|
|
id: ROBOT_LANGUAGE_ID,
|
||
|
|
extensions: [".robot", ".resource"],
|
||
|
|
aliases: ["RobotFramework", "robotframework", "Robot Framework"],
|
||
|
|
});
|
||
|
|
monaco.languages.setLanguageConfiguration(ROBOT_LANGUAGE_ID, conf);
|
||
|
|
monaco.languages.setMonarchTokensProvider(ROBOT_LANGUAGE_ID, language);
|
||
|
|
}
|