Skip to content

Instantly share code, notes, and snippets.

@Ruminat
Created February 5, 2022 12:32
Show Gist options
  • Save Ruminat/42364312e90ba5d71875697ede66990e to your computer and use it in GitHub Desktop.
Save Ruminat/42364312e90ba5d71875697ede66990e to your computer and use it in GitHub Desktop.
A Groovy language for monaco editor
import { languages } from "monaco-editor";
function getTokens(tokens: string, divider = "|"): string[] {
return tokens.split(divider);
}
const wordPattern = /(-?\d*\.\d\w*)|([^`~!@#%^&*()\-=+[{\]}\\|;:'",./?\s]+)/g;
const brackets: languages.CharacterPair[] = [
["{", "}"],
["[", "]"],
["(", ")"],
];
const bracketTokens = [
{
open: "[",
close: "]",
token: "delimiter.square",
},
{
open: "(",
close: ")",
token: "delimiter.parenthesis",
},
{
open: "{",
close: "}",
token: "delimiter.curly",
},
];
const autoClosingPairs = [
{ open: "{", close: "}" },
{ open: "[", close: "]" },
{ open: "(", close: ")" },
{ open: '"', close: '"' },
{ open: "'", close: "'" },
{ open: "`", close: "`" },
];
const surroundingPairs = autoClosingPairs;
const id = "groovy";
const label = "Groovy";
export const registerGroovyLanguageForMonaco = () => {
languages.register({ id, aliases: [label] });
languages.setMonarchTokensProvider(id, {
brackets: bracketTokens,
tokenPostfix: ".groovy",
keywords: getTokens(
"assert|with|abstract|continue|for|new|switch|assert|default|goto|package|synchronized|boolean|do|if|private|this|break|double|implements|protected|throw|byte|else|import|public|throws|case|enum|instanceof|return|transient|catch|extends|int|short|try|char|final|interface|static|void|class|finally|long|strictfp|volatile|def|float|native|super|while|in|as"
),
typeKeywords: getTokens(
"Long|Integer|Short|Byte|Double|Number|Float|Character|Boolean|StackTraceElement|Appendable|StringBuffer|Iterable|ThreadGroup|Runnable|Thread|IllegalMonitorStateException|StackOverflowError|OutOfMemoryError|VirtualMachineError|ArrayStoreException|ClassCastException|LinkageError|NoClassDefFoundError|ClassNotFoundException|RuntimeException|Exception|ThreadDeath|Error|Throwable|System|ClassLoader|Cloneable|Class|CharSequence|Comparable|String|Object"
),
constants: getTokens("null|Infinity|NaN|undefined|true|false"),
builtinFunctions: getTokens(
"AbstractMethodError|AssertionError|ClassCircularityError|ClassFormatError|Deprecated|EnumConstantNotPresentException|ExceptionInInitializerError|IllegalAccessError|IllegalThreadStateException|InstantiationError|InternalError|NegativeArraySizeException|NoSuchFieldError|Override|Process|ProcessBuilder|SecurityManager|StringIndexOutOfBoundsException|SuppressWarnings|TypeNotPresentException|UnknownError|UnsatisfiedLinkError|UnsupportedClassVersionError|VerifyError|InstantiationException|IndexOutOfBoundsException|ArrayIndexOutOfBoundsException|CloneNotSupportedException|NoSuchFieldException|IllegalArgumentException|NumberFormatException|SecurityException|Void|InheritableThreadLocal|IllegalStateException|InterruptedException|NoSuchMethodException|IllegalAccessException|UnsupportedOperationException|Enum|StrictMath|Package|Compiler|Readable|Runtime|StringBuilder|Math|IncompatibleClassChangeError|NoSuchMethodError|ThreadLocal|RuntimePermission|ArithmeticException|NullPointerException"
),
operators: [
".",
".&",
".@",
"?.",
"*",
"*.",
"*:",
"~",
"!",
"++",
"--",
"**",
"+",
"-",
"*",
"/",
"%",
"<<",
">>",
">>>",
"..",
"..<",
"<",
"<=",
">",
">",
"==",
"!=",
"<=>",
"===",
"!==",
"=~",
"==~",
"^",
"|",
"&&",
"||",
"?",
":",
"?:",
"=",
"**=",
"*=",
"/=",
"%=",
"+=",
"-=",
"<<=",
">>=",
">>>=",
"&=",
"^=",
"|=",
"?=",
],
symbols: /[=><!~?:&|+\-*/^%]+/,
escapes: /\\(?:[abfnrtv\\"'`]|x[0-9A-Fa-f]{1,4}|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})/,
regexpctl: /[(){}[\]$^|\-*+?.]/,
regexpesc: /\\(?:[bBdDfnrstvwWn0\\/]|@regexpctl|c[A-Z]|x[0-9a-fA-F]{2}|u[0-9a-fA-F]{4})/,
tokenizer: {
root: [
{ include: "@whitespace" },
[
/\/(?=([^\\/]|\\.)+\/([dgimsuy]*)(\s*)(\.|;|,|\)|\]|\}|$))/,
{ token: "regexp", bracket: "@open", next: "@regexp" },
],
{ include: "@comments" },
{ include: "@numbers" },
{ include: "common" },
[/[;,.]/, "delimiter"],
[/[(){}[\]]/, "@brackets"],
[
/[a-zA-Z_$]\w*/,
{
cases: {
"@keywords": "keyword",
"@typeKeywords": "type",
"@constants": "constant.groovy",
"@builtinFunctions": "constant.other.color",
"@default": "identifier",
},
},
],
[
/@symbols/,
{
cases: {
"@operators": "operator",
"@default": "",
},
},
],
],
common: [
// delimiters and operators
[/[()[\]]/, "@brackets"],
[/[<>](?!@symbols)/, "@brackets"],
[
/@symbols/,
{
cases: {
"@operators": "delimiter",
"@default": "",
},
},
],
[
/\/(?=([^\\/]|\\.)+\/([gimsuy]*)(\s*)(\.|;|\/|,|\)|\]|\}|$))/,
{ token: "regexp", bracket: "@open", next: "@regexp" },
],
// delimiter: after number because of .\d floats
[/[;,.]/, "delimiter"],
// strings
[/"([^"\\]|\\.)*$/, "string.invalid"],
[/'([^'\\]|\\.)*$/, "string.invalid"],
[/"/, "string", "@string_double"],
[/'/, "string", "@string_single"],
],
whitespace: [[/\s+/, "white"]],
comments: [
[/\/\/.*/, "comment"],
[
/\/\*/,
{
token: "comment.quote",
next: "@comment",
},
],
],
comment: [
[/[^*/]+/, "comment"],
[
/\*\//,
{
token: "comment.quote",
next: "@pop",
},
],
[/./, "comment"],
],
commentAnsi: [
[
/\/\*/,
{
token: "comment.quote",
next: "@comment",
},
],
[/[^*/]+/, "comment"],
[
/\*\//,
{
token: "comment.quote",
next: "@pop",
},
],
[/./, "comment"],
],
numbers: [
[/[+-]?\d+(?:(?:\.\d*)?(?:[eE][+-]?\d+)?)?f?\b/, "number.float"],
[/[+-]?(?:0[obx])?\d+(?:u?[lst]?)?\b/, "number"],
],
regexp: [
[/(\{)(\d+(?:,\d*)?)(\})/, ["regexp.escape.control", "regexp.escape.control", "regexp.escape.control"]],
[
/(\[)(\^?)(?=(?:[^\]\\/]|\\.)+)/,
// @ts-ignore
["regexp.escape.control", { token: "regexp.escape.control", next: "@regexrange" }],
],
[/(\()(\?:|\?=|\?!)/, ["regexp.escape.control", "regexp.escape.control"]],
[/[()]/, "regexp.escape.control"],
[/@regexpctl/, "regexp.escape.control"],
[/[^\\/]/, "regexp"],
[/@regexpesc/, "regexp.escape"],
[/\\\./, "regexp.invalid"],
// @ts-ignore
[/(\/)([gimsuy]*)/, [{ token: "regexp", bracket: "@close", next: "@pop" }, "keyword.other"]],
],
regexrange: [
[/-/, "regexp.escape.control"],
[/\^/, "regexp.invalid"],
[/@regexpesc/, "regexp.escape"],
[/[^\]]/, "regexp"],
[/\]/, { token: "regexp.escape.control", next: "@pop", bracket: "@close" }],
],
embedded: [
[
/([^@]|^)([@]{4})*[@]{2}([@]([^@]|$)|[^@]|$)/,
{
token: "@rematch",
next: "@pop",
nextEmbedded: "@pop",
},
],
],
string_double: [
[/\$\{/, { token: "delimiter.bracket", next: "@bracketCounting" }],
[/[^\\"$]+/, "string"],
[/[^\\"]+/, "string"],
[/@escapes/, "string.escape"],
[/\\./, "string.escape.invalid"],
[/"/, "string", "@pop"],
],
string_single: [
[/[^\\']+/, "string"],
[/@escapes/, "string.escape"],
[/\\./, "string.escape.invalid"],
[/'/, "string", "@pop"],
],
string_backtick: [
[/\$\{/, { token: "delimiter.bracket", next: "@bracketCounting" }],
[/[^\\"$]+/, "string"],
[/@escapes/, "string.escape"],
[/\\./, "string.escape.invalid"],
[/"/, "string", "@pop"],
],
bracketCounting: [
[/\{/, "delimiter.bracket", "@bracketCounting"],
[/\}/, "delimiter.bracket", "@pop"],
{ include: "common" },
],
},
});
languages.setLanguageConfiguration(id, {
comments: {
lineComment: "//",
blockComment: ["/*", "*/"],
},
brackets,
autoClosingPairs,
surroundingPairs,
wordPattern,
});
};
@GeniusP
Copy link

GeniusP commented Mar 17, 2023

how i use this file in react project

@Ruminat
Copy link
Author

Ruminat commented Mar 18, 2023

@GeniusP
You need to call the registerGroovyLanguageForMonaco function from this file somewhere in your project, and "groovy" should be available as one of the monaco languages

@inad9300
Copy link

inad9300 commented Aug 7, 2023

Note that this lacks support for single and double quote multi-line strings.

@svitrol
Copy link

svitrol commented Apr 30, 2025

I was getting some error
obrazek
and editor was not coloring correctly
Snímek obrazovky 2025-04-30 090051
instead of looking like this
Snímek obrazovky 2025-04-30 085800

So after comparing to java language definition
I located the issue to
obrazek
obrazek
after removal the above mentioned error no longer shows itself, but I don't realy understand what went wrong.
So here the edited file

import { languages } from "monaco-editor";

function getTokens(tokens: string, divider = "|"): string[] {
  return tokens.split(divider);
}

const wordPattern = /(-?\d*\.\d\w*)|([^`~!@#%^&*()\-=+[{\]}\\|;:'",./?\s]+)/g;

const brackets: languages.CharacterPair[] = [
  ["{", "}"],
  ["[", "]"],
  ["(", ")"],
];

const autoClosingPairs = [
  { open: "{", close: "}" },
  { open: "[", close: "]" },
  { open: "(", close: ")" },
  { open: '"', close: '"' },
  { open: "'", close: "'" },
  { open: "`", close: "`" },
];

const surroundingPairs = autoClosingPairs;

const id = "groovy";
const label = "Groovy";

export const registerGroovyLanguageForMonaco = () => {
  languages.register({ id, aliases: [label] });

  languages.setMonarchTokensProvider(id, {
    tokenPostfix: ".groovy",
    keywords: getTokens(
      "assert|with|abstract|continue|for|new|switch|assert|default|goto|package|synchronized|boolean|do|if|private|this|break|double|implements|protected|throw|byte|else|import|public|throws|case|enum|instanceof|return|transient|catch|extends|int|short|try|char|final|interface|static|void|class|finally|long|strictfp|volatile|def|float|native|super|while|in|as"
    ),
    typeKeywords: getTokens(
      "Long|Integer|Short|Byte|Double|Number|Float|Character|Boolean|StackTraceElement|Appendable|StringBuffer|Iterable|ThreadGroup|Runnable|Thread|IllegalMonitorStateException|StackOverflowError|OutOfMemoryError|VirtualMachineError|ArrayStoreException|ClassCastException|LinkageError|NoClassDefFoundError|ClassNotFoundException|RuntimeException|Exception|ThreadDeath|Error|Throwable|System|ClassLoader|Cloneable|Class|CharSequence|Comparable|String|Object"
    ),
    constants: getTokens("null|Infinity|NaN|undefined|true|false"),
    builtinFunctions: getTokens(
      "AbstractMethodError|AssertionError|ClassCircularityError|ClassFormatError|Deprecated|EnumConstantNotPresentException|ExceptionInInitializerError|IllegalAccessError|IllegalThreadStateException|InstantiationError|InternalError|NegativeArraySizeException|NoSuchFieldError|Override|Process|ProcessBuilder|SecurityManager|StringIndexOutOfBoundsException|SuppressWarnings|TypeNotPresentException|UnknownError|UnsatisfiedLinkError|UnsupportedClassVersionError|VerifyError|InstantiationException|IndexOutOfBoundsException|ArrayIndexOutOfBoundsException|CloneNotSupportedException|NoSuchFieldException|IllegalArgumentException|NumberFormatException|SecurityException|Void|InheritableThreadLocal|IllegalStateException|InterruptedException|NoSuchMethodException|IllegalAccessException|UnsupportedOperationException|Enum|StrictMath|Package|Compiler|Readable|Runtime|StringBuilder|Math|IncompatibleClassChangeError|NoSuchMethodError|ThreadLocal|RuntimePermission|ArithmeticException|NullPointerException"
    ),
    operators: [
      ".",
      ".&",
      ".@",
      "?.",
      "*",
      "*.",
      "*:",
      "~",
      "!",
      "++",
      "--",
      "**",
      "+",
      "-",
      "*",
      "/",
      "%",
      "<<",
      ">>",
      ">>>",
      "..",
      "..<",
      "<",
      "<=",
      ">",
      ">=",
      "==",
      "!=",
      "<=>",
      "===",
      "!==",
      "=~",
      "==~",
      "^",
      "|",
      "&&",
      "||",
      "?",
      ":",
      "?:",
      "=",
      "**=",
      "*=",
      "/=",
      "%=",
      "+=",
      "-=",
      "<<=",
      ">>=",
      ">>>=",
      "&=",
      "^=",
      "|=",
      "?=",
    ],
    symbols: /[=><!~?:&|+\-*/^%]+/,
    escapes: /\\(?:[abfnrtv\\"'`]|x[0-9A-Fa-f]{1,4}|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})/,

    regexpctl: /[(){}[\]$^|\-*+?.]/,
    regexpesc: /\\(?:[bBdDfnrstvwWn0\\/]|@regexpctl|c[A-Z]|x[0-9a-fA-F]{2}|u[0-9a-fA-F]{4})/,

    tokenizer: {
      root: [
        { include: "@whitespace" },
        [
          /\/(?=([^\\/]|\\.)+\/([dgimsuy]*)(\s*)(\.|;|,|\)|\]|\}|$))/,
          { token: "regexp", bracket: "@open", next: "@regexp" },
        ],
        { include: "@comments" },
        { include: "@numbers" },
        { include: "common" },
        [/[;,.]/, "delimiter"],
        [/[(){}[\]]/, "@brackets"],
        [
          /[a-zA-Z_$]\w*/,
          {
            cases: {
              "@keywords": "keyword",
              "@typeKeywords": "type",
              "@constants": "constant.groovy",
              "@builtinFunctions": "constant.other.color",
              "@default": "identifier",
            },
          },
        ],
        [
          /@symbols/,
          {
            cases: {
              "@operators": "operator",
              "@default": "",
            },
          },
        ],
      ],
      common: [
        // delimiters and operators
        [/[()[\]]/, "@brackets"],
        [/[<>](?!@symbols)/, "@brackets"],
        [
          /@symbols/,
          {
            cases: {
              "@operators": "delimiter",
              "@default": "",
            },
          },
        ],

        [
          /\/(?=([^\\/]|\\.)+\/([gimsuy]*)(\s*)(\.|;|\/|,|\)|\]|\}|$))/,
          { token: "regexp", bracket: "@open", next: "@regexp" },
        ],

        // delimiter: after number because of .\d floats
        [/[;,.]/, "delimiter"],

        // strings
        [/"([^"\\]|\\.)*$/, "string.invalid"],
        [/'([^'\\]|\\.)*$/, "string.invalid"],
        [/"/, "string", "@string_double"],
        [/'/, "string", "@string_single"],
      ],
      whitespace: [[/\s+/, "white"]],
      comments: [
        [/\/\/.*/, "comment"],
        [
          /\/\*/,
          {
            token: "comment.quote",
            next: "@comment",
          },
        ],
      ],
      comment: [
        [/[^*/]+/, "comment"],
        [
          /\*\//,
          {
            token: "comment.quote",
            next: "@pop",
          },
        ],
        [/./, "comment"],
      ],
      commentAnsi: [
        [
          /\/\*/,
          {
            token: "comment.quote",
            next: "@comment",
          },
        ],
        [/[^*/]+/, "comment"],
        [
          /\*\//,
          {
            token: "comment.quote",
            next: "@pop",
          },
        ],
        [/./, "comment"],
      ],
      numbers: [
        [/[+-]?\d+(?:(?:\.\d*)?(?:[eE][+-]?\d+)?)?f?\b/, "number.float"],
        [/[+-]?(?:0[obx])?\d+(?:u?[lst]?)?\b/, "number"],
      ],
      regexp: [
        [/(\{)(\d+(?:,\d*)?)(\})/, ["regexp.escape.control", "regexp.escape.control", "regexp.escape.control"]],
        [
          /(\[)(\^?)(?=(?:[^\]\\/]|\\.)+)/,
          // @ts-ignore
          ["regexp.escape.control", { token: "regexp.escape.control", next: "@regexrange" }],
        ],
        [/(\()(\?:|\?=|\?!)/, ["regexp.escape.control", "regexp.escape.control"]],
        [/[()]/, "regexp.escape.control"],
        [/@regexpctl/, "regexp.escape.control"],
        [/[^\\/]/, "regexp"],
        [/@regexpesc/, "regexp.escape"],
        [/\\\./, "regexp.invalid"],
        // @ts-ignore
        [/(\/)([gimsuy]*)/, [{ token: "regexp", bracket: "@close", next: "@pop" }, "keyword.other"]],
      ],

      regexrange: [
        [/-/, "regexp.escape.control"],
        [/\^/, "regexp.invalid"],
        [/@regexpesc/, "regexp.escape"],
        [/[^\]]/, "regexp"],
        [/\]/, { token: "regexp.escape.control", next: "@pop", bracket: "@close" }],
      ],
      embedded: [
        [
          /([^@]|^)([@]{4})*[@]{2}([@]([^@]|$)|[^@]|$)/,
          {
            token: "@rematch",
            next: "@pop",
            nextEmbedded: "@pop",
          },
        ],
      ],
      string_double: [
        [/\$\{/, { token: "delimiter.bracket", next: "@bracketCounting" }],
        [/[^\\"$]+/, "string"],
        [/[^\\"]+/, "string"],
        [/@escapes/, "string.escape"],
        [/\\./, "string.escape.invalid"],
        [/"/, "string", "@pop"],
      ],
      string_single: [
        [/[^\\']+/, "string"],
        [/@escapes/, "string.escape"],
        [/\\./, "string.escape.invalid"],
        [/'/, "string", "@pop"],
      ],
      string_backtick: [
        [/\$\{/, { token: "delimiter.bracket", next: "@bracketCounting" }],
        [/[^\\"$]+/, "string"],
        [/@escapes/, "string.escape"],
        [/\\./, "string.escape.invalid"],
        [/"/, "string", "@pop"],
      ],
      bracketCounting: [
        [/\{/, "delimiter.bracket", "@bracketCounting"],
        [/\}/, "delimiter.bracket", "@pop"],
        { include: "common" },
      ],
    },
  });
  languages.setLanguageConfiguration(id, {
    comments: {
      lineComment: "//",
      blockComment: ["/*", "*/"],
    },
    brackets,
    autoClosingPairs,
    surroundingPairs,
    wordPattern,
  });
};

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment