如何基于Next.js+pnpm实现仅高亮大括号的Monaco Editor自定义语言
解决方案
1. 修正Monarch Tokenizer,实现括号内文本高亮
现有代码的tokenizer仅标记了大括号本身,未处理括号内的文本。需要通过状态切换实现括号内内容的绿色标记:
export const languageDef = { defaultToken: 'plain', brackets: [['{', '}', 'delimiter.curly']], tokenizer: { root: [ // 匹配左括号,切换到inside状态并标记括号 [/{/, { token: 'delimiter.curly.open', next: '@inside' }], // 匹配右括号直接标记 [/}/, 'delimiter.curly.close'], // 非括号文本使用默认样式 [/[^{}]+/, 'plain'], ], inside: [ // 匹配右括号,切回root状态并标记括号 [/}/, { token: 'delimiter.curly.close', next: '@pop' }], // 括号内的文本标记为green-text [/[^{}]+/, 'green-text'], ], }, }
2. 调整主题样式,对应token颜色
修改主题配置,为不同token分配指定颜色:
- 正常大括号设为黑色(可根据需求调整)
- 括号内文本设为绿色
- 预留错误token的红色样式
const configuration = { base: 'vs', inherit: true, rules: [ { token: 'delimiter.curly.open', foreground: '000000' }, { token: 'delimiter.curly.close', foreground: '000000' }, { token: 'green-text', foreground: '008000' }, { token: 'error.curly', foreground: 'FF0000', fontStyle: 'bold' }, ], }
3. 添加语法诊断,标记未闭合/多余括号
通过注册诊断提供者,扫描文本中的括号匹配情况,生成错误提示:
在useEffect中补充以下代码:
// 配置语言的括号自动闭合规则 monaco.languages.setLanguageConfiguration('tracta', { brackets: [['{', '}']], autoClosingPairs: [{ open: '{', close: '}' }], }) // 注册诊断提供者,检查括号匹配 monaco.languages.registerDiagnosticsProvider('tracta', { provideDiagnostics: (model) => { const text = model.getValue() const diagnostics = [] const stack = [] for (let i = 0; i < text.length; i++) { const char = text[i] if (char === '{') { stack.push(i) } else if (char === '}') { if (stack.length === 0) { // 多余的右括号 const pos = model.getPositionAt(i) diagnostics.push({ severity: monaco.MarkerSeverity.Error, startLineNumber: pos.lineNumber, startColumn: pos.column, endLineNumber: pos.lineNumber, endColumn: pos.column + 1, message: '多余的闭合大括号', source: 'tracta', }) } else { stack.pop() } } } // 未闭合的左括号 stack.forEach((pos) => { const position = model.getPositionAt(pos) diagnostics.push({ severity: monaco.MarkerSeverity.Error, startLineNumber: position.lineNumber, startColumn: position.column, endLineNumber: position.lineNumber, endColumn: position.column + 1, message: '未闭合的大括号', source: 'tracta', }) }) return diagnostics }, })
完整组件代码
整合所有修改后的完整代码:
import React, { useEffect } from 'react' import Editor, { useMonaco } from '@monaco-editor/react' export const languageDef = { defaultToken: 'plain', brackets: [['{', '}', 'delimiter.curly']], tokenizer: { root: [ [/{/, { token: 'delimiter.curly.open', next: '@inside' }], [/}/, 'delimiter.curly.close'], [/[^{}]+/, 'plain'], ], inside: [ [/}/, { token: 'delimiter.curly.close', next: '@pop' }], [/[^{}]+/, 'green-text'], ], }, } const configuration = { base: 'vs', inherit: true, rules: [ { token: 'delimiter.curly.open', foreground: '000000' }, { token: 'delimiter.curly.close', foreground: '000000' }, { token: 'green-text', foreground: '008000' }, { token: 'error.curly', foreground: 'FF0000', fontStyle: 'bold' }, ], } export default function TractaEditor({ value, onChange, }: { value: string onChange: (value: string | undefined) => void }) { const monaco = useMonaco() useEffect(() => { if (!monaco) return monaco.languages.register({ id: 'tracta' }) monaco.languages.setMonarchTokensProvider('tracta', languageDef) monaco.languages.setLanguageConfiguration('tracta', { brackets: [['{', '}']], autoClosingPairs: [{ open: '{', close: '}' }], }) monaco.languages.registerDiagnosticsProvider('tracta', { provideDiagnostics: (model) => { const text = model.getValue() const diagnostics = [] const stack = [] for (let i = 0; i < text.length; i++) { const char = text[i] if (char === '{') { stack.push(i) } else if (char === '}') { if (stack.length === 0) { const pos = model.getPositionAt(i) diagnostics.push({ severity: monaco.MarkerSeverity.Error, startLineNumber: pos.lineNumber, startColumn: pos.column, endLineNumber: pos.lineNumber, endColumn: pos.column + 1, message: '多余的闭合大括号', source: 'tracta', }) } else { stack.pop() } } } stack.forEach((pos) => { const position = model.getPositionAt(pos) diagnostics.push({ severity: monaco.MarkerSeverity.Error, startLineNumber: position.lineNumber, startColumn: position.column, endLineNumber: position.lineNumber, endColumn: position.column + 1, message: '未闭合的大括号', source: 'tracta', }) }) return diagnostics }, }) monaco.editor.defineTheme('tracta', configuration) }, [monaco]) return ( <Editor defaultLanguage="tracta" theme="tracta" value={value} onChange={onChange} line={20} options={{ lineNumbers: 'off', minimap: { enabled: false }, readOnly: false, scrollbar: { vertical: 'hidden', horizontal: 'hidden' }, wordWrap: 'on', wrappingIndent: 'same', fontFamily: 'Geist', fontSize: 14, lineHeight: 20, lineDecorationsWidth: 0, glyphMargin: false, renderLineHighlight: 'none', overviewRulerBorder: false, folding: false, rulers: [], }} className="flex max-h-[120px] min-h-[80px] w-full rounded-base border-2 border-black bg-white p-2 text-sm font-base shadow-base ring-offset-white placeholder:text-black/50 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50" /> ) }
关键说明
- Tokenizer状态管理:通过
@inside和@pop实现括号内外的状态切换,精准标记不同区域的文本样式。 - 诊断逻辑:手动扫描文本中的括号匹配关系,生成错误标记,实现未闭合/多余括号的红色提示。
- 主题映射:将自定义token与颜色绑定,确保视觉效果符合需求。
内容的提问来源于stack exchange,提问作者charlietlamb
相关产品推荐
相关产品推荐

