Syntax highlighting fixes, autocomplete overhaul, tui changes

This commit is contained in:
Alexander R.
2026-07-28 22:30:25 +00:00
parent bcb054a3fa
commit 0559f7549a
11 changed files with 1046 additions and 245 deletions
+7 -7
View File
@@ -1,6 +1,6 @@
import React, { useState, useEffect, useRef } from 'react';
import { Editor } from '@monaco-editor/react';
import { registerMonacoCompletions } from '../utils/monacoCompletions';
import { registerMonacoCompletions, getMonacoLanguage, getLanguageFileName } from '../utils/monacoCompletions';
import {
getChallenges,
runCode,
@@ -184,7 +184,7 @@ const Workspace: React.FC<WorkspaceProps> = ({ challengeId, onBack }) => {
const inputToSend = overrideStdin !== undefined ? overrideStdin : stdinText;
try {
const res = await runCode(currentChallenge.language.toLowerCase() || 'python', code, inputToSend);
const res = await runCode(getMonacoLanguage(currentChallenge.language), code, inputToSend);
if (res.exit_code === 0) {
setOutput({
status: 'success',
@@ -210,7 +210,7 @@ const Workspace: React.FC<WorkspaceProps> = ({ challengeId, onBack }) => {
setIsLinting(true);
setOutput({ status: 'idle', text: 'Running syntax & style lint...' });
try {
const res = await lintCode(currentChallenge.language.toLowerCase() || 'python', code);
const res = await lintCode(getMonacoLanguage(currentChallenge.language), code);
if (res.exit_code === 0) {
setOutput({ status: 'success', text: '✓ Syntax & Style clean! No linting errors detected.' });
} else {
@@ -237,7 +237,7 @@ const Workspace: React.FC<WorkspaceProps> = ({ challengeId, onBack }) => {
try {
const res = await getGuidance(
cid,
currentChallenge.language.toLowerCase() || 'python',
getMonacoLanguage(currentChallenge.language),
code,
q
);
@@ -519,7 +519,7 @@ const Workspace: React.FC<WorkspaceProps> = ({ challengeId, onBack }) => {
{/* Middle Column: Monaco Code Editor & Resizable Output Console */}
<div className="flex-1 flex flex-col bg-[#1e1e1e] overflow-hidden min-w-0 min-h-0">
<div className="bg-darkBg text-gray-400 px-4 py-2 text-xs flex justify-between items-center border-b border-darkBorder shrink-0">
<span className="font-mono text-blue-400">main.py</span>
<span className="font-mono text-blue-400">{getLanguageFileName(currentChallenge?.language)}</span>
<span className="text-[11px] text-gray-500">TactiTerm IDE</span>
</div>
@@ -527,8 +527,8 @@ const Workspace: React.FC<WorkspaceProps> = ({ challengeId, onBack }) => {
<div className="flex-1 min-h-0 relative">
<Editor
height="100%"
defaultLanguage={currentChallenge?.language.toLowerCase() || 'python'}
language={currentChallenge?.language.toLowerCase() || 'python'}
defaultLanguage={getMonacoLanguage(currentChallenge?.language)}
language={getMonacoLanguage(currentChallenge?.language)}
defaultValue=""
value={code}
theme="vs-dark"
+536 -175
View File
@@ -1,12 +1,191 @@
import type { Monaco } from '@monaco-editor/react';
export function getMonacoLanguage(language?: string): string {
if (!language) return 'python';
const norm = language.toLowerCase().trim();
const map: Record<string, string> = {
'c++': 'cpp',
'cpp': 'cpp',
'cxx': 'cpp',
'c#': 'csharp',
'cs': 'csharp',
'csharp': 'csharp',
'js': 'javascript',
'javascript': 'javascript',
'ts': 'typescript',
'typescript': 'typescript',
'py': 'python',
'python': 'python',
'rs': 'rust',
'rust': 'rust',
'golang': 'go',
'go': 'go',
'html': 'html',
'java': 'java',
'lua': 'lua',
};
return map[norm] || norm;
}
export function getLanguageFileName(language?: string): string {
const lang = getMonacoLanguage(language);
const extMap: Record<string, string> = {
python: 'main.py',
csharp: 'Program.cs',
cpp: 'main.cpp',
java: 'Main.java',
javascript: 'main.js',
typescript: 'main.ts',
rust: 'main.rs',
lua: 'main.lua',
html: 'index.html',
go: 'main.go',
};
return extMap[lang] || 'main.txt';
}
function extractDocumentSymbols(code: string): string[] {
const words = code.match(/[a-zA-Z_][a-zA-Z0-9_]*/g) || [];
const unique = new Set<string>();
const reserved = new Set([
'if', 'else', 'for', 'while', 'return', 'import', 'from', 'def', 'class',
'public', 'private', 'protected', 'static', 'void', 'int', 'double', 'float',
'bool', 'boolean', 'char', 'const', 'let', 'var', 'func', 'package', 'struct',
]);
for (const w of words) {
if (w.length > 2 && !reserved.has(w)) {
unique.add(w);
}
}
return Array.from(unique);
}
let registered = false;
export function registerMonacoCompletions(monaco: Monaco) {
if (registered) return;
registered = true;
// Completion items for Python
// ── JAVA COMPLETER ──────────────────────────────────────────
monaco.languages.registerCompletionItemProvider('java', {
triggerCharacters: ['.'],
provideCompletionItems: (model, position) => {
const lineUntilPosition = model.getValueInRange({
startLineNumber: position.lineNumber,
startColumn: 1,
endLineNumber: position.lineNumber,
endColumn: position.column,
});
const word = model.getWordUntilPosition(position);
const range = {
startLineNumber: position.lineNumber,
endLineNumber: position.lineNumber,
startColumn: word.startColumn,
endColumn: word.endColumn,
};
const code = model.getValue();
const dotMatch = lineUntilPosition.match(/([a-zA-Z_][a-zA-Z0-9_]*)\.\s*$/);
if (dotMatch) {
const objName = dotMatch[1];
const lowerName = objName.toLowerCase();
let targetType = 'unknown';
if (new RegExp(`Map<|HashMap<|TreeMap<`, 'i').test(code) && (lowerName.includes('map') || lowerName.includes('dict'))) {
targetType = 'map';
} else if (new RegExp(`List<|ArrayList<|LinkedList<`, 'i').test(code) && (lowerName.includes('list') || lowerName.includes('arr') || lowerName.includes('items'))) {
targetType = 'list';
} else if (new RegExp(`Set<|HashSet<`, 'i').test(code) && lowerName.includes('set')) {
targetType = 'set';
} else if (lowerName === 'out' || objName === 'System') {
targetType = 'system';
} else if (lowerName.includes('str') || lowerName.includes('text') || lowerName.includes('name') || lowerName.includes('msg')) {
targetType = 'string';
} else {
if (lowerName.includes('map') || lowerName.includes('dict')) targetType = 'map';
else if (lowerName.includes('list') || lowerName.includes('arr')) targetType = 'list';
else if (lowerName.includes('set')) targetType = 'set';
else targetType = 'general';
}
const mapMethods = [
{ label: 'put', insertText: 'put(${1:key}, ${2:value})', doc: 'Associates specified value with key in map.' },
{ label: 'get', insertText: 'get(${1:key})', doc: 'Returns value mapped to specified key.' },
{ label: 'containsKey', insertText: 'containsKey(${1:key})', doc: 'Returns true if map contains key.' },
{ label: 'containsValue', insertText: 'containsValue(${1:value})', doc: 'Returns true if map contains value.' },
{ label: 'size', insertText: 'size()', doc: 'Returns number of key-value mappings.' },
{ label: 'isEmpty', insertText: 'isEmpty()', doc: 'Returns true if map contains no mappings.' },
{ label: 'keySet', insertText: 'keySet()', doc: 'Returns Set view of keys in map.' },
{ label: 'values', insertText: 'values()', doc: 'Returns Collection view of values in map.' },
{ label: 'entrySet', insertText: 'entrySet()', doc: 'Returns Set view of mappings in map.' },
{ label: 'remove', insertText: 'remove(${1:key})', doc: 'Removes mapping for key.' },
{ label: 'clear', insertText: 'clear()', doc: 'Removes all mappings from map.' },
{ label: 'getOrDefault', insertText: 'getOrDefault(${1:key}, ${2:defaultValue})', doc: 'Returns mapped value or default.' },
];
const listMethods = [
{ label: 'add', insertText: 'add(${1:element})', doc: 'Appends element to end of list.' },
{ label: 'get', insertText: 'get(${1:index})', doc: 'Returns element at index.' },
{ label: 'size', insertText: 'size()', doc: 'Returns number of elements.' },
{ label: 'remove', insertText: 'remove(${1:index})', doc: 'Removes element at index.' },
{ label: 'contains', insertText: 'contains(${1:element})', doc: 'Returns true if list contains element.' },
{ label: 'indexOf', insertText: 'indexOf(${1:element})', doc: 'Returns index of element.' },
{ label: 'isEmpty', insertText: 'isEmpty()', doc: 'Returns true if list contains no elements.' },
{ label: 'clear', insertText: 'clear()', doc: 'Removes all elements.' },
];
const stringMethods = [
{ label: 'length', insertText: 'length()', doc: 'Returns length of string.' },
{ label: 'substring', insertText: 'substring(${1:beginIndex})', doc: 'Returns substring starting at beginIndex.' },
{ label: 'charAt', insertText: 'charAt(${1:index})', doc: 'Returns char value at index.' },
{ label: 'toLowerCase', insertText: 'toLowerCase()', doc: 'Converts to lowercase.' },
{ label: 'toUpperCase', insertText: 'toUpperCase()', doc: 'Converts to uppercase.' },
{ label: 'trim', insertText: 'trim()', doc: 'Removes leading/trailing whitespace.' },
{ label: 'split', insertText: 'split("${1:regex}")', doc: 'Splits string around matches.' },
{ label: 'contains', insertText: 'contains("${1:str}")', doc: 'Returns true if contains string.' },
{ label: 'startsWith', insertText: 'startsWith("${1:prefix}")', doc: 'Checks if starts with prefix.' },
{ label: 'equals', insertText: 'equals(${1:anObject})', doc: 'Compares to specified object.' },
];
const systemMethods = [
{ label: 'println', insertText: 'println(${1:value});', doc: 'Prints value and terminates line.' },
{ label: 'printf', insertText: 'printf("${1:%s}\\n", ${2:args});', doc: 'Prints formatted string.' },
{ label: 'print', insertText: 'print(${1:value});', doc: 'Prints value.' },
];
let selected = mapMethods;
if (targetType === 'list') selected = listMethods;
else if (targetType === 'string') selected = stringMethods;
else if (targetType === 'system') selected = systemMethods;
else if (targetType === 'general') selected = [...mapMethods, ...listMethods, ...stringMethods];
return {
suggestions: selected.map(m => ({
label: m.label,
kind: monaco.languages.CompletionItemKind.Method,
insertText: m.insertText,
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
documentation: m.doc,
range,
})),
};
}
const symbols = extractDocumentSymbols(code);
const suggestions = [
{ label: 'System.out.println', kind: monaco.languages.CompletionItemKind.Snippet, insertText: 'System.out.println(${1:value});', range },
{ label: 'HashMap', kind: monaco.languages.CompletionItemKind.Class, insertText: 'Map<${1:String}, ${2:Integer}> ${3:map} = new HashMap<>();', range },
{ label: 'ArrayList', kind: monaco.languages.CompletionItemKind.Class, insertText: 'List<${1:String}> ${2:list} = new ArrayList<>();', range },
...symbols.map(s => ({ label: s, kind: monaco.languages.CompletionItemKind.Variable, insertText: s, range })),
];
return { suggestions };
},
});
// ── PYTHON COMPLETER ────────────────────────────────────────
monaco.languages.registerCompletionItemProvider('python', {
triggerCharacters: ['.'],
provideCompletionItems: (model, position) => {
@@ -25,70 +204,194 @@ export function registerMonacoCompletions(monaco: Monaco) {
endColumn: word.endColumn,
};
const code = model.getValue();
const dotMatch = lineUntilPosition.match(/([a-zA-Z_][a-zA-Z0-9_]*)\.\s*$/);
if (dotMatch) {
const objName = dotMatch[1].toLowerCase();
let methods = [
{ label: 'get', insertText: 'get(${1:key})', doc: 'Returns value for key.' },
{ label: 'keys', insertText: 'keys()', doc: 'Returns dictionary keys.' },
{ label: 'values', insertText: 'values()', doc: 'Returns dictionary values.' },
{ label: 'items', insertText: 'items()', doc: 'Returns key-value pairs.' },
{ label: 'append', insertText: 'append(${1:item})', doc: 'Appends item to list.' },
{ label: 'pop', insertText: 'pop(${1:index})', doc: 'Removes and returns item.' },
{ label: 'split', insertText: 'split("${1:sep}")', doc: 'Splits string.' },
{ label: 'join', insertText: 'join(${1:iterable})', doc: 'Joins elements.' },
];
if (objName.includes('map') || objName.includes('dict')) {
methods = [
{ label: 'get', insertText: 'get(${1:key})', doc: 'Returns value for key.' },
{ label: 'keys', insertText: 'keys()', doc: 'Returns dictionary keys.' },
{ label: 'values', insertText: 'values()', doc: 'Returns dictionary values.' },
{ label: 'items', insertText: 'items()', doc: 'Returns key-value pairs.' },
{ label: 'update', insertText: 'update(${1:dict})', doc: 'Updates dictionary.' },
{ label: 'pop', insertText: 'pop(${1:key})', doc: 'Removes key.' },
];
} else if (objName.includes('list') || objName.includes('arr')) {
methods = [
{ label: 'append', insertText: 'append(${1:item})', doc: 'Appends item.' },
{ label: 'extend', insertText: 'extend(${1:iterable})', doc: 'Extends list.' },
{ label: 'insert', insertText: 'insert(${1:index}, ${2:item})', doc: 'Inserts item.' },
{ label: 'remove', insertText: 'remove(${1:item})', doc: 'Removes item.' },
{ label: 'sort', insertText: 'sort()', doc: 'Sorts list.' },
];
}
return {
suggestions: methods.map(m => ({
label: m.label,
kind: monaco.languages.CompletionItemKind.Method,
insertText: m.insertText,
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
documentation: m.doc,
range,
})),
};
}
const symbols = extractDocumentSymbols(code);
const suggestions = [
{
label: 'print',
kind: monaco.languages.CompletionItemKind.Function,
insertText: 'print(${1:value})',
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
documentation: 'Prints specified objects to standard output.',
range,
},
{
label: 'len',
kind: monaco.languages.CompletionItemKind.Function,
insertText: 'len(${1:obj})',
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
documentation: 'Returns the number of items in a container.',
range,
},
{
label: 'range',
kind: monaco.languages.CompletionItemKind.Function,
insertText: 'range(${1:stop})',
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
documentation: 'Generates a sequence of numbers.',
range,
},
{
label: 'enumerate',
kind: monaco.languages.CompletionItemKind.Function,
insertText: 'enumerate(${1:iterable})',
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
documentation: 'Yields index and value tuples from an iterable.',
range,
},
{
label: 'append',
kind: monaco.languages.CompletionItemKind.Method,
insertText: 'append(${1:item})',
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
documentation: 'Appends a new item to the end of the list.',
range,
},
{
label: 'split',
kind: monaco.languages.CompletionItemKind.Method,
insertText: 'split(${1:sep})',
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
documentation: 'Splits string into a list of substrings.',
range,
},
{
label: 'def',
kind: monaco.languages.CompletionItemKind.Keyword,
insertText: 'def ${1:function_name}(${2:params}):\n ${3:pass}',
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
documentation: 'Defines a Python function.',
range,
},
{ label: 'print', kind: monaco.languages.CompletionItemKind.Function, insertText: 'print(${1:value})', range },
{ label: 'len', kind: monaco.languages.CompletionItemKind.Function, insertText: 'len(${1:obj})', range },
{ label: 'range', kind: monaco.languages.CompletionItemKind.Function, insertText: 'range(${1:stop})', range },
{ label: 'enumerate', kind: monaco.languages.CompletionItemKind.Function, insertText: 'enumerate(${1:iterable})', range },
...symbols.map(s => ({ label: s, kind: monaco.languages.CompletionItemKind.Variable, insertText: s, range })),
];
return { suggestions };
},
});
// Completion items for JavaScript / TypeScript
// ── C++ COMPLETER ───────────────────────────────────────────
monaco.languages.registerCompletionItemProvider('cpp', {
triggerCharacters: ['.', ':', '>'],
provideCompletionItems: (model, position) => {
const lineUntilPosition = model.getValueInRange({
startLineNumber: position.lineNumber,
startColumn: 1,
endLineNumber: position.lineNumber,
endColumn: position.column,
});
const word = model.getWordUntilPosition(position);
const range = {
startLineNumber: position.lineNumber,
endLineNumber: position.lineNumber,
startColumn: word.startColumn,
endColumn: word.endColumn,
};
const code = model.getValue();
const dotMatch = lineUntilPosition.match(/([a-zA-Z_][a-zA-Z0-9_]*|\bstd\b)(\.|\:\:|->)\s*$/);
if (dotMatch) {
const objName = dotMatch[1].toLowerCase();
let methods = [
{ label: 'push_back', insertText: 'push_back(${1:val})', doc: 'Appends element.' },
{ label: 'pop_back', insertText: 'pop_back()', doc: 'Removes last element.' },
{ label: 'size', insertText: 'size()', doc: 'Returns number of elements.' },
{ label: 'empty', insertText: 'empty()', doc: 'Checks if container is empty.' },
{ label: 'find', insertText: 'find(${1:key})', doc: 'Finds element.' },
{ label: 'insert', insertText: 'insert(${1:val})', doc: 'Inserts element.' },
{ label: 'clear', insertText: 'clear()', doc: 'Clears all elements.' },
];
if (objName === 'std') {
methods = [
{ label: 'cout', insertText: 'cout << ${1:value} << std::endl;', doc: 'Standard output stream.' },
{ label: 'cin', insertText: 'cin >> ${1:var};', doc: 'Standard input stream.' },
{ label: 'vector', insertText: 'vector<${1:int}> ${2:vec};', doc: 'Dynamic array container.' },
{ label: 'map', insertText: 'map<${1:string}, ${2:int}> ${3:map};', doc: 'Sorted associative container.' },
{ label: 'sort', insertText: 'sort(${1:begin}, ${2:end});', doc: 'Sorts elements.' },
];
}
return {
suggestions: methods.map(m => ({
label: m.label,
kind: monaco.languages.CompletionItemKind.Method,
insertText: m.insertText,
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
documentation: m.doc,
range,
})),
};
}
const symbols = extractDocumentSymbols(code);
return {
suggestions: [
{ label: 'std::cout', kind: monaco.languages.CompletionItemKind.Snippet, insertText: 'std::cout << ${1:value} << std::endl;', range },
{ label: 'std::vector', kind: monaco.languages.CompletionItemKind.Class, insertText: 'std::vector<${1:int}> ${2:vec};', range },
...symbols.map(s => ({ label: s, kind: monaco.languages.CompletionItemKind.Variable, insertText: s, range })),
],
};
},
});
// ── C# COMPLETER ────────────────────────────────────────────
monaco.languages.registerCompletionItemProvider('csharp', {
triggerCharacters: ['.'],
provideCompletionItems: (model, position) => {
const lineUntilPosition = model.getValueInRange({
startLineNumber: position.lineNumber,
startColumn: 1,
endLineNumber: position.lineNumber,
endColumn: position.column,
});
const word = model.getWordUntilPosition(position);
const range = {
startLineNumber: position.lineNumber,
endLineNumber: position.lineNumber,
startColumn: word.startColumn,
endColumn: word.endColumn,
};
const code = model.getValue();
const dotMatch = lineUntilPosition.match(/([a-zA-Z_][a-zA-Z0-9_]*)\.\s*$/);
if (dotMatch) {
const objName = dotMatch[1].toLowerCase();
let methods = [
{ label: 'Add', insertText: 'Add(${1:val});', doc: 'Adds value.' },
{ label: 'Remove', insertText: 'Remove(${1:val});', doc: 'Removes value.' },
{ label: 'ContainsKey', insertText: 'ContainsKey(${1:key})', doc: 'Checks if dictionary contains key.' },
{ label: 'Count', insertText: 'Count', doc: 'Gets number of elements.' },
{ label: 'Clear', insertText: 'Clear()', doc: 'Removes all elements.' },
];
if (objName === 'console') {
methods = [
{ label: 'WriteLine', insertText: 'WriteLine(${1:value});', doc: 'Writes value to standard output.' },
{ label: 'ReadLine', insertText: 'ReadLine()', doc: 'Reads next line.' },
{ label: 'Write', insertText: 'Write(${1:value});', doc: 'Writes value.' },
];
}
return {
suggestions: methods.map(m => ({
label: m.label,
kind: monaco.languages.CompletionItemKind.Method,
insertText: m.insertText,
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
documentation: m.doc,
range,
})),
};
}
const symbols = extractDocumentSymbols(code);
return {
suggestions: [
{ label: 'Console.WriteLine', kind: monaco.languages.CompletionItemKind.Method, insertText: 'Console.WriteLine(${1:value});', range },
{ label: 'Dictionary', kind: monaco.languages.CompletionItemKind.Class, insertText: 'Dictionary<${1:string}, ${2:int}> ${3:dict} = new Dictionary<${1:string}, ${2:int}>();', range },
...symbols.map(s => ({ label: s, kind: monaco.languages.CompletionItemKind.Variable, insertText: s, range })),
],
};
},
});
// ── JS & TS COMPLETER ───────────────────────────────────────
['javascript', 'typescript'].forEach((lang) => {
monaco.languages.registerCompletionItemProvider(lang, {
triggerCharacters: ['.'],
@@ -108,73 +411,62 @@ export function registerMonacoCompletions(monaco: Monaco) {
endColumn: word.endColumn,
};
if (lineUntilPosition.endsWith('console.')) {
const code = model.getValue();
const dotMatch = lineUntilPosition.match(/([a-zA-Z_][a-zA-Z0-9_]*)\.\s*$/);
if (dotMatch) {
const objName = dotMatch[1].toLowerCase();
let methods = [
{ label: 'map', insertText: 'map((${1:x}) => ${2:x})', doc: 'Creates a new array with mapped elements.' },
{ label: 'filter', insertText: 'filter((${1:x}) => ${2:true})', doc: 'Filters array.' },
{ label: 'push', insertText: 'push(${1:item})', doc: 'Appends element.' },
{ label: 'slice', insertText: 'slice(${1:start}, ${2:end})', doc: 'Returns section of array.' },
{ label: 'length', insertText: 'length', doc: 'Gets length.' },
{ label: 'get', insertText: 'get(${1:key})', doc: 'Gets Map element.' },
{ label: 'set', insertText: 'set(${1:key}, ${2:val})', doc: 'Sets Map element.' },
];
if (objName === 'console') {
methods = [
{ label: 'log', insertText: 'log(${1:msg});', doc: 'Outputs message.' },
{ label: 'error', insertText: 'error(${1:err});', doc: 'Outputs error.' },
{ label: 'warn', insertText: 'warn(${1:msg});', doc: 'Outputs warning.' },
];
}
return {
suggestions: [
{
label: 'log',
kind: monaco.languages.CompletionItemKind.Method,
insertText: 'log(${1:message});',
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
documentation: 'Outputs a message to the debugging console.',
range,
},
{
label: 'error',
kind: monaco.languages.CompletionItemKind.Method,
insertText: 'error(${1:err});',
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
documentation: 'Outputs an error message to the console.',
range,
},
{
label: 'warn',
kind: monaco.languages.CompletionItemKind.Method,
insertText: 'warn(${1:msg});',
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
documentation: 'Outputs a warning message to the console.',
range,
},
],
suggestions: methods.map(m => ({
label: m.label,
kind: monaco.languages.CompletionItemKind.Method,
insertText: m.insertText,
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
documentation: m.doc,
range,
})),
};
}
const suggestions = [
{
label: 'console.log',
kind: monaco.languages.CompletionItemKind.Snippet,
insertText: 'console.log(${1:val});',
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
documentation: 'Print debug message to console.',
range,
},
{
label: 'map',
kind: monaco.languages.CompletionItemKind.Method,
insertText: 'map((${1:item}) => ${2:item})',
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
documentation: 'Array map transformation function.',
range,
},
{
label: 'filter',
kind: monaco.languages.CompletionItemKind.Method,
insertText: 'filter((${1:item}) => ${2:true})',
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
documentation: 'Array filter function.',
range,
},
];
return { suggestions };
const symbols = extractDocumentSymbols(code);
return {
suggestions: [
{ label: 'console.log', kind: monaco.languages.CompletionItemKind.Snippet, insertText: 'console.log(${1:val});', range },
...symbols.map(s => ({ label: s, kind: monaco.languages.CompletionItemKind.Variable, insertText: s, range })),
],
};
},
});
});
// Completion items for Rust
// ── RUST COMPLETER ──────────────────────────────────────────
monaco.languages.registerCompletionItemProvider('rust', {
triggerCharacters: ['.', ':'],
provideCompletionItems: (model, position) => {
const lineUntilPosition = model.getValueInRange({
startLineNumber: position.lineNumber,
startColumn: 1,
endLineNumber: position.lineNumber,
endColumn: position.column,
});
const word = model.getWordUntilPosition(position);
const range = {
startLineNumber: position.lineNumber,
@@ -183,38 +475,43 @@ export function registerMonacoCompletions(monaco: Monaco) {
endColumn: word.endColumn,
};
const suggestions = [
{
label: 'println!',
kind: monaco.languages.CompletionItemKind.Function,
insertText: 'println!("${1:{}}", ${2:val});',
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
documentation: 'Prints formatted text to stdout.',
range,
},
{
label: 'format!',
kind: monaco.languages.CompletionItemKind.Function,
insertText: 'format!("${1:{}}", ${2:val})',
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
documentation: 'Constructs a formatted String.',
range,
},
{
label: 'Vec::push',
kind: monaco.languages.CompletionItemKind.Method,
insertText: 'push(${1:val});',
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
documentation: 'Appends element to back of vector.',
range,
},
];
const code = model.getValue();
const dotMatch = lineUntilPosition.match(/([a-zA-Z_][a-zA-Z0-9_]*|\bVec|\bHashMap)(\.|\:\:)\s*$/);
return { suggestions };
if (dotMatch) {
let methods = [
{ label: 'push', insertText: 'push(${1:val});', doc: 'Appends element.' },
{ label: 'insert', insertText: 'insert(${1:key}, ${2:val});', doc: 'Inserts key-value pair.' },
{ label: 'get', insertText: 'get(&${1:key})', doc: 'Returns reference to value.' },
{ label: 'len', insertText: 'len()', doc: 'Returns length.' },
{ label: 'is_empty', insertText: 'is_empty()', doc: 'Checks if empty.' },
{ label: 'iter', insertText: 'iter()', doc: 'Returns iterator.' },
];
return {
suggestions: methods.map(m => ({
label: m.label,
kind: monaco.languages.CompletionItemKind.Method,
insertText: m.insertText,
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
documentation: m.doc,
range,
})),
};
}
const symbols = extractDocumentSymbols(code);
return {
suggestions: [
{ label: 'println!', kind: monaco.languages.CompletionItemKind.Snippet, insertText: 'println!("${1:{}}", ${2:val});', range },
{ label: 'Vec::new', kind: monaco.languages.CompletionItemKind.Function, insertText: 'Vec::new()', range },
...symbols.map(s => ({ label: s, kind: monaco.languages.CompletionItemKind.Variable, insertText: s, range })),
],
};
},
});
// Completion items for Go
// ── GO COMPLETER ────────────────────────────────────────────
monaco.languages.registerCompletionItemProvider('go', {
triggerCharacters: ['.'],
provideCompletionItems: (model, position) => {
@@ -233,49 +530,113 @@ export function registerMonacoCompletions(monaco: Monaco) {
endColumn: word.endColumn,
};
if (lineUntilPosition.endsWith('fmt.')) {
const code = model.getValue();
const dotMatch = lineUntilPosition.match(/([a-zA-Z_][a-zA-Z0-9_]*)\.\s*$/);
if (dotMatch) {
let methods = [
{ label: 'Println', insertText: 'Println(${1:v})', doc: 'Writes formatted line.' },
{ label: 'Printf', insertText: 'Printf("${1:%v}\\n", ${2:v})', doc: 'Writes formatted string.' },
{ label: 'Split', insertText: 'Split(${1:s}, "${2:sep}")', doc: 'Splits string.' },
];
return {
suggestions: [
{
label: 'Println',
kind: monaco.languages.CompletionItemKind.Function,
insertText: 'Println(${1:v})',
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
documentation: 'Formats using default formats and writes to standard output.',
range,
},
{
label: 'Printf',
kind: monaco.languages.CompletionItemKind.Function,
insertText: 'Printf("${1:%v}\\n", ${2:v})',
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
documentation: 'Formats according to specifier and writes to standard output.',
range,
},
],
suggestions: methods.map(m => ({
label: m.label,
kind: monaco.languages.CompletionItemKind.Method,
insertText: m.insertText,
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
documentation: m.doc,
range,
})),
};
}
const symbols = extractDocumentSymbols(code);
return {
suggestions: [
{
label: 'fmt.Println',
kind: monaco.languages.CompletionItemKind.Snippet,
insertText: 'fmt.Println(${1:v})',
{ label: 'fmt.Println', kind: monaco.languages.CompletionItemKind.Snippet, insertText: 'fmt.Println(${1:v})', range },
{ label: 'make', kind: monaco.languages.CompletionItemKind.Function, insertText: 'make(${1:type}, ${2:len})', range },
...symbols.map(s => ({ label: s, kind: monaco.languages.CompletionItemKind.Variable, insertText: s, range })),
],
};
},
});
// ── LUA COMPLETER ───────────────────────────────────────────
monaco.languages.registerCompletionItemProvider('lua', {
triggerCharacters: ['.'],
provideCompletionItems: (model, position) => {
const lineUntilPosition = model.getValueInRange({
startLineNumber: position.lineNumber,
startColumn: 1,
endLineNumber: position.lineNumber,
endColumn: position.column,
});
const word = model.getWordUntilPosition(position);
const range = {
startLineNumber: position.lineNumber,
endLineNumber: position.lineNumber,
startColumn: word.startColumn,
endColumn: word.endColumn,
};
const code = model.getValue();
const dotMatch = lineUntilPosition.match(/([a-zA-Z_][a-zA-Z0-9_]*)\.\s*$/);
if (dotMatch) {
let methods = [
{ label: 'insert', insertText: 'insert(${1:t}, ${2:val})', doc: 'Inserts element.' },
{ label: 'remove', insertText: 'remove(${1:t}, ${2:pos})', doc: 'Removes element.' },
{ label: 'sub', insertText: 'sub(${1:s}, ${2:i}, ${3:j})', doc: 'Returns substring.' },
];
return {
suggestions: methods.map(m => ({
label: m.label,
kind: monaco.languages.CompletionItemKind.Method,
insertText: m.insertText,
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
documentation: 'Print line to stdout.',
documentation: m.doc,
range,
},
{
label: 'make',
kind: monaco.languages.CompletionItemKind.Function,
insertText: 'make(${1:type}, ${2:len})',
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
documentation: 'Allocates slice, map, or channel.',
range,
},
})),
};
}
const symbols = extractDocumentSymbols(code);
return {
suggestions: [
{ label: 'print', kind: monaco.languages.CompletionItemKind.Function, insertText: 'print(${1:value})', range },
...symbols.map(s => ({ label: s, kind: monaco.languages.CompletionItemKind.Variable, insertText: s, range })),
],
};
},
});
// ── HTML COMPLETER ──────────────────────────────────────────
monaco.languages.registerCompletionItemProvider('html', {
triggerCharacters: ['<'],
provideCompletionItems: (model, position) => {
const word = model.getWordUntilPosition(position);
const range = {
startLineNumber: position.lineNumber,
endLineNumber: position.lineNumber,
startColumn: word.startColumn,
endColumn: word.endColumn,
};
return {
suggestions: [
{ label: 'div', kind: monaco.languages.CompletionItemKind.Snippet, insertText: '<div>${1}</div>', range },
{ label: 'span', kind: monaco.languages.CompletionItemKind.Snippet, insertText: '<span>${1}</span>', range },
{ label: 'p', kind: monaco.languages.CompletionItemKind.Snippet, insertText: '<p>${1}</p>', range },
{ label: 'h1', kind: monaco.languages.CompletionItemKind.Snippet, insertText: '<h1>${1}</h1>', range },
{ label: 'button', kind: monaco.languages.CompletionItemKind.Snippet, insertText: '<button>${1:Click}</button>', range },
],
};
},
});
}