vscode: Code completion unexpectedly consumes closing paren (from auto-closing-brackets) that was inserted automatically when `(` triggered code completion

I’m unable to reproduce this, but a user was able to provide an LSP log that makes it seem like a VS Code issue. If not, I think it at least needs some direction to help debug.

Here’s what’s happening:

  • The user types foo(
  • The ( triggers code completion (because it’s a trigger character) but also causes a ) to be inserted automatically (auto closing brackets)
  • The user then types ba and selects bar from the completion list
  • VS Code replaces ba) with bar, instead of only ba

It seems like the automatically-inserted ) is being compensated for by VS Code as if it was a character typed since the completion request was sent, so the replacement range VS Code computes is 3 characters long (), b, a) but should only be two (since the ) is on the other side of the cursor).

Here’s the LSP log:


// User types the opening paren (
[18:47:31] [Analyzer] [Info] ==> {"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///Users/s-katoh/development/playground/sample_app/lib/main.dart","version":68},"contentChanges":[{"range":{"start":{"line":9,"character":9},"end":{"line":9,"character":9}},"rangeLength":0,"text":"("}]}}

// Code completion is triggered because ( is a trigger character
[18:47:31] [Analyzer] [Info] ==> {"jsonrpc":"2.0","id":162,"method":"textDocument/completion","params":{"textDocument":{"uri":"file:///Users/s-katoh/development/playground/sample_app/lib/main.dart"},"position":{"line":9,"character":10},"context":{"triggerKind":2,"triggerCharacter":"("}}}

// A closing paren ) is also inserted (this happens after the completion request is sent)
[18:47:31] [Analyzer] [Info] ==> {"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///Users/s-katoh/development/playground/sample_app/lib/main.dart","version":69},"contentChanges":[{"range":{"start":{"line":9,"character":10},"end":{"line":9,"character":10}},"rangeLength":0,"text":")"}]}}

// The server responds with "bar" which has a text edit at line 9 character 10 and a length of 0 (the entire thing will be inserted, as the user has not typed any of "bar" yet). This location corresponds to _between_ the parens since the closing paren ) was also inserted at 9:10.
[18:47:31] [Analyzer] [Info] <== {"id":162,"jsonrpc":"2.0","result":[{"label":"bar","kind":5,"detail":"bool","sortText":"999442","textEdit":{"range":{"start":{"line":9,"character":10},"end":{"line":9,"character":10}},"newText":"bar"}}

// The server is asked to resolve the completion item (it does, with no change to the range)
[18:47:32] [Analyzer] [Info] ==> {"jsonrpc":"2.0","id":168,"method":"completionItem/resolve","params":{"label":"bar","detail":"bool","insertTextFormat":1,"textEdit":{"newText":"bar","range":{"start":{"line":9,"character":10},"end":{"line":9,"character":10}}},"kind":5,"sortText":"999442"}}
[18:47:32] [Analyzer] [Info] <== {"id":168,"jsonrpc":"2.0","result":{"label":"bar","kind":5,"detail":"bool","sortText":"999442","insertTextFormat":1,"textEdit":{"range":{"start":{"line":9,"character":10},"end":{"line":9,"character":10}},"newText":"bar"}}}

// The user types "ba"
[18:47:33] [Analyzer] [Info] ==> {"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///Users/s-katoh/development/playground/sample_app/lib/main.dart","version":70},"contentChanges":[{"range":{"start":{"line":9,"character":10},"end":{"line":9,"character":10}},"rangeLength":0,"text":"b"}]}}
[18:47:33] [Analyzer] [Info] ==> {"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///Users/s-katoh/development/playground/sample_app/lib/main.dart","version":71},"contentChanges":[{"range":{"start":{"line":9,"character":11},"end":{"line":9,"character":11}},"rangeLength":0,"text":"a"}]}}

// User user completes "bar", which should be inserted at 9:10 but compensate for the "ba" they typed, making a replace range of 2 - however the range is 3 (as if the closing ) was somehow compensated for
[18:47:33] [Analyzer] [Info] ==> {"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///Users/s-katoh/development/playground/sample_app/lib/main.dart","version":72},"contentChanges":[{"range":{"start":{"line":9,"character":10},"end":{"line":9,"character":13}},"rangeLength":3,"text":"bar"}]}}

Based on the log, it seems like the server has behaved correctly. It wants to insert “bar” at 9:10 and there’s no reason for the ) to be replaced. Although it was typed after the completion request was sent, it was inserted automatically after the cursor and should be excluded from the range compensation (although in my testing, it seems like it is, because everything works as expected).

I don’t know of any way to debug this further - is there any way to get more information from VS Code about how its compensating for typing during async completion requests?

About this issue

  • Original URL
  • State: open
  • Created 3 years ago
  • Reactions: 2
  • Comments: 16 (11 by maintainers)

Most upvoted comments

Fixing this will conflict https://github.com/microsoft/vscode/issues/26012 - basically the unwanted behaviour here is the wanted behaviour for #26012

@SuporteSysrs01 aha! I can reproduce this now with that setting disabled.

@jrieken here’s my repro:

https://github.com/DanTup/vscode-repro-134013

It’s just a simple extension that always activates and contributes a completion provider that contains “barbarbarbar” and auto-triggers on (.

To reproduce, I open a blank file (in a workspace with "editor.autoClosingBrackets": "never") and I do the following:

  • type f
  • type o
  • type o
  • type (
  • type )
  • press <left cursor>
  • wait for for completion to open
  • select the barbarbarbar entry

This results in the ) disappearing. It does feel odd, although if I had typed an an alphanumeric character instead of the ) I’m less certain it would seem bad. Eg. if I had typed bar and then moved the cursor left 3 places, I probably would want it replacing. In that case, bar would have filtered the list though (whereas ) is clearly not).

Oct-27-2021 15-44-54

I’m able to reproduce this reliably by setting "editor.autoClosingBrackets": "never", quickly typing the opening and closing parentheses in a Flutter project (haven’t tested anywhere else) and triggering an autocomplete:

https://user-images.githubusercontent.com/87761804/139071982-13d434a9-eddd-48b7-9e5d-b9ce78bf9317.mov

It seems to only happen when I manually type the closing parentheses, not when VSCode autocompletes it for me, hence why I had to disable auto closing brackets.