Angular 18集成Monaco Editor:自定义主题失效问题排查
Angular 18集成Monaco Editor自定义主题与Monarch规则不生效问题
我计划在Angular 18应用中集成Monaco Editor用于编辑C#代码,暂不使用语言服务器,通过自定义主题与Monarch分词规则实现C#语法高亮,但自定义关键词hello始终无法显示预期的绿色,其他主题颜色设置也未生效。
现有实现代码
app.module.ts
monaco.languages.register({ id: 'csharp' }); // Set Monarch tokenization rules for C# language monaco.languages.setMonarchTokensProvider('csharp', { keywords: ['public', 'void', 'if', 'else', 'for', 'while', 'return', 'using'], typeKeywords: ['Console', 'string', 'int', 'bool', 'double'], operators: ['=', '>', '<', '!', '==', '!=', '+', '-', '*', '/', '&&', '||'], symbols: /[>=<!~?:&|+\-*\/\^%]+/, tokenizer: { root: [ // Match identifiers [/[A-Z][\w$]*/, { cases: { '@typeKeywords': 'type', // Types like Console (green) '@default': 'identifier' // Other identifiers } }], // Match keywords [/[a-zA-Z_]\w*/, { cases: { '@keywords': 'keyword', // C# keywords (blue) 'hello': 'helloToken', // Custom token for "hello" '@default': 'identifier' // Default identifier } }], // Recognize strings [/".*"/, 'string'], // Match brackets, operators, etc. [/[{}()\[\]]/, '@brackets'], [/@symbols/, { cases: { '@operators': 'operator' } }] ] } }); export const customTheme: monaco.editor.IStandaloneThemeData = { base: 'vs-dark', inherit: true, rules: [ { token: '', foreground: '2A2C2D' }, { token: 'hello', foreground: '#66ff66' }, { token: 'string.key.json', foreground: '7b3814' }, { token: 'string.value.json', foreground: '0F6980', fontStyle: 'bold' } ], colors: { 'editor.background': '#ff0000', 'editor.lineHighlightBackground': '#ff0000', 'editorCursor.foreground': '#ff0000', 'editor.selectionBackground': '#ff0000', 'editor.inactiveSelectionBackground': '#ff0000', } }; const monacoConfig: NgxMonacoEditorConfig = { baseUrl: 'assets', // configure base path for monaco editor. Starting with version 8.0.0 it defaults to './assets'. Previous releases default to '/assets' defaultOptions: { scrollBeyondLastLine: false }, // pass default options to be used onMonacoLoad: () => { monaco.editor.defineTheme('customTheme', customTheme); }, monacoRequire: (<any>window).monacoRequire, requireConfig: { preferScriptTags: true } }; @NgModule({ declarations: [ AppComponent ], imports: [ CommonModule, FormsModule, BrowserModule, AppRoutingModule, MonacoEditorModule.forRoot(monacoConfig) ], providers: [], bootstrap: [AppComponent] }) export class AppModule { }
app.component.ts
@Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.scss'] }) export class AppComponent { title = 'monaco-test4'; editorOptions = { theme: 'customTheme', language: 'csharp' }; code: string = `public void x() { Console.WriteLine("hello world"); hello; // This should be highlighted }`; }
app.component.html
<div style="height: 100vh; display: flex; flex-direction: column;"> <h1>{{ title }}</h1> <ngx-monaco-editor [options]="editorOptions" [(ngModel)]="code" style="flex: 1; margin-top: 20px;" ></ngx-monaco-editor> </div>
问题分析与修正方案
1. Token名称不匹配
Monarch规则中给hello定义的token是helloToken,但主题规则里写的是hello,两者名称不一致,导致主题无法匹配到该token。
2. 主题规则干扰与优先级问题
- 空token规则
{ token: '', foreground: '2A2C2D' }会覆盖基础主题的全局样式,干扰其他token的显示。 inherit: true时,基础主题规则优先级可能高于自定义规则,需确保自定义token名称准确无冲突。
3. Monaco初始化顺序问题
语言注册和Monarch规则设置在模块顶层执行时,Monaco可能尚未完成初始化,需将这些逻辑移到onMonacoLoad回调中,确保API就绪。
修正后的代码
app.module.ts
export const customTheme: monaco.editor.IStandaloneThemeData = { base: 'vs-dark', inherit: true, rules: [ { token: 'helloToken', foreground: '#66ff66' }, // 匹配Monarch中的token名称 { token: 'type', foreground: '#66ff66' }, // 显式定义类型颜色 { token: 'keyword', foreground: '#569cd6' }, // 显式定义关键词颜色 { token: 'string', foreground: '#ce9178' } // 显式定义字符串颜色 ], colors: { 'editor.background': '#1e1e1e', 'editor.lineHighlightBackground': '#2a2a2a', 'editorCursor.foreground': '#ffffff', 'editor.selectionBackground': '#264f78', 'editor.inactiveSelectionBackground': '#1e1e1e', } }; const monacoConfig: NgxMonacoEditorConfig = { baseUrl: 'assets', defaultOptions: { scrollBeyondLastLine: false }, onMonacoLoad: () => { // 将语言注册和Monarch规则移到此处,确保Monaco已初始化 monaco.languages.register({ id: 'csharp' }); monaco.languages.setMonarchTokensProvider('csharp', { keywords: ['public', 'void', 'if', 'else', 'for', 'while', 'return', 'using'], typeKeywords: ['Console', 'string', 'int', 'bool', 'double'], operators: ['=', '>', '<', '!', '==', '!=', '+', '-', '*', '/', '&&', '||'], symbols: /[>=<!~?:&|+\-*\/\^%]+/, tokenizer: { root: [ // 调整匹配顺序,先处理关键词和自定义token [/[a-zA-Z_]\w*/, { cases: { '@keywords': 'keyword', '@typeKeywords': 'type', 'hello': 'helloToken', '@default': 'identifier' } }], // 完善字符串匹配,支持转义字符 [/"/, 'string', '@string'], // 括号与运算符 [/[{}()\[\]]/, '@brackets'], [/@symbols/, { cases: { '@operators': 'operator' } }] ], string: [ [/[^\\"]+/, 'string'], [/\\./, 'string.escape'], [/"/, 'string', '@pop'] ] } }); monaco.editor.defineTheme('customTheme', customTheme); }, monacoRequire: (<any>window).monacoRequire, requireConfig: { preferScriptTags: true } }; @NgModule({ declarations: [ AppComponent ], imports: [ CommonModule, FormsModule, BrowserModule, AppRoutingModule, MonacoEditorModule.forRoot(monacoConfig) ], providers: [], bootstrap: [AppComponent] }) export class AppModule { }
额外说明
- 调整Monaco规则匹配顺序,避免自定义token被标识符规则提前拦截。
- 完善字符串tokenizer,支持转义字符,提升语法高亮准确性。
- 显式定义基础token颜色,避免继承主题样式不符合预期。
内容的提问来源于stack exchange,提问作者Banana828
相关产品推荐
相关产品推荐

