Skip to content

Support InsertReplaceEdit and keep existing arguments when completing function calls#2676

Open
Steffeeen wants to merge 2 commits into
swiftlang:mainfrom
Steffeeen:completion-improvements
Open

Support InsertReplaceEdit and keep existing arguments when completing function calls#2676
Steffeeen wants to merge 2 commits into
swiftlang:mainfrom
Steffeeen:completion-improvements

Conversation

@Steffeeen

Copy link
Copy Markdown
Member

This PR implements two closely related features for code completion.

InsertReplaceEdit Support

First, it adds support for InsertReplaceEdit allowing 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 InsertReplaceEdit changes:

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 InsertReplaceEdit to 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 just bar or replacing bar(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.

@ahoppen ahoppen left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice improvement. Thanks, Steffen!

Comment thread Sources/SwiftLanguageService/CodeCompletionContext.swift Outdated
Comment thread Sources/SwiftLanguageService/CodeCompletionContext.swift Outdated
Comment thread Sources/SwiftLanguageService/CodeCompletionContext.swift Outdated
return nil
}

if case .identifier(_) = token.tokenKind {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we also check for keywords here?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Comment thread Sources/SwiftLanguageService/CodeCompletionContext.swift
Comment thread Sources/SwiftLanguageService/CodeCompletionContext.swift Outdated
Comment thread Sources/SwiftLanguageService/CodeCompletionSession.swift
Comment thread Sources/SwiftLanguageService/CodeCompletionSession.swift
Comment thread Sources/SwiftLanguageService/CodeCompletionSession.swift Outdated
XCTAssertEqual(textEdit.newText, "foobar(x: , y: )")
}

func testFunctionCompletionDoesNotOverwriteMultilineParens() async throws {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Steffeeen added 2 commits June 5, 2026 12:03
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.
@Steffeeen Steffeeen force-pushed the completion-improvements branch from 197e2d0 to 24f6a35 Compare June 5, 2026 10:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants