@@ -17,65 +17,86 @@ export function registerSyntax(instance: Monaco) {
1717 const groovyKeywords = [ 'def' , 'as' , 'in' , 'trait' , 'with' ] ;
1818 groovyLanguage . keywords = [ ...( groovyLanguage . keywords || [ ] ) , ...groovyKeywords ] ;
1919
20- const javaRootRules = [ ...( groovyLanguage . tokenizer . root || [ ] ) ] . filter ( ( rule ) => {
21- // Removes Java's single quote interpretation from tokenizer
22- if ( Array . isArray ( rule ) && rule [ 0 ] instanceof RegExp && typeof rule [ 1 ] === 'string' ) {
23- return ! rule [ 1 ] . includes ( 'string' ) ;
24- }
25- return true ;
26- } ) ;
20+ // Copy Java root rules as base
21+ const javaRootRules = [ ...( groovyLanguage . tokenizer . root || [ ] ) ] ;
2722
2823 // Extend the tokenizer with Groovy-specific features
2924 groovyLanguage . tokenizer = {
3025 ...groovyLanguage . tokenizer ,
3126 root : [
32- ...javaRootRules ,
27+ // Groovy-specific rules MUST come before Java rules
28+
29+ // Import statements - color the whole qualified name
30+ [ / ( i m p o r t ) ( \s + ) ( [ \w . ] + ) / , [ 'keyword' , 'white' , 'type.identifier' ] ] ,
3331
34- // multiline strings
32+ // Slashy strings (regex): /pattern/
33+ // Only match when followed by regex-indicating chars, not division or comments
34+ [ / \/ (? = [ [ ( ^ . \\ a - z A - Z ] ) / , { token : 'regexp' , next : '@slashy_string' } ] ,
35+
36+ // Triple-quoted strings (must be before double quote)
3537 [ / " " " / , { token : 'string.quote' , bracket : '@open' , next : '@string_multiline' } ] ,
3638
37- // double quoted strings
39+ // Double- quoted strings with GString interpolation
3840 [ / " / , { token : 'string.quote' , bracket : '@open' , next : '@string_double' } ] ,
3941
40- // single quoted strings
42+ // Single- quoted strings (Groovy treats these as strings, not char literals)
4143 [ / ' / , { token : 'string.quote' , bracket : '@open' , next : '@string_single' } ] ,
4244
43- // Groovy closures
44- [ / \{ / , { token : 'delimiter.curly' , next : '@closure' } ] ,
45+ // Constants (UPPER_SNAKE_CASE) - before types to take precedence
46+ [ / [ A - Z ] [ A - Z 0 - 9 _ ] + \b / , 'constant' ] ,
47+
48+ // Type names (PascalCase identifiers)
49+ [ / [ A - Z ] [ \w $ ] * / , 'type.identifier' ] ,
50+
51+ // Java rules come after
52+ ...javaRootRules ,
53+ ] ,
54+
55+ // Slashy string state (regex literal)
56+ slashy_string : [
57+ [ / \\ ./ , 'regexp.escape' ] , // Escaped chars (including \/)
58+ [ / \/ / , { token : 'regexp' , next : '@pop' } ] , // Closing /
59+ [ / [ ^ \\ / \r \n ] + / , 'regexp' ] , // Content
60+ [ / \r ? \n / , { token : '' , next : '@pop' } ] , // Newline = exit (error recovery)
4561 ] ,
4662
63+ // Double-quoted string with GString interpolation
4764 string_double : [
48- [ / \\ \$ / , 'string.escape' ] ,
49- [ / \$ \{ / , { token : 'identifier' , bracket : '@open' , next : '@gstring_expression' } ] ,
50- [ / \\ ./ , 'string.escape' ] ,
51- [ / [ ^ \\ " $ ] + / , 'string' ] ,
52- [ / " / , { token : 'string.quote' , bracket : '@close' , next : '@pop' } ] ,
53- [ / [ $ ] / , 'string' ] ,
65+ [ / \\ \$ / , 'string.escape' ] , // Escaped $
66+ [ / \$ \{ / , { token : 'identifier' , bracket : '@open' , next : '@gstring_expression' } ] , // ${...}
67+ [ / \\ ./ , 'string.escape' ] , // Escape sequences
68+ [ / [ ^ \\ " $ ] + / , 'string' ] , // Regular content
69+ [ / " / , { token : 'string.quote' , bracket : '@close' , next : '@pop' } ] , // Closing "
70+ [ / [ $ ] / , 'string' ] , // Lone $ at end
5471 ] ,
5572
73+ // Single-quoted string (no interpolation)
5674 string_single : [
57- [ / [ ^ \\ ' ] + / , 'string' ] ,
58- [ / \\ ./ , 'string.escape' ] ,
59- [ / ' / , { token : 'string.quote' , bracket : '@close' , next : '@pop' } ] ,
75+ [ / [ ^ \\ ' ] + / , 'string' ] , // Regular content
76+ [ / \\ ./ , 'string.escape' ] , // Escape sequences
77+ [ / ' / , { token : 'string.quote' , bracket : '@close' , next : '@pop' } ] , // Closing '
6078 ] ,
6179
80+ // Triple-quoted multiline string with GString interpolation
6281 string_multiline : [
63- [ / \\ \$ / , 'string.escape' ] ,
64- [ / \$ \{ / , { token : 'identifier' , bracket : '@open' , next : '@gstring_expression_multiline' } ] ,
65- [ / \\ ./ , 'string.escape' ] ,
66- [ / [ ^ \\ " $ ] + / , 'string' ] ,
67- [ / " " " / , { token : 'string.quote' , bracket : '@close' , next : '@pop' } ] ,
68- [ / " / , 'string' ] ,
69- [ / [ $ ] / , 'string' ] ,
82+ [ / \\ \$ / , 'string.escape' ] , // Escaped $
83+ [ / \$ \{ / , { token : 'identifier' , bracket : '@open' , next : '@gstring_expression_multiline' } ] , // ${...}
84+ [ / \\ ./ , 'string.escape' ] , // Escape sequences
85+ [ / [ ^ \\ " $ ] + / , 'string' ] , // Regular content
86+ [ / " " " / , { token : 'string.quote' , bracket : '@close' , next : '@pop' } ] , // Closing """
87+ [ / " / , 'string' ] , // Single " inside multiline
88+ [ / [ $ ] / , 'string' ] , // Lone $
7089 ] ,
7190
91+ // GString expression ${...}
7292 gstring_expression : [
7393 [ / ' / , { token : 'string.quote' , bracket : '@open' , next : '@string_in_gstring_single' } ] ,
7494 [ / \{ / , { token : 'delimiter.curly' , bracket : '@open' , next : '@closure' } ] ,
7595 [ / \} / , { token : 'identifier' , bracket : '@close' , next : '@pop' } ] ,
7696 [ / [ ^ { } ' " ] + / , 'identifier' ] ,
7797 ] ,
7898
99+ // GString expression for multiline strings
79100 gstring_expression_multiline : [
80101 [ / ' / , { token : 'string.quote' , bracket : '@open' , next : '@string_in_gstring_single' } ] ,
81102 [ / " / , { token : 'string.quote' , bracket : '@open' , next : '@string_in_gstring_double' } ] ,
@@ -84,18 +105,21 @@ export function registerSyntax(instance: Monaco) {
84105 [ / [ ^ { } ' " ] + / , 'identifier' ] ,
85106 ] ,
86107
108+ // Single-quoted string inside GString expression
87109 string_in_gstring_single : [
88110 [ / [ ^ \\ ' ] + / , 'string' ] ,
89111 [ / \\ ./ , 'string.escape' ] ,
90112 [ / ' / , { token : 'string.quote' , bracket : '@close' , next : '@pop' } ] ,
91113 ] ,
92114
115+ // Double-quoted string inside GString expression
93116 string_in_gstring_double : [
94117 [ / [ ^ \\ " ] + / , 'string' ] ,
95118 [ / \\ ./ , 'string.escape' ] ,
96119 [ / " / , { token : 'string.quote' , bracket : '@close' , next : '@pop' } ] ,
97120 ] ,
98121
122+ // Groovy closure { ... } - simple version, relies on root rules for nested content
99123 closure : [
100124 [ / [ ^ { } ] + / , '' ] ,
101125 [ / \{ / , 'delimiter.curly' , '@push' ] ,
0 commit comments