如何在Monaco Editor JS中为Handlebars模板配置层级智能提示
实现Monaco Editor模板变量的智能提示(支持JSON定义的嵌套对象)
我最近刚好在项目里实现过类似的需求——给Monaco Editor中的模板语法(比如{{ myobject.field1 }})添加智能提示,而且支持用JSON对象定义可提示的结构,包括深层嵌套的字段。下面是具体的实现步骤和代码示例:
核心思路
Monaco Editor本身支持自定义语言服务,我们可以通过注册自定义补全提供者(Completion Item Provider),来解析模板中的变量上下文,根据预先定义的JSON对象结构,动态生成对应的智能提示项。
具体实现步骤
1. 定义你的模板变量结构
首先,把需要提示的对象结构用JSON格式定义好,支持任意层级的嵌套:
// 示例:定义模板可用的变量结构,支持深层嵌套 const templateVariables = { myobject: { field1: '', field2: '', nestedObj: { innerField1: '', innerField2: { deepField: '' } } }, anotherObject: { foo: '', bar: '' } };
2. 初始化Monaco Editor并注册补全提供者
接下来,初始化编辑器,然后注册自定义的补全逻辑。关键是要处理两种触发场景:
- 用户输入
{{后,提示根级别的对象(比如myobject、anotherObject) - 用户输入对象后的
.时,提示该对象的所有字段(包括嵌套字段)
完整代码示例:
<div id="container" style="width:800px;height:600px;border:1px solid gray"></div> <script src="https://cdn.jsdelivr.net/npm/monaco-editor@0.44.0/min/vs/loader.js"></script> <script> require.config({ paths: { 'vs': 'https://cdn.jsdelivr.net/npm/monaco-editor@0.44.0/min/vs' }}); require(['vs/editor/editor.main'], function() { // 1. 定义模板变量结构 const templateVariables = { myobject: { field1: '', field2: '', nestedObj: { innerField1: '', innerField2: { deepField: '' } } }, anotherObject: { foo: '', bar: '' } }; // 2. 初始化编辑器 const editor = monaco.editor.create(document.getElementById('container'), { value: '{{ myobject. }}', language: 'html', // 可以用html或者自定义语言,这里用html作为基础 theme: 'vs-dark' }); // 3. 注册自定义补全提供者 monaco.languages.registerCompletionItemProvider('html', { // 触发补全的字符:空格、.、以及{{ 后的输入 triggerCharacters: [' ', '.', '{', 'm', 'a'], // 可以根据需要调整 provideCompletionItems: function(model, position) { const textUntilPosition = model.getValueInRange({ startLineNumber: position.lineNumber, startColumn: 1, endLineNumber: position.lineNumber, endColumn: position.column }); // 判断当前是否在{{ }}模板内部 const templateMatch = textUntilPosition.match(/\{\{(\s+)?([\w\.]*)$/); if (!templateMatch) { return { suggestions: [] }; } const currentInput = templateMatch[2] || ''; const suggestions = []; // 解析当前输入的路径,比如"myobject.nestedObj"会拆分成["myobject", "nestedObj"] const pathSegments = currentInput.split('.').filter(seg => seg); let currentObj = templateVariables; // 递归找到当前路径对应的对象 for (const seg of pathSegments.slice(0, -1)) { if (currentObj[seg]) { currentObj = currentObj[seg]; } else { // 如果路径不存在,返回空提示 return { suggestions: [] }; } } // 如果是根路径(没有输入.),提示所有根对象 if (pathSegments.length === 0 || (pathSegments.length === 1 && currentInput.endsWith('.'))) { // 处理输入.后的场景,比如"myobject.",提示myobject的字段 if (currentInput.endsWith('.')) { Object.keys(currentObj).forEach(key => { suggestions.push({ label: key, kind: monaco.languages.CompletionItemKind.Property, insertText: key, detail: `字段:${key}` }); }); } else { // 提示根对象 Object.keys(templateVariables).forEach(key => { suggestions.push({ label: key, kind: monaco.languages.CompletionItemKind.Object, insertText: key, detail: `对象:${key}` }); }); } } else { // 处理中间路径的补全,比如输入"myobject.n"时提示nestedObj const lastSegment = pathSegments[pathSegments.length - 1]; Object.keys(currentObj).forEach(key => { if (key.startsWith(lastSegment)) { suggestions.push({ label: key, kind: typeof currentObj[key] === 'object' ? monaco.languages.CompletionItemKind.Object : monaco.languages.CompletionItemKind.Property, insertText: key, detail: typeof currentObj[key] === 'object' ? `对象:${key}` : `字段:${key}` }); } }); } return { suggestions: suggestions }; } }); }); </script>
4. 关键逻辑说明
- 上下文判断:通过正则匹配
{{后的内容,确保只在模板内部触发提示,不会影响其他内容的编辑。 - 路径解析:把用户输入的变量路径(比如
myobject.nestedObj)拆分成数组,递归找到对应的对象节点,然后生成该节点的所有属性作为提示项。 - 补全项类型区分:用
CompletionItemKind区分对象和字段,让提示更直观。
扩展优化
- 你可以把
templateVariables换成动态加载的JSON配置,方便后续维护。 - 如果模板语法有特殊规则(比如支持数组),可以扩展路径解析逻辑,支持数组下标提示。
- 可以添加文档说明,在补全项的
documentation字段里展示字段的描述信息。
内容的提问来源于stack exchange,提问作者Jonny
相关产品推荐
相关产品推荐

