diff --git a/app/client/components/AceEditorCompletions.ts b/app/client/components/AceEditorCompletions.ts index 81094f94..b66fee4f 100644 --- a/app/client/components/AceEditorCompletions.ts +++ b/app/client/components/AceEditorCompletions.ts @@ -106,10 +106,26 @@ function initCustomCompleter() { aceLanguageTools.addCompleter({ // For autocompletion we ship text to the sandbox and run standard completion there. async getCompletions( - editor: ace.Editor, session: ace.IEditSession, pos: ace.Position, prefix: string, callback: any + editor: ace.Editor, + session: ace.IEditSession, + pos: ace.Position, + prefix: string, + callback: any ) { const options = completionOptions.get(editor); if (!options || prefix.length === 0) { callback(null, []); return; } + + // Autocompletion can be triggered in the middle of a function or method call, like + // in the case where one function is being switched with another. Since we normally + // append a "(" when completing such suggestions, we need to be careful not to do + // so if a "(" is already present. One way to do this in ACE is to check if the + // current token is an identifier, and the next token is a lparen; if both are true, + // we skip appending a "(" to each suggestion. + const wordRange = session.getWordRange(pos.row, pos.column); + const token = session.getTokenAt(pos.row, wordRange.end.column) as TokenInfo; + const nextToken = session.getTokenAt(pos.row, wordRange.end.column + 1) as TokenInfo|null; + const isRenamingFunc = token.type === 'identifier' && nextToken?.type === 'paren.lparen'; + const suggestions = await options.getSuggestions(prefix); // ACE autocompletions are very poorly documented. This is somewhat helpful: // https://prog.world/implementing-code-completion-in-ace-editor/ @@ -118,7 +134,7 @@ function initCustomCompleter() { if (Array.isArray(suggestion)) { const [funcname, argSpec] = suggestion; return { - value: funcname + '(', + value: funcname + (isRenamingFunc ? '' : '('), caption: funcname + argSpec, score: 1, example,