From fd82946e7f256324c9f3ddbdc6bec63763046abb Mon Sep 17 00:00:00 2001 From: Kishikawa Katsumi Date: Fri, 12 Jun 2026 04:46:17 +0900 Subject: [PATCH 1/3] Improve completion triggering and add parameter hints (signature help) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Completion previously only auto-triggered after "." and relied on Monaco defaults that weren't configured. Make it behave more like Xcode: - Editor options: quickSuggestions on (code), quickSuggestionsDelay 0, suggestOnTriggerCharacters, tabCompletion — so suggestions pop while typing. - More completion trigger characters: "(", ":", "<", " ", "," in addition to ".". - Explicit Ctrl+Space action bound to editor.action.triggerSuggest (Cmd+Space is taken by Spotlight on macOS, so bind Control). Add Signature Help (parameter hints): - Register a Monaco signatureHelpProvider (trigger on "(" and ","). - New requestSignatureHelp() sends the `signatureHelp` method to the backend and the response is mapped to Monaco's SignatureHelp shape. Also fix a pre-existing bug: the `completion` case in the response handler was missing a `break` and fell through into `diagnostics`, clearing the diagnostic markers on every completion. Co-Authored-By: Claude Opus 4.8 (1M context) --- Public/js/app.js | 47 ++++++++++++++++++++++++++++++++++++ Public/js/editor.js | 26 +++++++++++++++++++- Public/js/language_server.js | 11 +++++++++ Public/js/main_view.js | 6 +++++ 4 files changed, 89 insertions(+), 1 deletion(-) diff --git a/Public/js/app.js b/Public/js/app.js index 26468cc0..673673cc 100644 --- a/Public/js/app.js +++ b/Public/js/app.js @@ -118,6 +118,38 @@ export class App { } else { promise.fulfill(); } + break; + case "signatureHelp": { + if (!promise) { + return; + } + if (response.value && response.value.signatures) { + const signatures = response.value.signatures.map((sig) => ({ + label: sig.label, + documentation: sig.documentation + ? sig.documentation.value ?? sig.documentation + : undefined, + parameters: (sig.parameters || []).map((parameter) => ({ + label: parameter.label, + documentation: parameter.documentation + ? parameter.documentation.value ?? parameter.documentation + : undefined, + })), + activeParameter: sig.activeParameter, + })); + promise.fulfill({ + value: { + signatures: signatures, + activeSignature: response.value.activeSignature ?? 0, + activeParameter: response.value.activeParameter ?? 0, + }, + dispose: () => {}, + }); + } else { + promise.fulfill(); + } + break; + } case "diagnostics": this.updateLanguageServerStatus(true); this.editor.clearMarkers(); @@ -229,6 +261,21 @@ export class App { return promise; }; + this.editor.onsignaturehelp = (position) => { + if (!languageServer.isReady) { + return; + } + + sequence++; + const row = position.lineNumber - 1; + const column = position.column - 1; + languageServer.requestSignatureHelp(sequence, row, column); + + return new Promise((fulfill, reject) => { + promises[sequence] = { fulfill: fulfill, reject: reject }; + }); + }; + this.editor.focus(); this.editor.scrollToBottm(); diff --git a/Public/js/editor.js b/Public/js/editor.js index c4d8997d..d7057ae0 100644 --- a/Public/js/editor.js +++ b/Public/js/editor.js @@ -33,16 +33,40 @@ export class Editor { }, }); + // Trigger completion after "." as well as after these characters, which + // commonly start a new identifier/argument in Swift. monaco.languages.registerCompletionItemProvider("swift", { - triggerCharacters: ["."], + triggerCharacters: [".", "(", ":", "<", " ", ","], provideCompletionItems: (model, position) => { return this.oncompletion(position); }, }); + // Parameter hints (Xcode-style): pops up the function signature with the + // active argument highlighted when typing "(" or ",". + monaco.languages.registerSignatureHelpProvider("swift", { + signatureHelpTriggerCharacters: ["(", ","], + signatureHelpRetriggerCharacters: [",", ")"], + provideSignatureHelp: (model, position) => { + return this.onsignaturehelp(position); + }, + }); + + // Explicit Ctrl+Space to trigger completion, like Xcode. (Cmd+Space is + // taken by Spotlight on macOS, so bind WinCtrl = the Control key.) + this.editor.addAction({ + id: "trigger-suggest", + label: "Trigger Suggest", + keybindings: [monaco.KeyMod.WinCtrl | monaco.KeyCode.Space], + run: (ed) => { + ed.trigger("keyboard", "editor.action.triggerSuggest", {}); + }, + }); + this.onchange = () => {}; this.onhover = () => {}; this.oncompletion = () => {}; + this.onsignaturehelp = () => {}; this.onaction = () => {}; } diff --git a/Public/js/language_server.js b/Public/js/language_server.js index b3b4184d..3db6838c 100644 --- a/Public/js/language_server.js +++ b/Public/js/language_server.js @@ -58,6 +58,17 @@ export class LanguageServer { this.connection.send(JSON.stringify(params)); } + requestSignatureHelp(sequence, row, column) { + const params = { + method: "signatureHelp", + id: sequence, + row: row, + column: column, + sessionId: this.sessionId, + }; + this.connection.send(JSON.stringify(params)); + } + requestFormat(code) { const params = { method: "format", diff --git a/Public/js/main_view.js b/Public/js/main_view.js index 20bc2650..99778979 100644 --- a/Public/js/main_view.js +++ b/Public/js/main_view.js @@ -28,6 +28,12 @@ export class MainView { wordWrap: "on", wrappingIndent: "indent", tabSize: 2, + // Make completion pop up while typing (not only after "."), with no + // delay, and allow Tab to accept. Ctrl+Space still triggers it manually. + quickSuggestions: { other: true, comments: false, strings: false }, + quickSuggestionsDelay: 0, + suggestOnTriggerCharacters: true, + tabCompletion: "on", lightbulb: { enabled: true, }, From c08244773376a888158028717bac2c66ac5f4aae Mon Sep 17 00:00:00 2001 From: Kishikawa Katsumi Date: Fri, 12 Jun 2026 05:02:55 +0900 Subject: [PATCH 2/3] Clear the promises entry once a response is handled MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `promises` is keyed by an ever-incrementing sequence and nothing removed entries, so it grew unbounded — now worse since completion fires on every keystroke (quick suggestions + more trigger characters) and signature help fires inside calls. Delete the entry right after retrieving it in onresponse, covering hover/completion/signatureHelp at once (no-op for diagnostics/format). Co-Authored-By: Claude Opus 4.8 (1M context) --- Public/js/app.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Public/js/app.js b/Public/js/app.js index 673673cc..3627a3ea 100644 --- a/Public/js/app.js +++ b/Public/js/app.js @@ -64,6 +64,10 @@ export class App { languageServer.onresponse = (response) => { const promise = promises[response.id]; + // Each request gets exactly one response, so drop the entry now. Without + // this, `promises` grows unbounded as hover/completion/signatureHelp fire + // while typing. No-op for diagnostics/format, which carry no id. + delete promises[response.id]; switch (response.method) { case "hover": if (!promise) { From 6ea0c40e7132776d4c2c5f61b277f78e8da0730c Mon Sep 17 00:00:00 2001 From: Kishikawa Katsumi Date: Fri, 12 Jun 2026 05:08:03 +0900 Subject: [PATCH 3/3] Drop space from completion trigger characters MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A literal space triggered the completion provider (and a backend request) on every whitespace keystroke — very frequent and returning a large, unfiltered list. quickSuggestions already shows suggestions once the next identifier is typed, so the space trigger only added wasteful traffic. Co-Authored-By: Claude Opus 4.8 (1M context) --- Public/js/editor.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Public/js/editor.js b/Public/js/editor.js index d7057ae0..b9163466 100644 --- a/Public/js/editor.js +++ b/Public/js/editor.js @@ -34,9 +34,12 @@ export class Editor { }); // Trigger completion after "." as well as after these characters, which - // commonly start a new identifier/argument in Swift. + // commonly start a new identifier/argument in Swift. Whitespace is + // intentionally excluded: it's typed constantly and quickSuggestions + // already pops the list once the next identifier is started, so a space + // trigger would only generate heavy, unfiltered requests. monaco.languages.registerCompletionItemProvider("swift", { - triggerCharacters: [".", "(", ":", "<", " ", ","], + triggerCharacters: [".", "(", ":", "<", ","], provideCompletionItems: (model, position) => { return this.oncompletion(position); },