monaco-editor实现SQL代码补全

· 2 min read

最近使用monaco-editor来实现SQL编辑器,为了提升用户体验,需要实现代码补全。研究后发现了实现方法,因此这里Mark下。

https://static.1991421.cn/2024/2024-09-08-180231.jpeg

内置补全?

YES,默认monaco-editor是内置了一些语言支持,比如JavaScriptTypeScriptCSSJSONHTML。 对于已内置支持的语言。 只需要将编辑器语言设置为javascript,就可以实现代码补全了。

而比如SQL语言是没有内置支持的,当我们将编辑器语言设置为sql,只可以实现语法高亮。

monaco.editor.create(editorElRef.current, {
     	...
      language: 'sql',
    	...
      autoClosingBrackets: 'always',
      autoClosingOvertype: 'always',
      autoClosingQuotes: 'always',
    })

自定义补全?

比如SQL语言,如果想自动补全只能走自定义补全的方式解决。

调研发现可以通过monaco.languages.registerCompletionItemProvider方法来注册对应语言的补全逻辑。

registerCompletionItemProvider方法可以定义当不同的输入上下文情况下,提供给用户的补全选项列表,当然该方法也支持异步返回,因此比如请求后台实现补全也是可行的。

registerCompletionItemProvider vs registerInlineCompletionsProvider

仔细看API会发现,补全相关的方法有两个。一般来说我们需要实现的都是registerCompletionItemProvider,那么两者什么区别呢。

registerCompletionItemProvider提供的补全项是弹出选项列表,正如文章一开始贴出的图片,而registerInlineCompletionsProvider提供的补全项是直接显示在光标位置后

在ZSH里的话,就类似于autosuggestionautocompletion的区别。

https://static.1991421.cn/2024/2024-09-08-224808.jpeg

举例子

以下即实现最简单的SQL高亮,在特定的输入情况下,进行SQL关键字,数据库名称,表名称,字段名称的补全提示。

monaco.languages.registerCompletionItemProvider('sql', {
  triggerCharacters: ['.', ' '],
  provideCompletionItems: function (model, position) {
    let suggestions: any[] = [];
    const { lineNumber, column } = position;
    const textBeforePointer = model.getValueInRange({
      startLineNumber: lineNumber,
      startColumn: 0,
      endLineNumber: lineNumber,
      endColumn: column,
    });
    const tokens = textBeforePointer.toLocaleLowerCase().trim().split(/\s+/);
    const lastToken = tokens[tokens.length - 1]; // 获取最后一段非空字符串
    const word = model.getWordUntilPosition(position);
    const range = {
      startLineNumber: position.lineNumber,
      endLineNumber: position.lineNumber,
      startColumn: word.startColumn,
      endColumn: word.endColumn,
    };
    if (lastToken.endsWith('.')) {
      suggestions = [...tableSuggest];
    } else if (textBeforePointer.endsWith(' ')) {
      if (textBeforePointer.match(/from\s+$/i)) {
        suggestions = [...databaseSuggest];
      } else if (textBeforePointer.match(/where\s+$/i)) {
        suggestions = [...columnSuggest];
      } else {
        suggestions = [...sqlKeywordsSuggest];
      }
    } else {
      suggestions = [...sqlKeywordsSuggest];
    }
    console.log('suggestions', suggestions);
    return {
      suggestions: suggestions.map(item => ({
        ...item,
        range,
      })),
    };
  },
})

写在最后

通过上述方法即了解了如何在monaco-editor中实现SQL代码补全,延伸下该方法,实际上也可以通过AI服务实现SQL代码补全,毕竟Monaco提供的补全注册方法是支持异步返回的。