I’m creating a custom language for Monaco Editor and having trouble with autocompletion. The provideCompletionItems function in my completion provider doesn’t seem to be called at all. I’ve registered the language and the provider, but nothing happens when I type. I suspect there might be an issue with my tokenizer.
Interestingly, syntax highlighting is working for keywords, types, constants, and operators, but not for built-in functions.
Here’s my language definition (CodeFrLanguage.js):
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api';
// Define keywords and tokens (keeping your existing definitions)
const keywords = [
'Algorithme', 'Debut', 'Fin',
'Variable', 'Variables', 'Constante',
'Si', 'Alors', 'SinonSi', 'Sinon', 'FinSi',
'TantQue', 'Faire', 'FinTantQue',
'Pour', 'De', 'A', 'FinPour',
'Lire', 'Ecrire',
'Et', 'Ou', 'Non', 'Oux',
'Mod', 'Tableau'
];
const typeKeywords = [
'Entier', 'Decimal', 'Chaine', 'Caractere', 'Logique'
];
const constants = [
'Vrai', 'Faux'
];
const builtins = [
'Racine', 'Abs', 'Log', 'Log10', 'Arrondi'
];
const operators = [
'=', '>', '<', '!', '~', '?', ':',
'==', '<=', '>=', '!=', '+', '-', '*', '/',
'^', 'Mod', 'Et', 'Ou', 'Non', 'Oux'
];
// Create completion items
const createCompletionItem = (label, kind, insertText, documentation = '') => ({
label,
kind,
insertText,
documentation: { value: documentation },
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet
});
// Register language
export const registerCodeFrLanguage = () => {
// Register the language first
monaco.languages.register({ id: 'codefr' });
// Set the tokens provider
monaco.languages.setMonarchTokensProvider('codefr', {
ignoreCase: false,
defaultToken: '',
tokenPostfix: '.codefr',
keywords,
typeKeywords,
constants,
builtins,
operators,
symbols: /[=><!~?:&|+-*/^%]+/,
tokenizer: {
root: [
[/^(Algorithme)(s+)([a-zA-Z_]w*)/, ['keyword', 'white', 'identifier']],
[/^(Debut|Fin)/, 'keyword'],
[/[a-zA-Z_]w*/, {
cases: {
'@keywords': 'keyword',
'@typeKeywords': 'type',
'@constants': 'constant',
'@builtins': 'function',
'@default': 'identifier'
}
}],
{ include: '@whitespace' },
[/[{}()[]]/, '@brackets'],
[/[<>](?!@symbols)/, '@brackets'],
[/@symbols/, {
cases: {
'@operators': 'operator',
'@default': ''
}
}],
[/d*.d+([eE][-+]?d+)?/, 'number.float'],
[/d+/, 'number'],
[/"([^"\]|\.)*$/, 'string.invalid'],
[/"/, { token: 'string.quote', bracket: '@open', next: '@string' }],
],
string: [
[/[^\"]+/, 'string'],
[/"/, { token: 'string.quote', bracket: '@close', next: '@pop' }]
],
whitespace: [
[/[ trn]+/, 'white'],
[//*/, 'comment', '@comment'],
[///.*$/, 'comment'],
],
comment: [
[/[^/*]+/, 'comment'],
[//*/, 'comment', '@push'],
["\*/", 'comment', '@pop'],
[/[/*]/, 'comment']
],
}
});
// Register completion item provider
monaco.languages.registerCompletionItemProvider('codefr', {
provideCompletionItems: (model, position) => {
const word = model.getWordUntilPosition(position);
const range = {
startLineNumber: position.lineNumber,
endLineNumber: position.lineNumber,
startColumn: word.startColumn,
endColumn: word.endColumn
};
const suggestions = [
// Keywords
...keywords.map(keyword =>
createCompletionItem(keyword, monaco.languages.CompletionItemKind.Keyword, keyword)
),
// Types
...typeKeywords.map(type =>
createCompletionItem(type, monaco.languages.CompletionItemKind.Class, type)
),
// Constants
...constants.map(constant =>
createCompletionItem(constant, monaco.languages.CompletionItemKind.Constant, constant)
),
// Built-in functions
...builtins.map(func =>
createCompletionItem(func, monaco.languages.CompletionItemKind.Function, func)
),
// Snippets
createCompletionItem(
'Algorithme',
monaco.languages.CompletionItemKind.Snippet,
'Algorithme ${1:nom}nVariablesnt${2}nDebutnt${3}nFin',
'Structure de base d'un algorithme'
),
createCompletionItem(
'Si',
monaco.languages.CompletionItemKind.Snippet,
'Si ${1:condition} Alorsnt${2}nFinSi',
'Structure conditionnelle Si'
),
createCompletionItem(
'Pour',
monaco.languages.CompletionItemKind.Snippet,
'Pour ${1:i} De ${2:debut} A ${3:fin} Fairent${4}nFinPour',
'Boucle Pour'
),
createCompletionItem(
'TantQue',
monaco.languages.CompletionItemKind.Snippet,
'TantQue ${1:condition} Fairent${2}nFinTantQue',
'Boucle TantQue'
)
];
return {
suggestions: suggestions.map(s => ({
...s,
range: range
}))
};
},
triggerCharacters: [' ', 'n', ':', '.', '(', '[', 'A']
});
// Register language configuration
monaco.languages.setLanguageConfiguration('codefr', {
comments: {
lineComment: '//',
blockComment: ['/*', '*/']
},
brackets: [
['{', '}'],
['[', ']'],
['(', ')']
],
autoClosingPairs: [
{ open: '{', close: '}' },
{ open: '[', close: ']' },
{ open: '(', close: ')' },
{ open: '"', close: '"', notIn: ['string'] },
{ open: "'", close: "'", notIn: ['string', 'comment'] }
],
surroundingPairs: [
{ open: '{', close: '}' },
{ open: '[', close: ']' },
{ open: '(', close: ')' },
{ open: '"', close: '"' },
{ open: "'", close: "'" }
],
});
};
And here’s how I’m creating the editor in my React component (MonacoEditor.js):
import React, { useRef, useEffect } from 'react';
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api';
import { useTheme } from '../../contexts/ThemeContext';
import { Box } from '@mui/material';
import { registerCodeFrLanguage } from './CodeFrLanguage';
const MonacoEditor = ({ value, onChange, readOnly = false }) => {
const editorRef = useRef(null);
const containerRef = useRef(null);
const { mode } = useTheme();
useEffect(() => {
// Register language
registerCodeFrLanguage();
if (containerRef.current && !editorRef.current) {
// Create editor instance
editorRef.current = monaco.editor.create(containerRef.current, {
value: value || 'Algorithme Nom_AlgorithmenVariablesntVariable1: Type1nDebutntInstruction1nFin',
language: 'codefr',
theme: mode === 'dark' ? 'vs-dark' : 'vs',
minimap: { enabled: true },
fontSize: 14,
automaticLayout: true,
lineNumbers: 'on',
readOnly: readOnly,
tabSize: 2,
wordWrap: 'on',
quickSuggestions: {
other: true,
comments: true,
strings: true
},
suggestOnTriggerCharacters: true,
acceptSuggestionOnEnter: 'on',
tabCompletion: 'on',
wordBasedSuggestions: true,
snippetSuggestions: 'top',
suggest: {
localityBonus: true,
snippetsPreventQuickSuggestions: false,
showIcons: true,
maxVisibleSuggestions: 12,
filterGraceful: true,
showInlineDetails: true,
preview: true
}
});
// Handle changes
editorRef.current.onDidChangeModelContent(() => {
if (onChange) {
onChange(editorRef.current.getValue());
}
});
return () => {
if (editorRef.current) {
editorRef.current.dispose();
editorRef.current = null;
}
};
}
}, []);
// Update value when prop changes
useEffect(() => {
if (editorRef.current && value !== undefined && value !== editorRef.current.getValue()) {
editorRef.current.setValue(value);
}
}, [value]);
// Update theme when mode changes
useEffect(() => {
if (editorRef.current) {
monaco.editor.setTheme(mode === 'dark' ? 'vs-dark' : 'vs');
}
}, [mode]);
return (
<Box
ref={containerRef}
sx={{
width: '100%',
height: '400px',
'& .monaco-editor': {
paddingTop: 1,
},
}}
/>
);
};
export default MonacoEditor;
I’ve tried setting triggerCharacters to all letters and even added console.log statements inside provideCompletionItems, but they’re never executed. I’ve also checked the console for errors and verified that the language is being registered.
Any help in diagnosing why the completion provider isn’t being triggered and why built-in functions aren’t highlighted would be greatly appreciated.