Files
agent-desktop/console/src/monaco/robotframework.ts
T

122 lines
4.2 KiB
TypeScript
Raw Normal View History

2026-08-23 22:43:18 +08:00
/**
* 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);
}