Implementing SQL Autocompletion in Monaco Editor

Recently, I used Monaco editor to implement an SQL editor. To improve the user experience, code auto-completion was necessary. After some research, I found a solution, so I’m marking it down here.

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

Built-in Autocompletion?

YES, Monaco supports some built-in languages by default, such as JavaScript, TypeScript, CSS, JSON, and HTML. You can achieve code autocompletion for languages with built-in support simply by setting the editor language to JavaScript`.

However, for languages like SQL that do not have built-in support, setting the editor language to SQL only enables syntax highlighting.

1
2
3
4
5
6
7
8
monaco.editor.create(editorElRef.current, {
...
language: 'sql',
...
autoClosingBrackets: 'always',
autoClosingOvertype: 'always',
autoClosingQuotes: 'always',
})

Custom Autocompletion?

For languages like SQL, if you want autocompletion, you must rely on custom autocompletion.

Through research, I found that you can register completion logic for the corresponding language using the monaco.languages.registerCompletionItemProvider method.

The registerCompletionItemProvider method allows you to define a list of completion options for the user under different input contexts. This method also supports asynchronous returns, making requesting completion from the backend feasible.

registerCompletionItemProvider vs registerInlineCompletionsProvider

Upon closely examining the API, you will notice two methods related to completion. Generally, we need to implement registerCompletionItemProvider, but what’s the difference between the two?

registerCompletionItemProvider presents a list of completion options in a dropdown, as shown in the image at the beginning of the article. In contrast, registerInlineCompletionsProvider displays completion suggestions directly after the cursor.

In the context of ZSH, this is similar to the difference between autosuggestion and autocompletion.

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

Example

If you want to fetch autocompletion suggestions from a backend service, you can implement asynchronous logic within provideCompletionItems. Here’s an example of requesting autocompletion data from an API:

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
monaco.languages.registerCompletionItemProvider('sql', {
triggerCharacters: [' '],
provideCompletionItems: async (model, position) => {
const wordInfo = model.getWordUntilPosition(position);
const range = {
startLineNumber: position.lineNumber,
endLineNumber: position.lineNumber,
startColumn: wordInfo.startColumn,
endColumn: wordInfo.endColumn
};

const suggestions = await fetch('/api/getSqlSuggestions', {
method: 'POST',
body: JSON.stringify({word: wordInfo.word})
}).then(res => res.json());

return {
suggestions: suggestions.map(s => ({
label: s.label,
kind: monaco.languages.CompletionItemKind.Keyword,
insertText: s.insertText,
range: range
}))
};
}
});

At the end

Following the above methods, you now understand how to implement SQL code autocompletion in Monaco-editor. Extending this method further, it’s possible to implement SQL code autocompletion through AI services since the autocompletion registration method provided by Monaco supports asynchronous returns.