monaco-editor实现SQL代码补全

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

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

内置补全?

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

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

1
2
3
4
5
6
7
8
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关键字,数据库名称,表名称,字段名称的补全提示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
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提供的补全注册方法是支持异步返回的。