Support InsertReplaceEdit and keep existing arguments when completing function calls#2676
Support InsertReplaceEdit and keep existing arguments when completing function calls#2676Steffeeen wants to merge 2 commits into
InsertReplaceEdit and keep existing arguments when completing function calls#2676Conversation
ahoppen
left a comment
There was a problem hiding this comment.
Very nice improvement. Thanks, Steffen!
| return nil | ||
| } | ||
|
|
||
| if case .identifier(_) = token.tokenKind { |
There was a problem hiding this comment.
Should we also check for keywords here?
There was a problem hiding this comment.
I suppose this would be relevant when trying to complete a function that contains a keyword as part of its name, e.g. selfTest and the editor only contains the self part? Or is there any other use case that I'm missing?
| XCTAssertEqual(textEdit.newText, "foobar(x: , y: )") | ||
| } | ||
|
|
||
| func testFunctionCompletionDoesNotOverwriteMultilineParens() async throws { |
There was a problem hiding this comment.
A lot of these tests have the exact same structure. Would it make sense to add a test helper like assertOnlyCompletionEdit that takes a source file, a position marker at which to invoke completion and checks that it matches an expected text edit?
I haven’t fully looked into it but it might also be possible to design a helper in such a way that you don’t need the local assert functions below.
There was a problem hiding this comment.
I agree that currently the tests all look really similar. I didn't introduce a newer, more general, helper as most of the other existing tests should also be changed to use that new helper and I considered that to be out of scope for this PR. Maybe I will look into this in a follow-up PR.
But I now added a helper to remove the inner assert... functions.
If the user invokes code completion in the middle of an identifier and the client supports `InsertReplaceEdit`s the client can now let the user choose between inserting the completion or replacing the existing rest of the identifier.
With this commit, we no longer insert the parentheses or trailing closure when completing a function call (or something that looks similar to a function call) if they are already present.
197e2d0 to
24f6a35
Compare
This PR implements two closely related features for code completion.
InsertReplaceEditSupportFirst, it adds support for
InsertReplaceEditallowing the user to choose between inserting (Enter in VSCode) the function name or replacing (Tab in VSCode) the existing rest of the identifier if the completion was invoked in the middle of an identifier. This often happens when changing which function is called. Example showing the difference:Screen.Recording.2026-05-29.at.15.43.22.mov
Reusing Existing Parentheses
The second feature this PR implements is not inserting parentheses or braces if they are already present in the document. This is useful in general, but was especially needed for the use case of changing which function is called. This example shows what happens with just the
InsertReplaceEditchanges:Screen.Recording.2026-05-29.at.15.47.46.mov
Compare this to the behavior when using the changes from both commits:
Screen.Recording.2026-05-29.at.15.49.37.mov
Below is an example of the general case of completing a function call with already present parentheses. Previously, the parentheses and argument labels would always be inserted and the user would end up with
testabc(x: , y: )(x: 3, y: 5).Screen.Recording.2026-05-29.at.15.56.48.mov
If the parentheses already contain arguments, they are kept, even if the labels don't match the function call. I also thought about using an
InsertReplaceEditto let the user decide whether to replace the arguments or keep the existing ones. This however leads to a problem in situations like this:foo|bar(x: 1, y: 2). Here, the replace operation could be for replacing justbaror replacingbar(x: 1, y: 2). I also could not find another language server that does it that way, so I opted for not implementing this for now. But then again, no other language (that I know of) has mandatory argument labels where autocomplete is a real benefit. In Rust for example, the only benefit of autocomplete in this case would be inserting the commas between the arguments.If the parentheses are empty, they are overwritten with the argument labels:
Screen.Recording.2026-05-29.at.15.51.23.mov
While this feature was primarily intended for the use case of completing function calls, I also ensured that it works for other similar cases, like enum case completion and macros.