Question: assert.equal(editor.getValue(), '    let name = foo'); }); <ins> test('use additionalTextEdits sync when possible', async function () { disposables.add(CompletionProviderRegistry.register({ scheme: 'test-ctrl' }, { provideCompletionItems(doc, pos) { return { suggestions: [{ kind: CompletionItemKind.Snippet, label: 'let', insertText: 'hello', range: Range.fromPositions(pos), additionalTextEdits: [{ text: 'I came sync', range: { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 1 } }] }] }; }, async resolveCompletionItem(item) { return item; } })); editor.setValue('hellonhallo'); editor.setSelection(new Selection(2, 6, 2, 6)); // trigger let p1 = Event.toPromise(controller.model.onDidSuggest); controller.triggerSuggest(); await p1; // let p2 = Event.toPromise(controller.model.onDidCancel); controller.acceptSelectedSuggestion(false, false); await p2; // insertText happens sync! assert.equal(editor.getValue(), 'I came synchellonhallohello'); }); test('resolve additionalTextEdits async when needed', async function () { let resolveCallCount = 0; disposables.add(CompletionProviderRegistry.register({ scheme: 'test-ctrl' }, { provideCompletionItems(doc, pos) { return { suggestions: [{ kind: CompletionItemKind.Snippet, label: 'let', insertText: 'hello', range: Range.fromPositions(pos) }] }; }, async resolveCompletionItem(item) { resolveCallCount += 1; await timeout(10); item.additionalTextEdits = [{ text: 'I came late', range: { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 1 } }]; return item; } })); editor.setValue('hellonhallo'); editor.setSelection(new Selection(2, 6, 2, 6)); // trigger let p1 = Event.toPromise(controller.model.onDidSuggest); controller.triggerSuggest(); await p1; // let p2 = Event.toPromise(controller.model.onDidCancel); controller.acceptSelectedSuggestion(false, false); await p2; // insertText happens sync! assert.equal(editor.getValue(), 'hellonhallohello'); assert.equal(resolveCallCount, 1); // additional edits happened after a litte wait await timeout(20); assert.equal(editor.getValue(), 'I came latehellonhallohello'); // single undo stop editor.getModel()?.undo(); assert.equal(editor.getValue(), 'hellonhallo'); }); test('resolve additionalTextEdits async when needed (typing)', async function () { let resolveCallCount = 0; let resolve: Function = () => { }; disposables.add(CompletionProviderRegistry.register({ scheme: 'test-ctrl' }, { provideCompletionItems(doc, pos) { return { suggestions: [{ kind: CompletionItemKind.Snippet, label: 'let', insertText: 'hello', range: Range.fromPositions(pos) }] }; }, async resolveCompletionItem(item) { resolveCallCount += 1; await new Promise(_resolve => resolve = _resolve); item.additionalTextEdits = [{ text: 'I came late', range: { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 1 } }]; return item; } })); editor.setValue('hellonhallo'); editor.setSelection(new Selection(2, 6, 2, 6)); // trigger let p1 = Event.toPromise(controller.model.onDidSuggest); controller.triggerSuggest(); await p1; // let p2 = Event.toPromise(controller.model.onDidCancel); controller.acceptSelectedSuggestion(false, false); await p2; // insertText happens sync! assert.equal(editor.getValue(), 'hellonhallohello'); assert.equal(resolveCallCount, 1); // additional edits happened after a litte wait assert.ok(editor.getSelection()?.equalsSelection(new Selection(2, 11, 2, 11))); editor.trigger('test', 'type', { text: 'TYPING' }); assert.equal(editor.getValue(), 'hellonhallohelloTYPING'); resolve(); await timeout(10); assert.equal(editor.getValue(), 'I came latehellonhallohelloTYPING'); assert.ok(editor.getSelection()?.equalsSelection(new Selection(2, 17, 2, 17))); }); // additional edit come late and are AFTER the selection -> cancel test('resolve additionalTextEdits async when needed (simple conflict)', async function () { let resolveCallCount = 0; let resolve: Function = () => { }; disposables.add(CompletionProviderRegistry.register({ scheme: 'test-ctrl' }, { provideCompletionItems(doc, pos) { return { suggestions: [{ kind: CompletionItemKind.Snippet, label: 'let', insertText: 'hello', range: Range.fromPositions(pos) }] }; }, async resolveCompletionItem(item) { resolveCallCount += 1; await new Promise(_resolve => resolve = _resolve); item.additionalTextEdits = [{ text: 'I came late', range: { startLineNumber: 1, startColumn: 6, endLineNumber: 1, endColumn: 6 } }]; return item; } })); editor.setValue(''); editor.setSelection(new Selection(1, 1, 1, 1)); // trigger let p1 = Event.toPromise(controller.model.onDidSuggest); controller.triggerSuggest(); await p1; // let p2 = Event.toPromise(controller.model.onDidCancel); controller.acceptSelectedSuggestion(false, false); await p2; // insertText happens sync! assert.equal(editor.getValue(), 'hello'); assert.equal(resolveCallCount, 1); resolve(); await timeout(10); assert.equal(editor.getValue(), 'hello'); }); // additional edit come late and are AFTER the position at which the user typed -> cancelled test('resolve additionalTextEdits async when needed (conflict)', async function () { let resolveCallCount = 0; let resolve: Function = () => { }; disposables.add(CompletionProviderRegistry.register({ scheme: 'test-ctrl' }, { provideCompletionItems(doc, pos) { return { suggestions: [{ kind: CompletionItemKind.Snippet, label: 'let', insertText: 'hello', range: Range.fromPositions(pos) }] }; }, async resolveCompletionItem(item) { resolveCallCount += 1; await new Promise(_resolve => resolve = _resolve); item.additionalTextEdits = [{ text: 'I came late', range: { startLineNumber: 1, startColumn: 2, endLineNumber: 1, endColumn: 2 } }]; return item; } })); editor.setValue('hellonhallo'); editor.setSelection(new Selection(2, 6, 2, 6)); // trigger let p1 = Event.toPromise(controller.model.onDidSuggest); controller.triggerSuggest(); await p1; // let p2 = Event.toPromise(controller.model.onDidCancel); controller.acceptSelectedSuggestion(false, false); await p2; // insertText happens sync! assert.equal(editor.getValue(), 'hellonhallohello'); assert.equal(resolveCallCount, 1); // additional edits happened after a litte wait editor.setSelection(new Selection(1, 1, 1, 1)); editor.trigger('test', 'type', { text: 'TYPING' }); assert.equal(editor.getValue(), 'TYPINGhellonhallohello'); resolve(); await timeout(10); assert.equal(editor.getValue(), 'TYPINGhellonhallohello'); assert.ok(editor.getSelection()?.equalsSelection(new Selection(1, 7, 1, 7))); }); test('resolve additionalTextEdits async when needed (cancel)', async function () { let resolve: Function[] = []; disposables.add(CompletionProviderRegistry.register({ scheme: 'test-ctrl' }, { provideCompletionItems(doc, pos) { return { suggestions: [{ kind: CompletionItemKind.Snippet, label: 'let', insertText: 'hello', range: Range.fromPositions(pos) }, { kind: CompletionItemKind.Snippet, label: 'let', insertText: 'hallo', range: Range.fromPositions(pos) }] }; }, async resolveCompletionItem(item) { await new Promise(_resolve => resolve.push(_resolve)); item.additionalTextEdits = [{ text: 'additionalTextEdits', range: { startLineNumber: 1, startColumn: 2, endLineNumber: 1, endColumn: 2 } }]; return item; } })); editor.setValue('abc'); editor.setSelection(new Selection(1, 1, 1, 1)); // trigger let p1 = Event.toPromise(controller.model.onDidSuggest); controller.triggerSuggest(); await p1; // let p2 = Event.toPromise(controller.model.onDidCancel); controller.acceptSelectedSuggestion(true, false); await p2; // insertText happens sync! assert.equal(editor.getValue(), 'helloabc'); // next controller.acceptNextSuggestion(); // resolve additional edits (MUST be cancelled) resolve.forEach(fn => fn); resolve.length = 0; await timeout(10); // next suggestion used assert.equal(editor.getValue(), 'halloabc'); }); </ins> });

This is somewhat the opposite of and the acknowledgement that computing additional text edits for all items upfront is too demanding. This issue is only about and the goal is a pragmatic solution, similar to the one that we have for . Idea accept an unresolved completion proceed with insertion as usual set up a content change listener resolve the items once resolved check if additional edits overlap with "concurrent" changes. when they overlap, do nothing otherwise proceed
The first version for this is now merged to master, it's a pragmatic approach that's better then not resolving but worst than (costly) upfront resolving. Items to followup on [ ] telemetry and follow up issues [ ] - TS issue for having incomplete items [ ] [ ] java for returning very few (resolved) completions, e.g doing the right thing
/cc - My understanding is that Java resolves all items upfront, e.g compute additional text edits during "provide", and that it is quite expensive and therefore Java only returns a small amount of items. Is that correct? Could Java return results faster when additional text edits are computed during resolve (and therefore only for selected items)?
Yes. I think so! is the dev who was working on Java completion. I believe he can provide more insights toward this topic. // cc
this is exactly the behavior that used to have, ie compute all the completion items first (fast), then the text edits were computed during resolveCompletion. We were asked to not do that as and changed to return a small subset of completion items (with incomplete=true), with full text edits computed upfront. The user experience is definitely not as good as with our initial implementation.
To be clear, computing any Java textedit upfront is costly when returning 100+ items (and we can return several thousands items). We'd prefer if the async insertion of textedits covered all textedits, not just the additional ones.
The requirements discussed in are still true and there are no plans to change that. There is (which might be derived from ) and . The former () is inserted at the cursor and must be inserted synchronous to accepting a completion (we cannot block typing while waiting for that). The latter () is meant for things like import-statements and we now want to relax that, e.g it can come late and we try to apply those edits despite the user having typed more.


Question: assert.equal(editor.getValue(), '    let name = foo'); }); <ins> test('use additionalTextEdits sync when possible', async function () { disposables.add(CompletionProviderRegistry.register({ scheme: 'test-ctrl' }, { provideCompletionItems(doc, pos) { return { suggestions: [{ kind: CompletionItemKind.Snippet, label: 'let', insertText: 'hello', range: Range.fromPositions(pos), additionalTextEdits: [{ text: 'I came sync', range: { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 1 } }] }] }; }, async resolveCompletionItem(item) { return item; } })); editor.setValue('hellonhallo'); editor.setSelection(new Selection(2, 6, 2, 6)); // trigger let p1 = Event.toPromise(controller.model.onDidSuggest); controller.triggerSuggest(); await p1; // let p2 = Event.toPromise(controller.model.onDidCancel); controller.acceptSelectedSuggestion(false, false); await p2; // insertText happens sync! assert.equal(editor.getValue(), 'I came synchellonhallohello'); }); test('resolve additionalTextEdits async when needed', async function () { let resolveCallCount = 0; disposables.add(CompletionProviderRegistry.register({ scheme: 'test-ctrl' }, { provideCompletionItems(doc, pos) { return { suggestions: [{ kind: CompletionItemKind.Snippet, label: 'let', insertText: 'hello', range: Range.fromPositions(pos) }] }; }, async resolveCompletionItem(item) { resolveCallCount += 1; await timeout(10); item.additionalTextEdits = [{ text: 'I came late', range: { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 1 } }]; return item; } })); editor.setValue('hellonhallo'); editor.setSelection(new Selection(2, 6, 2, 6)); // trigger let p1 = Event.toPromise(controller.model.onDidSuggest); controller.triggerSuggest(); await p1; // let p2 = Event.toPromise(controller.model.onDidCancel); controller.acceptSelectedSuggestion(false, false); await p2; // insertText happens sync! assert.equal(editor.getValue(), 'hellonhallohello'); assert.equal(resolveCallCount, 1); // additional edits happened after a litte wait await timeout(20); assert.equal(editor.getValue(), 'I came latehellonhallohello'); // single undo stop editor.getModel()?.undo(); assert.equal(editor.getValue(), 'hellonhallo'); }); test('resolve additionalTextEdits async when needed (typing)', async function () { let resolveCallCount = 0; let resolve: Function = () => { }; disposables.add(CompletionProviderRegistry.register({ scheme: 'test-ctrl' }, { provideCompletionItems(doc, pos) { return { suggestions: [{ kind: CompletionItemKind.Snippet, label: 'let', insertText: 'hello', range: Range.fromPositions(pos) }] }; }, async resolveCompletionItem(item) { resolveCallCount += 1; await new Promise(_resolve => resolve = _resolve); item.additionalTextEdits = [{ text: 'I came late', range: { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 1 } }]; return item; } })); editor.setValue('hellonhallo'); editor.setSelection(new Selection(2, 6, 2, 6)); // trigger let p1 = Event.toPromise(controller.model.onDidSuggest); controller.triggerSuggest(); await p1; // let p2 = Event.toPromise(controller.model.onDidCancel); controller.acceptSelectedSuggestion(false, false); await p2; // insertText happens sync! assert.equal(editor.getValue(), 'hellonhallohello'); assert.equal(resolveCallCount, 1); // additional edits happened after a litte wait assert.ok(editor.getSelection()?.equalsSelection(new Selection(2, 11, 2, 11))); editor.trigger('test', 'type', { text: 'TYPING' }); assert.equal(editor.getValue(), 'hellonhallohelloTYPING'); resolve(); await timeout(10); assert.equal(editor.getValue(), 'I came latehellonhallohelloTYPING'); assert.ok(editor.getSelection()?.equalsSelection(new Selection(2, 17, 2, 17))); }); // additional edit come late and are AFTER the selection -> cancel test('resolve additionalTextEdits async when needed (simple conflict)', async function () { let resolveCallCount = 0; let resolve: Function = () => { }; disposables.add(CompletionProviderRegistry.register({ scheme: 'test-ctrl' }, { provideCompletionItems(doc, pos) { return { suggestions: [{ kind: CompletionItemKind.Snippet, label: 'let', insertText: 'hello', range: Range.fromPositions(pos) }] }; }, async resolveCompletionItem(item) { resolveCallCount += 1; await new Promise(_resolve => resolve = _resolve); item.additionalTextEdits = [{ text: 'I came late', range: { startLineNumber: 1, startColumn: 6, endLineNumber: 1, endColumn: 6 } }]; return item; } })); editor.setValue(''); editor.setSelection(new Selection(1, 1, 1, 1)); // trigger let p1 = Event.toPromise(controller.model.onDidSuggest); controller.triggerSuggest(); await p1; // let p2 = Event.toPromise(controller.model.onDidCancel); controller.acceptSelectedSuggestion(false, false); await p2; // insertText happens sync! assert.equal(editor.getValue(), 'hello'); assert.equal(resolveCallCount, 1); resolve(); await timeout(10); assert.equal(editor.getValue(), 'hello'); }); // additional edit come late and are AFTER the position at which the user typed -> cancelled test('resolve additionalTextEdits async when needed (conflict)', async function () { let resolveCallCount = 0; let resolve: Function = () => { }; disposables.add(CompletionProviderRegistry.register({ scheme: 'test-ctrl' }, { provideCompletionItems(doc, pos) { return { suggestions: [{ kind: CompletionItemKind.Snippet, label: 'let', insertText: 'hello', range: Range.fromPositions(pos) }] }; }, async resolveCompletionItem(item) { resolveCallCount += 1; await new Promise(_resolve => resolve = _resolve); item.additionalTextEdits = [{ text: 'I came late', range: { startLineNumber: 1, startColumn: 2, endLineNumber: 1, endColumn: 2 } }]; return item; } })); editor.setValue('hellonhallo'); editor.setSelection(new Selection(2, 6, 2, 6)); // trigger let p1 = Event.toPromise(controller.model.onDidSuggest); controller.triggerSuggest(); await p1; // let p2 = Event.toPromise(controller.model.onDidCancel); controller.acceptSelectedSuggestion(false, false); await p2; // insertText happens sync! assert.equal(editor.getValue(), 'hellonhallohello'); assert.equal(resolveCallCount, 1); // additional edits happened after a litte wait editor.setSelection(new Selection(1, 1, 1, 1)); editor.trigger('test', 'type', { text: 'TYPING' }); assert.equal(editor.getValue(), 'TYPINGhellonhallohello'); resolve(); await timeout(10); assert.equal(editor.getValue(), 'TYPINGhellonhallohello'); assert.ok(editor.getSelection()?.equalsSelection(new Selection(1, 7, 1, 7))); }); test('resolve additionalTextEdits async when needed (cancel)', async function () { let resolve: Function[] = []; disposables.add(CompletionProviderRegistry.register({ scheme: 'test-ctrl' }, { provideCompletionItems(doc, pos) { return { suggestions: [{ kind: CompletionItemKind.Snippet, label: 'let', insertText: 'hello', range: Range.fromPositions(pos) }, { kind: CompletionItemKind.Snippet, label: 'let', insertText: 'hallo', range: Range.fromPositions(pos) }] }; }, async resolveCompletionItem(item) { await new Promise(_resolve => resolve.push(_resolve)); item.additionalTextEdits = [{ text: 'additionalTextEdits', range: { startLineNumber: 1, startColumn: 2, endLineNumber: 1, endColumn: 2 } }]; return item; } })); editor.setValue('abc'); editor.setSelection(new Selection(1, 1, 1, 1)); // trigger let p1 = Event.toPromise(controller.model.onDidSuggest); controller.triggerSuggest(); await p1; // let p2 = Event.toPromise(controller.model.onDidCancel); controller.acceptSelectedSuggestion(true, false); await p2; // insertText happens sync! assert.equal(editor.getValue(), 'helloabc'); // next controller.acceptNextSuggestion(); // resolve additional edits (MUST be cancelled) resolve.forEach(fn => fn); resolve.length = 0; await timeout(10); // next suggestion used assert.equal(editor.getValue(), 'halloabc'); }); </ins> });

When exactly does run, is it only when an item gets highlighted in the menu? Can't tell from the docs. I only need to generate for the completion item the user ends up applying -- doing it for other items is unnecessary work that's bogging down the extension host, and I will have to implement yielding. I assume no one would need to generate before a completion item is applied? Is there any kind of completion applied event we can hook into to work around this for the time being?
That's exactly what is about . Today, there is no guarantee that is ever called because its original design is to provide data for the suggest details widget. However, we see that (amongst other things) are computed during resolve and leads to subtle bugs (because don't always call resolve or always await it) Well, is the problem if you do not generate them upfront. select suggestion synchronously inserts the string starts for additional text edits continues to type quickly and all over the place -call eventually returns but it is unclear if the edits are still valid because the state changed many types in-between Solutions: Won't work because in JS you cannot wait for another "thread" or "process", the world always keeps turning Implement a full -solution because this is two entities "typing" concurrently in the same document. However, it will feel awkward because the user doesn't expect to be such a scenario. approach that tries to apply additional edits if possible but inserts the "main edit" (insertText) always synchronously. () "if possible" is currently defined as "no edit has occurred before the first additional edit". That works well if you use additional edits for things like imports or prefixes (which is the indented use-case) but won't work if additional text edits change the main edit, like appending a call-operators with arguments etc
I mean JS can't block but ignoring typing while awaiting is possible -- I was imagining that VSCode would keep the completions menu open and ignore any typing until is resolved and all text edits applied for the accepted item. Obviously this would be bad if resolving takes a long time, but the API has cancelation tokens already, so the UI would just need to show a spinner/cancel icon, and also cancel if the user presses ESC or navigates to a different editor, for a decent UX.


Question: assert.equal(editor.getValue(), '    let name = foo'); }); <ins> test('use additionalTextEdits sync when possible', async function () { disposables.add(CompletionProviderRegistry.register({ scheme: 'test-ctrl' }, { provideCompletionItems(doc, pos) { return { suggestions: [{ kind: CompletionItemKind.Snippet, label: 'let', insertText: 'hello', range: Range.fromPositions(pos), additionalTextEdits: [{ text: 'I came sync', range: { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 1 } }] }] }; }, async resolveCompletionItem(item) { return item; } })); editor.setValue('hellonhallo'); editor.setSelection(new Selection(2, 6, 2, 6)); // trigger let p1 = Event.toPromise(controller.model.onDidSuggest); controller.triggerSuggest(); await p1; // let p2 = Event.toPromise(controller.model.onDidCancel); controller.acceptSelectedSuggestion(false, false); await p2; // insertText happens sync! assert.equal(editor.getValue(), 'I came synchellonhallohello'); }); test('resolve additionalTextEdits async when needed', async function () { let resolveCallCount = 0; disposables.add(CompletionProviderRegistry.register({ scheme: 'test-ctrl' }, { provideCompletionItems(doc, pos) { return { suggestions: [{ kind: CompletionItemKind.Snippet, label: 'let', insertText: 'hello', range: Range.fromPositions(pos) }] }; }, async resolveCompletionItem(item) { resolveCallCount += 1; await timeout(10); item.additionalTextEdits = [{ text: 'I came late', range: { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 1 } }]; return item; } })); editor.setValue('hellonhallo'); editor.setSelection(new Selection(2, 6, 2, 6)); // trigger let p1 = Event.toPromise(controller.model.onDidSuggest); controller.triggerSuggest(); await p1; // let p2 = Event.toPromise(controller.model.onDidCancel); controller.acceptSelectedSuggestion(false, false); await p2; // insertText happens sync! assert.equal(editor.getValue(), 'hellonhallohello'); assert.equal(resolveCallCount, 1); // additional edits happened after a litte wait await timeout(20); assert.equal(editor.getValue(), 'I came latehellonhallohello'); // single undo stop editor.getModel()?.undo(); assert.equal(editor.getValue(), 'hellonhallo'); }); test('resolve additionalTextEdits async when needed (typing)', async function () { let resolveCallCount = 0; let resolve: Function = () => { }; disposables.add(CompletionProviderRegistry.register({ scheme: 'test-ctrl' }, { provideCompletionItems(doc, pos) { return { suggestions: [{ kind: CompletionItemKind.Snippet, label: 'let', insertText: 'hello', range: Range.fromPositions(pos) }] }; }, async resolveCompletionItem(item) { resolveCallCount += 1; await new Promise(_resolve => resolve = _resolve); item.additionalTextEdits = [{ text: 'I came late', range: { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 1 } }]; return item; } })); editor.setValue('hellonhallo'); editor.setSelection(new Selection(2, 6, 2, 6)); // trigger let p1 = Event.toPromise(controller.model.onDidSuggest); controller.triggerSuggest(); await p1; // let p2 = Event.toPromise(controller.model.onDidCancel); controller.acceptSelectedSuggestion(false, false); await p2; // insertText happens sync! assert.equal(editor.getValue(), 'hellonhallohello'); assert.equal(resolveCallCount, 1); // additional edits happened after a litte wait assert.ok(editor.getSelection()?.equalsSelection(new Selection(2, 11, 2, 11))); editor.trigger('test', 'type', { text: 'TYPING' }); assert.equal(editor.getValue(), 'hellonhallohelloTYPING'); resolve(); await timeout(10); assert.equal(editor.getValue(), 'I came latehellonhallohelloTYPING'); assert.ok(editor.getSelection()?.equalsSelection(new Selection(2, 17, 2, 17))); }); // additional edit come late and are AFTER the selection -> cancel test('resolve additionalTextEdits async when needed (simple conflict)', async function () { let resolveCallCount = 0; let resolve: Function = () => { }; disposables.add(CompletionProviderRegistry.register({ scheme: 'test-ctrl' }, { provideCompletionItems(doc, pos) { return { suggestions: [{ kind: CompletionItemKind.Snippet, label: 'let', insertText: 'hello', range: Range.fromPositions(pos) }] }; }, async resolveCompletionItem(item) { resolveCallCount += 1; await new Promise(_resolve => resolve = _resolve); item.additionalTextEdits = [{ text: 'I came late', range: { startLineNumber: 1, startColumn: 6, endLineNumber: 1, endColumn: 6 } }]; return item; } })); editor.setValue(''); editor.setSelection(new Selection(1, 1, 1, 1)); // trigger let p1 = Event.toPromise(controller.model.onDidSuggest); controller.triggerSuggest(); await p1; // let p2 = Event.toPromise(controller.model.onDidCancel); controller.acceptSelectedSuggestion(false, false); await p2; // insertText happens sync! assert.equal(editor.getValue(), 'hello'); assert.equal(resolveCallCount, 1); resolve(); await timeout(10); assert.equal(editor.getValue(), 'hello'); }); // additional edit come late and are AFTER the position at which the user typed -> cancelled test('resolve additionalTextEdits async when needed (conflict)', async function () { let resolveCallCount = 0; let resolve: Function = () => { }; disposables.add(CompletionProviderRegistry.register({ scheme: 'test-ctrl' }, { provideCompletionItems(doc, pos) { return { suggestions: [{ kind: CompletionItemKind.Snippet, label: 'let', insertText: 'hello', range: Range.fromPositions(pos) }] }; }, async resolveCompletionItem(item) { resolveCallCount += 1; await new Promise(_resolve => resolve = _resolve); item.additionalTextEdits = [{ text: 'I came late', range: { startLineNumber: 1, startColumn: 2, endLineNumber: 1, endColumn: 2 } }]; return item; } })); editor.setValue('hellonhallo'); editor.setSelection(new Selection(2, 6, 2, 6)); // trigger let p1 = Event.toPromise(controller.model.onDidSuggest); controller.triggerSuggest(); await p1; // let p2 = Event.toPromise(controller.model.onDidCancel); controller.acceptSelectedSuggestion(false, false); await p2; // insertText happens sync! assert.equal(editor.getValue(), 'hellonhallohello'); assert.equal(resolveCallCount, 1); // additional edits happened after a litte wait editor.setSelection(new Selection(1, 1, 1, 1)); editor.trigger('test', 'type', { text: 'TYPING' }); assert.equal(editor.getValue(), 'TYPINGhellonhallohello'); resolve(); await timeout(10); assert.equal(editor.getValue(), 'TYPINGhellonhallohello'); assert.ok(editor.getSelection()?.equalsSelection(new Selection(1, 7, 1, 7))); }); test('resolve additionalTextEdits async when needed (cancel)', async function () { let resolve: Function[] = []; disposables.add(CompletionProviderRegistry.register({ scheme: 'test-ctrl' }, { provideCompletionItems(doc, pos) { return { suggestions: [{ kind: CompletionItemKind.Snippet, label: 'let', insertText: 'hello', range: Range.fromPositions(pos) }, { kind: CompletionItemKind.Snippet, label: 'let', insertText: 'hallo', range: Range.fromPositions(pos) }] }; }, async resolveCompletionItem(item) { await new Promise(_resolve => resolve.push(_resolve)); item.additionalTextEdits = [{ text: 'additionalTextEdits', range: { startLineNumber: 1, startColumn: 2, endLineNumber: 1, endColumn: 2 } }]; return item; } })); editor.setValue('abc'); editor.setSelection(new Selection(1, 1, 1, 1)); // trigger let p1 = Event.toPromise(controller.model.onDidSuggest); controller.triggerSuggest(); await p1; // let p2 = Event.toPromise(controller.model.onDidCancel); controller.acceptSelectedSuggestion(true, false); await p2; // insertText happens sync! assert.equal(editor.getValue(), 'helloabc'); // next controller.acceptNextSuggestion(); // resolve additional edits (MUST be cancelled) resolve.forEach(fn => fn); resolve.length = 0; await timeout(10); // next suggestion used assert.equal(editor.getValue(), 'halloabc'); }); </ins> });

I think this would be less confusing for users too; it's the only way it would be clear when VSCode is completely done applying the completion. I think many users would be confused if imports usually get but not when they resume typing too soon. FWIW I can syncronously generate the additional text edits I need, so I'd be happy if there were an API to synchronously generate them when the completion item is accepted. I know not everyone would be able to generate them sync though.
Also I know my questions may have seemed redundant, but I hadn't seen anyone on this thread point out that it would be ideal to avoid ever computing additional text edits (or even the primary insert text) for unselected completions. Since can get called before any item is accepted (I assume, since it seems to be for showing additional details in the menu), I'd think any solution via could still result in unnecessarily generating additional edits that are ultimately discarded.
The golden rule is that "you can always type inside the editor". Ignoring typing is not a good solution and will bug users. Just think of quick suggest and auto accept characters - you don't want the editor to stop typing just because an extension is taking a little longer (+50ms). To be precise you should have written "typing at or before the offset at which additional edits are going to happen" I honestly think the majority can generate them synchronously but this happens in the extension host process or the language server process, and all of that might happen on a different machine and there is no way to synchronously transport that into the UI.
re with the current changes you should be able to have a "simple insert text", say , and compute an additional text edit, like , that goes in front of it so that you eventually have . Right? Or am I missing something?
that might actually be a good idea ;-) In practice though, we might need to revisit the way the principal TextEdit is computed (i.e rewrite a bunch of stuff from upstream JDT)
I get the desire to be able to always be able to type inside the editor and agree that's an important principle. The only reason I think this case is an exception is that: The user would probably want to wait for all edits to get applied anyway.


Question: assert.equal(editor.getValue(), '    let name = foo'); }); <ins> test('use additionalTextEdits sync when possible', async function () { disposables.add(CompletionProviderRegistry.register({ scheme: 'test-ctrl' }, { provideCompletionItems(doc, pos) { return { suggestions: [{ kind: CompletionItemKind.Snippet, label: 'let', insertText: 'hello', range: Range.fromPositions(pos), additionalTextEdits: [{ text: 'I came sync', range: { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 1 } }] }] }; }, async resolveCompletionItem(item) { return item; } })); editor.setValue('hellonhallo'); editor.setSelection(new Selection(2, 6, 2, 6)); // trigger let p1 = Event.toPromise(controller.model.onDidSuggest); controller.triggerSuggest(); await p1; // let p2 = Event.toPromise(controller.model.onDidCancel); controller.acceptSelectedSuggestion(false, false); await p2; // insertText happens sync! assert.equal(editor.getValue(), 'I came synchellonhallohello'); }); test('resolve additionalTextEdits async when needed', async function () { let resolveCallCount = 0; disposables.add(CompletionProviderRegistry.register({ scheme: 'test-ctrl' }, { provideCompletionItems(doc, pos) { return { suggestions: [{ kind: CompletionItemKind.Snippet, label: 'let', insertText: 'hello', range: Range.fromPositions(pos) }] }; }, async resolveCompletionItem(item) { resolveCallCount += 1; await timeout(10); item.additionalTextEdits = [{ text: 'I came late', range: { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 1 } }]; return item; } })); editor.setValue('hellonhallo'); editor.setSelection(new Selection(2, 6, 2, 6)); // trigger let p1 = Event.toPromise(controller.model.onDidSuggest); controller.triggerSuggest(); await p1; // let p2 = Event.toPromise(controller.model.onDidCancel); controller.acceptSelectedSuggestion(false, false); await p2; // insertText happens sync! assert.equal(editor.getValue(), 'hellonhallohello'); assert.equal(resolveCallCount, 1); // additional edits happened after a litte wait await timeout(20); assert.equal(editor.getValue(), 'I came latehellonhallohello'); // single undo stop editor.getModel()?.undo(); assert.equal(editor.getValue(), 'hellonhallo'); }); test('resolve additionalTextEdits async when needed (typing)', async function () { let resolveCallCount = 0; let resolve: Function = () => { }; disposables.add(CompletionProviderRegistry.register({ scheme: 'test-ctrl' }, { provideCompletionItems(doc, pos) { return { suggestions: [{ kind: CompletionItemKind.Snippet, label: 'let', insertText: 'hello', range: Range.fromPositions(pos) }] }; }, async resolveCompletionItem(item) { resolveCallCount += 1; await new Promise(_resolve => resolve = _resolve); item.additionalTextEdits = [{ text: 'I came late', range: { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 1 } }]; return item; } })); editor.setValue('hellonhallo'); editor.setSelection(new Selection(2, 6, 2, 6)); // trigger let p1 = Event.toPromise(controller.model.onDidSuggest); controller.triggerSuggest(); await p1; // let p2 = Event.toPromise(controller.model.onDidCancel); controller.acceptSelectedSuggestion(false, false); await p2; // insertText happens sync! assert.equal(editor.getValue(), 'hellonhallohello'); assert.equal(resolveCallCount, 1); // additional edits happened after a litte wait assert.ok(editor.getSelection()?.equalsSelection(new Selection(2, 11, 2, 11))); editor.trigger('test', 'type', { text: 'TYPING' }); assert.equal(editor.getValue(), 'hellonhallohelloTYPING'); resolve(); await timeout(10); assert.equal(editor.getValue(), 'I came latehellonhallohelloTYPING'); assert.ok(editor.getSelection()?.equalsSelection(new Selection(2, 17, 2, 17))); }); // additional edit come late and are AFTER the selection -> cancel test('resolve additionalTextEdits async when needed (simple conflict)', async function () { let resolveCallCount = 0; let resolve: Function = () => { }; disposables.add(CompletionProviderRegistry.register({ scheme: 'test-ctrl' }, { provideCompletionItems(doc, pos) { return { suggestions: [{ kind: CompletionItemKind.Snippet, label: 'let', insertText: 'hello', range: Range.fromPositions(pos) }] }; }, async resolveCompletionItem(item) { resolveCallCount += 1; await new Promise(_resolve => resolve = _resolve); item.additionalTextEdits = [{ text: 'I came late', range: { startLineNumber: 1, startColumn: 6, endLineNumber: 1, endColumn: 6 } }]; return item; } })); editor.setValue(''); editor.setSelection(new Selection(1, 1, 1, 1)); // trigger let p1 = Event.toPromise(controller.model.onDidSuggest); controller.triggerSuggest(); await p1; // let p2 = Event.toPromise(controller.model.onDidCancel); controller.acceptSelectedSuggestion(false, false); await p2; // insertText happens sync! assert.equal(editor.getValue(), 'hello'); assert.equal(resolveCallCount, 1); resolve(); await timeout(10); assert.equal(editor.getValue(), 'hello'); }); // additional edit come late and are AFTER the position at which the user typed -> cancelled test('resolve additionalTextEdits async when needed (conflict)', async function () { let resolveCallCount = 0; let resolve: Function = () => { }; disposables.add(CompletionProviderRegistry.register({ scheme: 'test-ctrl' }, { provideCompletionItems(doc, pos) { return { suggestions: [{ kind: CompletionItemKind.Snippet, label: 'let', insertText: 'hello', range: Range.fromPositions(pos) }] }; }, async resolveCompletionItem(item) { resolveCallCount += 1; await new Promise(_resolve => resolve = _resolve); item.additionalTextEdits = [{ text: 'I came late', range: { startLineNumber: 1, startColumn: 2, endLineNumber: 1, endColumn: 2 } }]; return item; } })); editor.setValue('hellonhallo'); editor.setSelection(new Selection(2, 6, 2, 6)); // trigger let p1 = Event.toPromise(controller.model.onDidSuggest); controller.triggerSuggest(); await p1; // let p2 = Event.toPromise(controller.model.onDidCancel); controller.acceptSelectedSuggestion(false, false); await p2; // insertText happens sync! assert.equal(editor.getValue(), 'hellonhallohello'); assert.equal(resolveCallCount, 1); // additional edits happened after a litte wait editor.setSelection(new Selection(1, 1, 1, 1)); editor.trigger('test', 'type', { text: 'TYPING' }); assert.equal(editor.getValue(), 'TYPINGhellonhallohello'); resolve(); await timeout(10); assert.equal(editor.getValue(), 'TYPINGhellonhallohello'); assert.ok(editor.getSelection()?.equalsSelection(new Selection(1, 7, 1, 7))); }); test('resolve additionalTextEdits async when needed (cancel)', async function () { let resolve: Function[] = []; disposables.add(CompletionProviderRegistry.register({ scheme: 'test-ctrl' }, { provideCompletionItems(doc, pos) { return { suggestions: [{ kind: CompletionItemKind.Snippet, label: 'let', insertText: 'hello', range: Range.fromPositions(pos) }, { kind: CompletionItemKind.Snippet, label: 'let', insertText: 'hallo', range: Range.fromPositions(pos) }] }; }, async resolveCompletionItem(item) { await new Promise(_resolve => resolve.push(_resolve)); item.additionalTextEdits = [{ text: 'additionalTextEdits', range: { startLineNumber: 1, startColumn: 2, endLineNumber: 1, endColumn: 2 } }]; return item; } })); editor.setValue('abc'); editor.setSelection(new Selection(1, 1, 1, 1)); // trigger let p1 = Event.toPromise(controller.model.onDidSuggest); controller.triggerSuggest(); await p1; // let p2 = Event.toPromise(controller.model.onDidCancel); controller.acceptSelectedSuggestion(true, false); await p2; // insertText happens sync! assert.equal(editor.getValue(), 'helloabc'); // next controller.acceptNextSuggestion(); // resolve additional edits (MUST be cancelled) resolve.forEach(fn => fn); resolve.length = 0; await timeout(10); // next suggestion used assert.equal(editor.getValue(), 'halloabc'); }); </ins> });

Seems unlikely that people would want to get the main edit without the additional edits By making the operation cancelable, you can still go back to typing immediately if you so desire. I certainly wouldn't want to block the user from typing without recourse (as you know, I feel frustrated that happens currently when using Vim extensions) On the other hand, I guess it's unlikely that the additional text edits would overlap whatever the user continues typing...
I hadn't thought about auto accept characters though, that's a good point. That's something I don't have experience with. Also considering that VSCode could pop up a warning if it has to bail on inserting additional edits, it's not a big deal if it ever happens
I remember that we have some new data points regarding the time consumption in different stages of code completion in Can you share that data and see how we can tune it with this change?
For Java language server, below is a sample of call tree when triggering completion. (time value is larger than expected because of extra overhead from JProfiler) ! Total time of completion stage mainly consists of three parts: competion candidates in JDT to CompletionItems (calc textEdits, additionalTextEdits, etc.) --- calc an incomplete list of only 50 items by default (the full list can contain thousands) --- here calculating import statements costs a lot of time server-side code snippets (not much related to this topic) I did a simple experiment on java language server, with vscode-insider built on 2020-5-15. I was using spring-petclinic, which is not a big one, but with some dependencies. Type a initial letter, e.g. "O", and observe perf of completion and resolve. Do multiple times for statistics. Calculating additionalTextEdits async in resolve stage does decrease the time cost a lot. textEdits and additionalTextEdits are calculated during completion stage. completion: ~150 ms resolve(avg of top 10 items): ~10 ms Now I simply move a bunch of textedit calculation from "completion" stage into "resolve" stage. completion: ~100 ms resolve(avg of top 10 items): ~17 ms !
I just realized that can return a thenable, but I assume that would delay the appearance of the entire menu?
Closing as we haven't seen issues with the new pragmatic approach and the plan is to stick with it.


Question: assert.equal(editor.getValue(), '    let name = foo'); }); <ins> test('use additionalTextEdits sync when possible', async function () { disposables.add(CompletionProviderRegistry.register({ scheme: 'test-ctrl' }, { provideCompletionItems(doc, pos) { return { suggestions: [{ kind: CompletionItemKind.Snippet, label: 'let', insertText: 'hello', range: Range.fromPositions(pos), additionalTextEdits: [{ text: 'I came sync', range: { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 1 } }] }] }; }, async resolveCompletionItem(item) { return item; } })); editor.setValue('hellonhallo'); editor.setSelection(new Selection(2, 6, 2, 6)); // trigger let p1 = Event.toPromise(controller.model.onDidSuggest); controller.triggerSuggest(); await p1; // let p2 = Event.toPromise(controller.model.onDidCancel); controller.acceptSelectedSuggestion(false, false); await p2; // insertText happens sync! assert.equal(editor.getValue(), 'I came synchellonhallohello'); }); test('resolve additionalTextEdits async when needed', async function () { let resolveCallCount = 0; disposables.add(CompletionProviderRegistry.register({ scheme: 'test-ctrl' }, { provideCompletionItems(doc, pos) { return { suggestions: [{ kind: CompletionItemKind.Snippet, label: 'let', insertText: 'hello', range: Range.fromPositions(pos) }] }; }, async resolveCompletionItem(item) { resolveCallCount += 1; await timeout(10); item.additionalTextEdits = [{ text: 'I came late', range: { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 1 } }]; return item; } })); editor.setValue('hellonhallo'); editor.setSelection(new Selection(2, 6, 2, 6)); // trigger let p1 = Event.toPromise(controller.model.onDidSuggest); controller.triggerSuggest(); await p1; // let p2 = Event.toPromise(controller.model.onDidCancel); controller.acceptSelectedSuggestion(false, false); await p2; // insertText happens sync! assert.equal(editor.getValue(), 'hellonhallohello'); assert.equal(resolveCallCount, 1); // additional edits happened after a litte wait await timeout(20); assert.equal(editor.getValue(), 'I came latehellonhallohello'); // single undo stop editor.getModel()?.undo(); assert.equal(editor.getValue(), 'hellonhallo'); }); test('resolve additionalTextEdits async when needed (typing)', async function () { let resolveCallCount = 0; let resolve: Function = () => { }; disposables.add(CompletionProviderRegistry.register({ scheme: 'test-ctrl' }, { provideCompletionItems(doc, pos) { return { suggestions: [{ kind: CompletionItemKind.Snippet, label: 'let', insertText: 'hello', range: Range.fromPositions(pos) }] }; }, async resolveCompletionItem(item) { resolveCallCount += 1; await new Promise(_resolve => resolve = _resolve); item.additionalTextEdits = [{ text: 'I came late', range: { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 1 } }]; return item; } })); editor.setValue('hellonhallo'); editor.setSelection(new Selection(2, 6, 2, 6)); // trigger let p1 = Event.toPromise(controller.model.onDidSuggest); controller.triggerSuggest(); await p1; // let p2 = Event.toPromise(controller.model.onDidCancel); controller.acceptSelectedSuggestion(false, false); await p2; // insertText happens sync! assert.equal(editor.getValue(), 'hellonhallohello'); assert.equal(resolveCallCount, 1); // additional edits happened after a litte wait assert.ok(editor.getSelection()?.equalsSelection(new Selection(2, 11, 2, 11))); editor.trigger('test', 'type', { text: 'TYPING' }); assert.equal(editor.getValue(), 'hellonhallohelloTYPING'); resolve(); await timeout(10); assert.equal(editor.getValue(), 'I came latehellonhallohelloTYPING'); assert.ok(editor.getSelection()?.equalsSelection(new Selection(2, 17, 2, 17))); }); // additional edit come late and are AFTER the selection -> cancel test('resolve additionalTextEdits async when needed (simple conflict)', async function () { let resolveCallCount = 0; let resolve: Function = () => { }; disposables.add(CompletionProviderRegistry.register({ scheme: 'test-ctrl' }, { provideCompletionItems(doc, pos) { return { suggestions: [{ kind: CompletionItemKind.Snippet, label: 'let', insertText: 'hello', range: Range.fromPositions(pos) }] }; }, async resolveCompletionItem(item) { resolveCallCount += 1; await new Promise(_resolve => resolve = _resolve); item.additionalTextEdits = [{ text: 'I came late', range: { startLineNumber: 1, startColumn: 6, endLineNumber: 1, endColumn: 6 } }]; return item; } })); editor.setValue(''); editor.setSelection(new Selection(1, 1, 1, 1)); // trigger let p1 = Event.toPromise(controller.model.onDidSuggest); controller.triggerSuggest(); await p1; // let p2 = Event.toPromise(controller.model.onDidCancel); controller.acceptSelectedSuggestion(false, false); await p2; // insertText happens sync! assert.equal(editor.getValue(), 'hello'); assert.equal(resolveCallCount, 1); resolve(); await timeout(10); assert.equal(editor.getValue(), 'hello'); }); // additional edit come late and are AFTER the position at which the user typed -> cancelled test('resolve additionalTextEdits async when needed (conflict)', async function () { let resolveCallCount = 0; let resolve: Function = () => { }; disposables.add(CompletionProviderRegistry.register({ scheme: 'test-ctrl' }, { provideCompletionItems(doc, pos) { return { suggestions: [{ kind: CompletionItemKind.Snippet, label: 'let', insertText: 'hello', range: Range.fromPositions(pos) }] }; }, async resolveCompletionItem(item) { resolveCallCount += 1; await new Promise(_resolve => resolve = _resolve); item.additionalTextEdits = [{ text: 'I came late', range: { startLineNumber: 1, startColumn: 2, endLineNumber: 1, endColumn: 2 } }]; return item; } })); editor.setValue('hellonhallo'); editor.setSelection(new Selection(2, 6, 2, 6)); // trigger let p1 = Event.toPromise(controller.model.onDidSuggest); controller.triggerSuggest(); await p1; // let p2 = Event.toPromise(controller.model.onDidCancel); controller.acceptSelectedSuggestion(false, false); await p2; // insertText happens sync! assert.equal(editor.getValue(), 'hellonhallohello'); assert.equal(resolveCallCount, 1); // additional edits happened after a litte wait editor.setSelection(new Selection(1, 1, 1, 1)); editor.trigger('test', 'type', { text: 'TYPING' }); assert.equal(editor.getValue(), 'TYPINGhellonhallohello'); resolve(); await timeout(10); assert.equal(editor.getValue(), 'TYPINGhellonhallohello'); assert.ok(editor.getSelection()?.equalsSelection(new Selection(1, 7, 1, 7))); }); test('resolve additionalTextEdits async when needed (cancel)', async function () { let resolve: Function[] = []; disposables.add(CompletionProviderRegistry.register({ scheme: 'test-ctrl' }, { provideCompletionItems(doc, pos) { return { suggestions: [{ kind: CompletionItemKind.Snippet, label: 'let', insertText: 'hello', range: Range.fromPositions(pos) }, { kind: CompletionItemKind.Snippet, label: 'let', insertText: 'hallo', range: Range.fromPositions(pos) }] }; }, async resolveCompletionItem(item) { await new Promise(_resolve => resolve.push(_resolve)); item.additionalTextEdits = [{ text: 'additionalTextEdits', range: { startLineNumber: 1, startColumn: 2, endLineNumber: 1, endColumn: 2 } }]; return item; } })); editor.setValue('abc'); editor.setSelection(new Selection(1, 1, 1, 1)); // trigger let p1 = Event.toPromise(controller.model.onDidSuggest); controller.triggerSuggest(); await p1; // let p2 = Event.toPromise(controller.model.onDidCancel); controller.acceptSelectedSuggestion(true, false); await p2; // insertText happens sync! assert.equal(editor.getValue(), 'helloabc'); // next controller.acceptNextSuggestion(); // resolve additional edits (MUST be cancelled) resolve.forEach(fn => fn); resolve.length = 0; await timeout(10); // next suggestion used assert.equal(editor.getValue(), 'halloabc'); }); </ins> });

so how do servers know that clients support lazy-loading of additional textedits? Surely this should be a client capability advertised by vscode (that needs to be specced in the LSP)
is the right person for that and you should probably create a follow up issue in the LSP land
corresponding LSP feature request created here please add more details if I miss anything.
Thanks
To verify: use TypeScript have a suggestion that will import a symbol make sure that suggestion isn't first or focused in the UI use the mouse(!) to select the suggestion make sure the import is there make sure undo, redo works as expected


Question: // create the suggestion resolver if (typeof provider.resolveCompletionItem !== 'function') { this._resolveCache = Promise.resolve(); <ins> this._isResolved = true; </ins> } } // resolving get isResolved() { <del> return Boolean(this._resolveCache); </del> <ins> return Boolean(this._isResolved); </ins> } private _resolveCache?: Promise<void>; <ins> private _isResolved?: boolean; </ins> async resolve(token: CancellationToken) { if (!this._resolveCache) { const sub = token.onCancellationRequested(() => { this._resolveCache = undefined; <ins> this._isResolved = false; </ins> }); this._resolveCache = Promise.resolve(this.provider.resolveCompletionItem!(this.completion, token)).then(value => { Object.assign(this.completion, value); <ins> this._isResolved = true; </ins> sub.dispose(); }, err => { if (isPromiseCanceledError(err)) { // the IPC queue will reject the request with the // cancellation error -> reset cached this._resolveCache = undefined; <ins> this._isResolved = false; </ins> } }); }

Issue Type: <bBug</bLike what the heading says, inserting a code snippet anywhere in the code file causes an autoscroll. This is a slight inconvenience, as while inserting snippets, viewing code above it is desirable, causing us to rescroll into view. So adding an option to revert to the functionality in the stable release would be preferred VS Code version: Code - Insiders 1.46.0-insider (, 2020-05-07T16:36:39.681Z) OS version: Linux x64 5.4.38-1-MANJARO <details<summarySystem Info</summary </details<details<summaryExtensions (4)</summaryExtensionVersion ------ cpptools0.27.1 Go0.14.1 material-icon-theme4.1.0 material-theme3.5.0 (1 theme extensions excluded) </details<!-- generated by issue reporter --
Yeah - that's a regression from a recent change to keep the position stable when additional edits are inserted...
is the item that caused this regression.
fixed via
Yep, it works fine now. Thanks a lot!
This bug has been fixed in to the latest release of ! you can help us out by commenting if things are now working as expected. If things still don't seem right, please ensure you're on version of Insiders (today's or later - you can use in the command pallette to check), and leave a comment letting us know what isn't working as expected. Happy Coding!
/verified


Question: // create the suggestion resolver if (typeof provider.resolveCompletionItem !== 'function') { this._resolveCache = Promise.resolve(); <ins> this._isResolved = true; </ins> } } // resolving get isResolved() { <del> return Boolean(this._resolveCache); </del> <ins> return Boolean(this._isResolved); </ins> } private _resolveCache?: Promise<void>; <ins> private _isResolved?: boolean; </ins> async resolve(token: CancellationToken) { if (!this._resolveCache) { const sub = token.onCancellationRequested(() => { this._resolveCache = undefined; <ins> this._isResolved = false; </ins> }); this._resolveCache = Promise.resolve(this.provider.resolveCompletionItem!(this.completion, token)).then(value => { Object.assign(this.completion, value); <ins> this._isResolved = true; </ins> sub.dispose(); }, err => { if (isPromiseCanceledError(err)) { // the IPC queue will reject the request with the // cancellation error -> reset cached this._resolveCache = undefined; <ins> this._isResolved = false; </ins> } }); }


Question: // create the suggestion resolver if (typeof provider.resolveCompletionItem !== 'function') { this._resolveCache = Promise.resolve(); <ins> this._isResolved = true; </ins> } } // resolving get isResolved() { <del> return Boolean(this._resolveCache); </del> <ins> return Boolean(this._isResolved); </ins> } private _resolveCache?: Promise<void>; <ins> private _isResolved?: boolean; </ins> async resolve(token: CancellationToken) { if (!this._resolveCache) { const sub = token.onCancellationRequested(() => { this._resolveCache = undefined; <ins> this._isResolved = false; </ins> }); this._resolveCache = Promise.resolve(this.provider.resolveCompletionItem!(this.completion, token)).then(value => { Object.assign(this.completion, value); <ins> this._isResolved = true; </ins> sub.dispose(); }, err => { if (isPromiseCanceledError(err)) { // the IPC queue will reject the request with the // cancellation error -> reset cached this._resolveCache = undefined; <ins> this._isResolved = false; </ins> } }); }


Question: // create the suggestion resolver if (typeof provider.resolveCompletionItem !== 'function') { this._resolveCache = Promise.resolve(); <ins> this._isResolved = true; </ins> } } // resolving get isResolved() { <del> return Boolean(this._resolveCache); </del> <ins> return Boolean(this._isResolved); </ins> } private _resolveCache?: Promise<void>; <ins> private _isResolved?: boolean; </ins> async resolve(token: CancellationToken) { if (!this._resolveCache) { const sub = token.onCancellationRequested(() => { this._resolveCache = undefined; <ins> this._isResolved = false; </ins> }); this._resolveCache = Promise.resolve(this.provider.resolveCompletionItem!(this.completion, token)).then(value => { Object.assign(this.completion, value); <ins> this._isResolved = true; </ins> sub.dispose(); }, err => { if (isPromiseCanceledError(err)) { // the IPC queue will reject the request with the // cancellation error -> reset cached this._resolveCache = undefined; <ins> this._isResolved = false; </ins> } }); }


Question: // create the suggestion resolver if (typeof provider.resolveCompletionItem !== 'function') { this._resolveCache = Promise.resolve(); <ins> this._isResolved = true; </ins> } } // resolving get isResolved() { <del> return Boolean(this._resolveCache); </del> <ins> return Boolean(this._isResolved); </ins> } private _resolveCache?: Promise<void>; <ins> private _isResolved?: boolean; </ins> async resolve(token: CancellationToken) { if (!this._resolveCache) { const sub = token.onCancellationRequested(() => { this._resolveCache = undefined; <ins> this._isResolved = false; </ins> }); this._resolveCache = Promise.resolve(this.provider.resolveCompletionItem!(this.completion, token)).then(value => { Object.assign(this.completion, value); <ins> this._isResolved = true; </ins> sub.dispose(); }, err => { if (isPromiseCanceledError(err)) { // the IPC queue will reject the request with the // cancellation error -> reset cached this._resolveCache = undefined; <ins> this._isResolved = false; </ins> } }); }


Question: // create the suggestion resolver if (typeof provider.resolveCompletionItem !== 'function') { this._resolveCache = Promise.resolve(); <ins> this._isResolved = true; </ins> } } // resolving get isResolved() { <del> return Boolean(this._resolveCache); </del> <ins> return Boolean(this._isResolved); </ins> } private _resolveCache?: Promise<void>; <ins> private _isResolved?: boolean; </ins> async resolve(token: CancellationToken) { if (!this._resolveCache) { const sub = token.onCancellationRequested(() => { this._resolveCache = undefined; <ins> this._isResolved = false; </ins> }); this._resolveCache = Promise.resolve(this.provider.resolveCompletionItem!(this.completion, token)).then(value => { Object.assign(this.completion, value); <ins> this._isResolved = true; </ins> sub.dispose(); }, err => { if (isPromiseCanceledError(err)) { // the IPC queue will reject the request with the // cancellation error -> reset cached this._resolveCache = undefined; <ins> this._isResolved = false; </ins> } }); }


Question: readonly outputScrolling?: boolean; readonly outputLinkifyFilePaths?: boolean; readonly minimalError?: boolean; <ins> readonly readonly?: boolean; </ins> } const defaultConfigConstants = Object.freeze({

Type: <bBug</bDelete a notebook file in a git repo Open it from the SCM view It's editable ;( VS Code version: Code - Insiders 1.94.0-insider (Universal) (, 2024-09-11T05:04:03.124Z) OS version: Darwin arm64 23.6.0 Modes: <!-- generated by issue reporter --
Fixed by


Question: export type SocketCloseEvent = NodeSocketCloseEvent | WebSocketCloseEvent | undefined; <ins> export interface SocketTimeoutEvent { readonly unacknowledgedMsgCount: number; readonly timeSinceOldestUnacknowledgedMsg: number; readonly timeSinceLastReceivedSomeData: number; } </ins> export interface ISocket extends IDisposable { onData(listener: (e: VSBuffer) => void): IDisposable; onClose(listener: (e: SocketCloseEvent) => void): IDisposable;

I've had an SSH window open in the background and noticed that it keeps going through reconnection. Looking at the logs, it looks like vscode is often triggering reconnection, even though nothing strange is going on with my network, as far as I know. Noticed this at home and also the office network. This is the log from vscode. The SSH log doesn't show anything unusual before the resolver is invoked again. Has anything changed here lately?
Sorry this issue fell through the cracks. Are you still running into this?
Yes
Do you perhaps have a log with tracing enabled? The reason in the original log appears to be timeout. That is triggered here -- So the conditions are: a message was written to the socket 20 seconds ago and the server side didn't acknowledge it (and) no data was received from the server within the past 20 seconds (and) no previous timeout event was fired within the past 20 seconds (this just avoid sending many timeout events) (and) the renderer process appears responsive (Just speculating) I think we have fixed something in the terminal land and we now no longer send any data in an idle window. Could it be that your window becomes idle and the connection is dropped by SSH? (I believe due to some terminal heartbeat mechanism there was always data being written every 5 seconds, but now that is no longer happening).
Well, now I can't repro it.
I've more logging via . Please keep an eye out. In case you hit this again, the logging will have more data.
This issue has been closed automatically because it needs more information and has not had recent activity. See also our guidelines. Happy Coding!
Finally got it
It seems like the log message you touch didn't show up there. But, it does show up in an earlier reconnection in this window (before I enabled trace logging). So I'll just share the full logs This shows a window that was mostly idle with 2 reconnections, the second one had logging at the trace level enabled
Here's my interpretation: the management connection timed out at due to our own timeout logic. We wrote one message and that message didn't get acknowledged within 20s by the server. As an observation, the connection didn't receive any bytes from the server within 320s (5 minutes).


Question: export type SocketCloseEvent = NodeSocketCloseEvent | WebSocketCloseEvent | undefined; <ins> export interface SocketTimeoutEvent { readonly unacknowledgedMsgCount: number; readonly timeSinceOldestUnacknowledgedMsg: number; readonly timeSinceLastReceivedSomeData: number; } </ins> export interface ISocket extends IDisposable { onData(listener: (e: VSBuffer) => void): IDisposable; onClose(listener: (e: SocketCloseEvent) => void): IDisposable;

this resulted in the core invoking on the SSH extension and the SSH extension replied with a new port i.e. instead of the previous . the extension host connection immediately closed at with code , indicating that perhaps the previous tunnel at was killed as part of invoking . Precisely 20 minutes later, at , both management and extension host connections receive a event from Chromium. I can't quite explain the first timeout, why would the server not acknowledge a message within 20 seconds? But the timing of the second disconnection seems to indicate to me that after 20 min of idleness, the underlying connection is closed. Anyone could be closing it: either on the client side by Chromium or the OS, either on the server side by nodejs or the OS, either by the SSH tunnel timing out. For the second disconnection I think we might need to do something to tackle . For a long time the terminal was doing a heartbeat mechanism which resulted in data being sent over the wire (and that would not make the connection idle), but that was fixed via Right now, if you are really idle in a window, the underlying connection will be idle too and I think the OS or someone closes it as idle after 20 minutes.
My best guess is some other weird optimization where network requests don't even get delivered when an app is in the background. I've noticed this in windows that are not focused
This issue has been closed automatically because it needs more information and has not had recent activity. See also our guidelines. Happy Coding!


Question: <del> /*--------------------------------------------------------------------------------------------- *  Copyright (c) Microsoft Corporation. All rights reserved. *  Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ declare module 'vscode' { // https://github.com/microsoft/vscode/issues/144944 /** * Impacts the behavior and appearance of the validation message. */ export enum InputBoxValidationSeverity { Info = 1, Warning = 2, Error = 3 } /** * Object to configure the behavior of the validation message. */ export interface InputBoxValidationMessage { /** * The validation message to display. */ readonly message: string; /** * The severity of the validation message. * NOTE: When using `InputBoxValidationSeverity.Error`, the user will not be allowed to accept (hit ENTER) the input. * `Info` and `Warning` will still allow the InputBox to accept the input. */ readonly severity: InputBoxValidationSeverity; } export interface InputBoxOptions { /** * The validation message to display. This will become the new {@link InputBoxOptions#validateInput} upon finalization. */ validateInput2?(value: string): string | InputBoxValidationMessage | undefined | null | Thenable<string | InputBoxValidationMessage | undefined | null>; } export interface InputBox { /** * The validation message to display. This will become the new {@link InputBox#validationMessage} upon finalization. */ validationMessage2: string | InputBoxValidationMessage | undefined; } } </del>

<!-- Do Not Delete This! featurerequesttemplate --<!-- Please read our Rules of Conduct: --<!-- Please search existing issues to avoid creating duplicates. --<!-- Describe the feature you'd like. --We already have this feature in Core: where you can return where . Truth be told, this already works in API today though it's not officially a part of the API today (so it could change at any moment). For example, I can do: <img width="738" alt="image" src="https://user-"with it goes from: to it goes from: to
ref
Created a TPI so moving this to April for Finalization.


Question: return parsedPath.ext.slice(1); default: { // dirname and dirname(arg) const n = variable === 'dirname' ? 0 : parseInt(arg); <del> const nthDir = this.getNthDirname(parsedPath, n); </del> <ins> const nthDir = this.getNthDirname(relevantPath, n); </ins> if (nthDir) { return nthDir; }

Currently, the editor does not support customizing file labels in a way that would allow filenames to be prefixed with the name of the application or folder from the start of the path. This limitation affects projects structured in a monorepo setup, particularly when files are deeply nested within application-specific directories. Supporting negative indices for could solve this problem. This would enable accessing folder names starting from the beginning of the path. Implementing negative indices would cater to the needs of projects with files at various nesting levels within their structure.
Is the reasoning not the other way round or am I misunderstanding how it currently works? E.g. I'd like to access last 2 parts of the path, but the description of the settings suggests that only the single final and the starting folders can be accessed.
For the following path you will get the following values: Path: : : : : With negative indices: : : :
Here is an example of the mono repo: It has a root and multiple projects in the folder. It is common to make changes in multiple "packages" at once, and it would be convenient if we could identify files by the . Example: -
Verification steps: a custom editor label. The template should include , for which is a negative number. For example, add in , item: value: a file which matches the pattern specified in step 1. For the example specified in step 1: every file matches the pattern editor tab should have set the correct folder name for . Here an explanation on how negative indices work: For the example specified in step 1: the name of the editor in the tab should start with the top level folder the file is located in. In the vscode repo the file would have the custom name
I have followed the steps for the verification and the settings file is renamed to . The TS files are renamed correctly to . Is this expected?


Question: readonly token?: CancellationToken; } <del> /*marked.* </del> <ins> /** </ins> * Renders a string of markdown as a document. * * Uses VS Code's syntax highlighting code blocks.

<!-- Do Not Delete This! bugreporttemplate --<!-- Please read our Rules of Conduct: --<!-- Read our guide about submitting issues: --<!-- Search existing issues to avoid creating duplicates. --<!-- Test using the latest Insiders build to see if your issue has already been fixed: --<!-- Instead of creating your report here, use 'Report Issue' from the 'Help' menu in VS Code to pre-fill useful information. --<!-- Launch with to check. --<!-- If you answered No above, use 'Help: Start Extension Bisect' from Command Palette to try to identify the cause. --<!-- Issues caused by an extension need to be reported directly to the extension publisher. The 'Help Report Issue' dialog can assist with this. --- VS Code Version: Version: 1.93.0-insider (user setup) Commit: Date: 2024-09-04T13:13:15.344Z Electron: 30.4.0 ElectronBuildId: Chromium: 124.0.6367.243 20.15.1 V8: 12.4.254.20-electron.0 OS: Windows_NT x64 10.0. OS Version: Windows 11 Insiders Steps to Reproduce: in the first paragraph doesn't appear to be a link, which I was surprised about: ! I hover over it, I see it's supposed to be a link, but clicking on it does nothing: ! The other docs links at the bottom of the playground appear correctly as links and do open when I click on them: !
This seems related to the bookmark issue we had in the release notes. The other bookmark links in the interactive playground don't work either.
Fixed the toc links. Need another fix for the documentation link too (which use command uris)
The Editor Playground is not showing at all anymore in the latest insiders. When selecting the Editor Playground menu item, a black editor comes up.
hm working from oss builds. Insiders, it seems like the error is:
Actually unrelated. Seems like a duplicate of and fixed by


Question: constructor() { super({ id: SETTINGS_EDITOR_COMMAND_FOCUS_CONTROL, <del> precondition: CONTEXT_SETTINGS_ROW_FOCUS, </del> <ins> precondition: ContextKeyExpr.and(CONTEXT_SETTINGS_EDITOR, CONTEXT_SETTINGS_ROW_FOCUS), </ins> keybinding: { primary: KeyCode.Enter, weight: KeybindingWeight.WorkbenchContrib,

On Safari/Codespace Open a notebook Open settings Switch to notebook Press enter https://user-


Question: @IInstantiationService private readonly instantiationService: IInstantiationService, @ISharedProcessService private readonly sharedProcessService: ISharedProcessService, @IProgressService private readonly progressService: IProgressService, <del> @ILabelService private readonly labelService: ILabelService, @IUriIdentityService private readonly uriIdentityService: IUriIdentityService </del> <ins> @ILabelService private readonly labelService: ILabelService </ins> ) { super();

a JS file: to definition on Bug I see this scary notification: <img width="671" alt="Screen Shot 2022-07-26 at 2 31 10 PM" src="https://user-"In this case, the user was likely just trying to view the file Would it be possible to either: show this notification when saving? open these file as ?
This came in via from and I think is not good. This also reminds me that when debugging you may end up in a file from the installation dir. I think we should probably revert the change for now and think about a better solution unless there are objections.
I agree. Maybe pursue your earlier suggestion of opening all installation directory files in readonly mode. Perhaps combine with a scary warning if you save a new file in there.


Question: # This makes sure the model is included in the package !@vscode/vscode-languagedetection/model/** <ins> # Ensure only the required telemetry pieces are loaded in web to reduce bundle size @microsoft/1ds-core-js/** @microsoft/1ds-post-js/** @microsoft/applicationinsights-core-js/** @microsoft/applicationinsights-shims/** !@microsoft/1ds-core-js/dist/ms.core.min.js !@microsoft/1ds-post-js/dist/ms.post.min.js !@microsoft/applicationinsights-core-js/browser/applicationinsights-core-js.min.js !@microsoft/applicationinsights-shims/dist/umd/applicationinsights-shims.min.js </ins>

We very likely do not need to ship all of this in the web bits: This was already once done before in , and when dependencies got renamed this was once again forgotten about.


Question: })); } <ins> clearInput(): void { this.contentDisposables.clear(); this.transientDisposables.clear(); super.clearInput(); } </ins> focus(): void { if (this.activeElement) { this.activeElement.focus();

Issue Type: <bBug</bdeveloper tools themes Everything appears to work as normal, but the console gets flooded with errors. VS Code version: Code - Insiders 1.36.0-insider (, 2019-06-14T10:36:51.530Z) OS version: Darwin x64 18.6.0 <details<summarySystem Info</summary </details<details<summaryExtensions (13)</summaryExtensionVersion ------ vscode-eslint1.9.0 gitlens9.8.2 vsc-material-theme2.9.0 remote-containers0.61.0 remote-ssh-edit-nightly2019.6. remote-ssh-explorer-nightly2019.6. remote-ssh-nightly2019.6. vscode-typescript-tslint-plugin1.1.0 debugger-for-chrome4.11.3 vscode-docker0.6.2 material-icon-theme3.8.0 polacode0.3.2 vscode-sort-json1.13.0 </details<!-- generated by issue reporter --
I can't repo this. Which developer tools and theme are you talking about specifically?
I tried repoing this again and it appears that this error is related to installing a new theme. Here are some better repo steps: developer tools themes (no errors) a theme themes (errors even if it is one of the themes shipped with VS code) The errors go away after a reload, but installing a theme does not tell me a reload is required.
Ok, with the extensions view open, I sometimes see Does not cause seem to cause any actual problems
I get that as well sometimes, but do you not get the webview must be attached to the DOM error as well?
I can reproduce it. Steps: on any extension to load it's Readme in an editor that editor theme (this triggers the webview error) step 1 (this triggers the guestInstanceId error)


Question: }, { "command": "npm.packageManager", <del> "title": "%command.packageManager" </del> <ins> "title": "%command.packageManager%" </ins> } ], "menus": {

Issue Type: <bBug</bkeyboard shortcuts for "npm.packageManager" ! VS Code version: Code - Insiders 1.62.0-insider (, 2021-10-11T05:16:36.885Z) OS version: WindowsNT x64 10.0. Restricted Mode: No <details<summarySystem Info</summary </details<details<summaryExtensions (26)</summaryExtensionVersion ------ vscode-javascript-repl0.7.2 Bookmarks13.2.2 gitlens11.6.1 copilot-nightly1.5.3048 remotehub0.15.0 rest-client0.24.5 monkey-patch0.1.15 search-node-modules1.3.0 vscode-docker1.17.0 vscode-edge-devtools1.3.1 remote-containers0.202.1 remote-ssh-edit-nightly2021.10.5220 remote-ssh-nightly2021.10.5220 remote-wsl0.58.2 hexeditor1.8.2 js-debug-nightly2021.10.817 vscode-typescript-next4.5. debugger-for-edge1.0.15 vetur0.34.1 explorer-exclude-vscode-extension1.2.0 reveal1.2.7 luna-paint0.10.0 vscode-icons11.6.0 cloudmusic9.2.4 markdown-all-in-one3.4.0 material-theme3.13.2 </details<details<summaryA/B Experiments</summary</details<!-- generated by issue reporter --


Question: return GitErrorCodes.RepositoryNotFound; } else if (/unable to access/.test(stderr)) { return GitErrorCodes.CantAccessRemote; <ins> } else if (/Couldn't find remote ref/.test(stderr)) { return GitErrorCodes.CantAccessRemote; </ins> } return void 0;

We have the ability to select which branch to push to in the source control, could we please have a pull from option? To target specific branches. Unless it exists already? I have only recently started using VS Code and i love it!
This is an easy contribution, care to submit a PR? PS: You currently have the ability to select which remote to push to, not which branch.
I'll take on this. Is it correct that branch names are not stored anywhere? I've checked in and cannot find anywhere they could be stored. If there is nothing, I'll just take a user input using showInputBox.
I created a pull request for this: Please take a look when you have the chance.
merge conflict is fixed.
Merging with


Question: this.selectedConfig = config || (this.selectedLaunch && this.selectedName ? this.selectedLaunch.getConfiguration(this.selectedName) : undefined); if (this.selectedConfig) { this.debugConfigurationTypeContext.set(this.selectedConfig.type); <del> this.storageService.store(DEBUG_SELECTED_CONFIG, JSON.stringify(this.selectedConfig), StorageScope.WORKSPACE); </del> } else { this.debugConfigurationTypeContext.reset(); <del> this.storageService.remove(DEBUG_SELECTED_CONFIG, StorageScope.WORKSPACE); </del> } if (this.selectedLaunch !== previousLaunch || this.selectedName !== previousName) {

VS Code 1.46 start VS Code Stable use this launch config: close "" editor run it Observe: (everything is good) <img width="749" alt="2020-06-1609-34-37" src="https://user-"- shutdown VS Code open same project with VS Code Insiders (or any other editor) modify "" and include argument "1234" between "hello" and "world" shutdown VS Code Insiders launch VS Code Stable again go to debug view and run "echo args" launch config open Observe: <img width="1080" alt="2020-06-1609-39-11" src="https://user-"VS Code runs old launch config which lacks the "1234". Workaround: an edit change of is needed before VS Code will run the correct launch config.
We no longer store the configuration in storage. The only downside is that now when you select a dynamic configuration, reload, we do not preserve the dynamic configuratoin and you have to select it again. For that we would have to store the "origin" as you suggested, but that I will tackle as a seperate issue, thus reopening
I've verified that no stale launch configs are run.


Question: return this._modelService.createModel(resource.fragment, null, resource, false); } <ins> public showAccessibleViewHelp(): void { if (!this._currentProvider) { return; } const previousProvider = Object.assign({}, this._currentProvider); const accessibleViewHelpProvider = Object.assign({}, this._currentProvider); accessibleViewHelpProvider.options.type = AccessibleViewType.Help; accessibleViewHelpProvider.provideContent = () => this._getAccessibleViewHelpDialogContent(accessibleViewHelpProvider!); accessibleViewHelpProvider.onClose = () => this.show(previousProvider); this._accessiblityHelpIsShown.set(true); const delegate: IContextViewDelegate = { getAnchor: () => { return { x: (window.innerWidth / 2) - ((Math.min(this._layoutService.dimension.width * 0.62 /* golden cut */, DIMENSIONS.MAX_WIDTH)) / 2), y: this._layoutService.offset.quickPickTop }; }, render: (container) => { container.classList.add('accessible-view-container'); return this._render(accessibleViewHelpProvider, container, true); }, }; this._contextViewService.showContextView(delegate); } private _getAccessibleViewHelpDialogContent(provider: IAccessibleContentProvider): string { const navigationHint = this._getNavigationAriaHint(provider.verbositySettingKey); const goToSymbolHint = this._getGoToSymbolHint(provider); return [navigationHint, goToSymbolHint].filter(i => !!i).join('n'); } </ins> private _getNavigationAriaHint(verbositySettingKey: AccessibilityVerbositySettingId): string { let hint = ''; const nextKeybinding = this._keybindingService.lookupKeybinding('editor.action.accessibleViewNext')?.getAriaLabel();

accessible view go to symbol picker are closed sometimes (when there's more than one item) cc


Question: import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { CopyAction } from 'vs/editor/contrib/clipboard/browser/clipboard'; import { localize } from 'vs/nls'; <ins> import { CONTEXT_ACCESSIBILITY_MODE_ENABLED } from 'vs/platform/accessibility/common/accessibility'; </ins> import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; <ins> import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; </ins> import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { TerminalLocation } from 'vs/platform/terminal/common/terminal'; import { IUntitledTextResourceEditorInput } from 'vs/workbench/common/editor';

is used in the inline chat widget to accept a suggestion is used in the accessible view to accept an inline completion suggestion There is no keybinding to insert the current chat code block suggestion at the current cursor location is used to submit a comment from within a comment widget suggested aligning these. what do you think of adding a default keybinding of for inserting the current chat code block at the current cursor location? I will change the inline completion suggestion acceptance in the accessible view to .
-- Did you mean ? I think can be used for submission. There is no issue with this key. Submitting pull request, for example, is also . However, when Accepting something, we may want to use for the consistency.
I was suggesting we align all of these to use rather than .
I don't think people will be in favor of to accept in the inline chat widget as that would break muscle memory.
-- FYI, the following keybinding is also :
I've refined the original content of this issue to reflect the ask. We shall see what people say.
Here is my summary: for submitting actions. Evidence: GitHub Pull Request uses this keybinding. Also, "Report Issue" under Help menu also adopts as its default keybinding for submitting issue content. for accepting or inserting suggestions. Evidence: already uses this keybinding. Also, Accessible View adapted this for accepting the current inline suggestion.
I think that's not a good keybinding and that Ctrl+Enter is way better because it's easier to reach, e.g on a DE/CH keyboard you need to press Shift+7 to get /
I am then not against unifying all the listed keybindings in favor of .
To verify: Focus an editor Enable screen reader mode Focus a chat code block Use and verify that the code is inserted in the editor at the current cursor position


Question: return undefined; } <del> export function getSelectionTextFromEditor(allowUnselectedWord: boolean, editor: IEditor): string | null { </del> <ins> export function getSelectionTextFromEditor(allowUnselectedWord: boolean, activeEditor: IEditor): string | null { let editor = activeEditor; if (isDiffEditor(editor)) { if (editor.getOriginalEditor().hasTextFocus()) { editor = editor.getOriginalEditor(); } else { editor = editor.getModifiedEditor(); } } </ins> if (!isCodeEditor(editor) || !editor.hasModel()) { return null;

<!-- Do Not Delete This! bugreporttemplate --<!-- Please read our Rules of Conduct: --<!-- Read our guide about submitting issues: --<!-- Search existing issues to avoid creating duplicates. --<!-- Test using the latest Insiders build to see if your issue has already been fixed: --<!-- Instead of creating your report here, use 'Report Issue' from the 'Help' menu in VS Code to pre-fill useful information. --<!-- Launch with to check. --Does this issue occur when all extensions are disabled?: Yes/No <!-- If you answered No above, use 'Help: Start Extension Bisect' from Command Palette to try to identify the cause. --<!-- Issues caused by an extension need to be reported directly to the extension publisher. The 'Help Report Issue' dialog can assist with this. --- VS Code Version: OS Version: Steps to Reproduce: up a keybinding to launch quick text search a diff editor some text quick text search Nothing in input: ! Expected: !
Hi , I guess the PR has fixed this already. I tried reproducing it locally with the changes of my current PR, and it seems to be working fine. Attaching the screen recording for your reference.
You need to open a diff editor. For example: open a folder that is a git repo Make a change in any file Go to the source control viewlet and click on the changed file: <img width="1679" alt="Screenshot 2023-10-08 at 2 57 57 PM" src=""Select text in the right editor The bug appears.
Got it, will try to correct it
Hi , I understand that you must be busy. Can you take a look any time you are available? I need some help in figuring out where exactly the diff editor is interacting with search bindings. For example, I am not able to understand how hitting cmd+f calls the search function from the diff, or where it is getting called from. Similarly for the quickTextSearch, can you point me in the right direction?
I was looking for code pointers and ended up finding the bug anyways:


Question: const viewStartPosition = this._context.viewModel.coordinatesConverter.convertModelPositionToViewPosition(modelStartPosition); const verticalOffsetStart = this._context.viewLayout.getVerticalOffsetForLineNumber(viewStartPosition.lineNumber); const editorScrollTop = this._context.viewLayout.getCurrentScrollTop(); <ins> const editorScrollLeft = this._context.viewLayout.getCurrentScrollLeft(); </ins> const top = parentBounds.top + verticalOffsetStart - editorScrollTop; const height = (this._primarySelection.endLineNumber - this._primarySelection.startLineNumber + 1) * lineHeight; <del> let left = parentBounds.left + contentLeft; </del> <ins> let left = parentBounds.left + contentLeft - editorScrollLeft; </ins> let width: number; if (this._primarySelection.isEmpty()) {

Version: 1.94.0-insider Commit: Date: 2024-09-23T05:12:01.964Z Electron: 30.5.0 ElectronBuildId: Chromium: 124.0.6367.243 20.16.0 V8: 12.4.254.20-electron.0 OS: Darwin arm64 22.6.0 Steps to Reproduce: a very long line. Google IME for Japanese. at the last of the long line. position of the IME widget is not optimal. ! With , it is worse. !
Hi yes thanks a lot for opening this valuable issue. This is happening because we use the model ranges instead of the view ranges in the control bounds, selection bounds and character bounds positioning. The following PR fixes this:
I try the OSS version from the repository. The issue with is fixed. However, I still see the issue with Version: 1.94.0 Commit: Date: Unknown Electron: 30.5.1 ElectronBuildId: undefined Chromium: 124.0.6367.243 20.16.0 V8: 12.4.254.20-electron.0 OS: Darwin arm64 22.6.0 a very long line. Google IME for Japanese. at the last of the long line. position of the IME widget is not optimal. !
Oh I see thanks for letting me know that is curious. I will reopen the issue and have a look.
After investigating the issue, I believe this happens because I didn't remove the editor scroll left position from the bounds set for the selection and character bounds. The following PR should fix this.


Question: private hoverPosition: Position | null = null; private mouseDown = false; private exceptionWidgetVisible: IContextKey<boolean>; <ins> private gutterIsHovered = false; </ins> private exceptionWidget: ExceptionWidget | undefined; private configurationWidget: FloatingClickWidget | undefined;

<!-- Do Not Delete This! bugreporttemplate --<!-- Please read our Rules of Conduct: --<!-- Read our guide about submitting issues: --<!-- Search existing issues to avoid creating duplicates. --<!-- Test using the latest Insiders build to see if your issue has already been fixed: --<!-- Instead of creating your report here, use 'Report Issue' from the 'Help' menu in VS Code to pre-fill useful information. --<!-- Launch with to check. --Does this issue occur when all extensions are disabled?: Yes/No <!-- If you answered No above, use 'Help: Start Extension Bisect' from Command Palette to try to identify the cause. --<!-- Issues caused by an extension need to be reported directly to the extension publisher. The 'Help Report Issue' dialog can assist with this. --I'm currently implementing hit conditions in vscode-php-debug. Xdebug has very limited conditions it supports and we need to signal the user if they typed something unsupported. If a breakpoint has such a condition we try to return that in the field in the setBreakpointResponse/body/breakpoints. There is the implementation line: I noticed that the message is only visible when hovering with the mouse over the unverified breakpoint and only when the DAP isn't in a running state. If the adapter is in a breakpoint, there is no indication why this breakpoint isn't valid. Steps to Reproduce: and build vscode-php-debug from branch . a PHP debug Optionally add under setting. a breakpoint with hit condition "blabla". breakpoint should be unverified, hovering over it should show the message. Messages exchanged until now: launchResponse initializedEvent setBreakpointsRequest (see full message below) setBreakpointsResponse setFunctionBreakpointsRequest setFunctionBreakpointsResponse setExceptionBreakpointsRequest setExceptionBreakpointsResponse configurationDoneRequest configurationDoneResponse threadsRequest threadsResponse () a PHP process that will cause the debug adapter to move into running state. Following messages will be exchanged: ThreadEvent () StoppedEvent () threadsRequest threadsResponse () stackTraceRequest stackTraceResponse threadsRequest threadsResponse () scopesRequest scopesResponse variablesRequest variablesResponse this point hovering over the unverified breakpoint will not show the message.


Question: private hoverPosition: Position | null = null; private mouseDown = false; private exceptionWidgetVisible: IContextKey<boolean>; <ins> private gutterIsHovered = false; </ins> private exceptionWidget: ExceptionWidget | undefined; private configurationWidget: FloatingClickWidget | undefined;

Full breakpoint messages Version: 1.59.0 (user setup) Commit: Date: 2021-08-04T23:13:12.822Z Electron: 13.1.7 Chrome: 91.0.4472.124 14.16.0 V8: 9.1.269.36-electron.0 OS: Windows_NT x64 10.0. Full log of message exchange (DAP and Xdebug)
Maybe a good issue to get introduced to the breakpoint message computation. Thus assigning to Code pointer where we compute breakpoint messages There is probably some check wrong depending on the debug state.
Any luck looking into this? I just want to check if there is something I did wrong before releasing. Btw: Is there any way to enable DAP logging of node/typescript adapter so I can compare messages?
we just did not have a chance to look into this yet. will probably look into it soon, if he is busy I can also help. Yes, it is possible to enable DAP logging for js-debug. Add to your and start debugging. The location of the log file on your disk will be written to the Debug Console. That log file should have the communication.
I totally lost this one, and it's fairly straightforward. This is due to us disabling editor hover in favor of the custom debug hover.
<!-- AUTHORVERIFICATIONREQUEST --This bug has been fixed in the latest release of ! you can help us out by commenting if things are now working as expected. If things still don't seem right, please ensure you're on version of Insiders (today's or later - you can use in the command palette to check), and leave a comment letting us know what isn't working as expected. Happy Coding!


Question: // Dispose old listeners this.activeEditorListeners.clear(); <del> // Handle editor change unless the editor is transient </del> <ins> // Handle editor change unless the editor is transient. In that case // setup a listener to see if the transient editor becomes non-transient // (https://github.com/microsoft/vscode/issues/211769) </ins> if (!activeEditorPane?.group.isTransient(activeEditorPane.input)) { this.handleActiveEditorChange(activeEditorGroup, activeEditorPane); } else { <del> this.logService.trace(`[History]: ignoring transient editor change (editor: ${activeEditorPane.input?.resource?.toString()}})`); </del> <ins> this.logService.trace(`[History]: ignoring transient editor change until becoming non-transient (editor: ${activeEditorPane.input?.resource?.toString()}})`); const transientListener = activeEditorGroup.onDidModelChange(e => { if (e.kind === GroupModelChangeKind.EDITOR_TRANSIENT && e.editor === activeEditorPane.input && !activeEditorPane.group.isTransient(activeEditorPane.input)) { transientListener.dispose(); this.handleActiveEditorChange(activeEditorGroup, activeEditorPane); } }); this.activeEditorListeners.add(transientListener); </ins> } // Listen to selection changes unless the editor is transient

Steps to Reproduce: recently opened ("Clear Editor History") quick open and search for a file (such as a TS file) to see symbols (notice how the file opens in the background) to a symbol = when you open quick open again, the file is not part of the recently opened section //cc this is a regression from the transient work we did


Question: this.ariaLabel = label; } <del> setMarkerSelection(marker: Marker): void { </del> <ins> setMarkerSelection(selection?: Marker[], focus?: Marker[]): void { </ins> if (this.isVisible()) { <del> if (marker) { const markerNode = this.findMarkerNode(marker); </del> <ins> if (selection && selection.length > 0) { this.setSelection(selection.map(m => this.findMarkerNode(m))); </ins> <del> if (markerNode) { this.setFocus([markerNode]); this.setSelection([markerNode]); </del> <ins> if (focus && focus.length > 0) { this.setFocus(focus.map(f => this.findMarkerNode(f))); } else { this.setFocus([this.findMarkerNode(selection[0])]); </ins> } } else if (this.getSelection().length === 0) { const firstVisibleElement = this.firstVisibleElement;

Testing Not sure if this lack of functionality blocks anything truly useful, but at least in the treeview, you can multi-select problems and copy them to the clipboard. In the table view you can't.
This also has the side effect of losing a multi-select that you may have had in the treeview.
what was the thinking for enabling multi-select in the problems treeview? Was there a particular scenario or this is what the default behaviour is for a treeview? If there is a real scenario, than we should enable multi-select in the table as well.
You can copy multiple problems is the primary use case.
that makes sense. Thanks!


Question: readonly initialCwd?: string; readonly os?: OperatingSystem; readonly capabilities: ITerminalCapabilityStore; <ins> readonly usedShellIntegrationInjection: boolean; </ins> readonly statusList: ITerminalStatusList;

Type: <bBug</b<!-- Please fill in all XXX markers --Python Debug Console shows "Shell integration failed to activate" ! a Python file command over terminal with mouse <!-- After creating the issue on GitHub, you can add screenshots and GIFs of what is happening. Consider tools like , or for GIF creation. --<!-- NOTE: Everything below except Python output panel is auto-generated; no editing required. Please do provide Python output panel. --- Python version (& distribution if applicable, e.g. Anaconda): 3.10.5 Type of virtual environment used (e.g. conda, venv, virtualenv, etc.): Poetry Value of the setting: Default <details<summaryOutput for <codePython</codein the <codeOutput</codepanel (<codeView</code→<codeOutput</code, change the drop-down the upper-right of the <codeOutput</codepanel to <codePython</code) </summary<p</p</details<details<summaryUser Settings</summary<p</p</detailsExtension version: 2022.12.0 VS Code version: Code 1.70.1 (, 2022-08-10T06:08:33.642Z) OS version: WindowsNT x64 10.0. Modes: <details<summarySystem Info</summary </details<details<summaryA/B Experiments</summary</details<!-- generated by issue reporter --
Same problem here
Same.
VS Code creates this terminal as a part of the request that gets sent over DAP. It seems like it is failing when VS code tries to create the terminal, moving this there for further investigation.
what needs to be done here for correct activation?
This issue has been closed automatically because it needs more information and has not had recent activity. See also our guidelines. Happy Coding!
Can you elaborate why this ticket was closed? The issue is still there.
what needs to be done in order to prevent the "Shell integration" from failing?
If run in terminal replaces the shell args then this warning shouldn't be showing (as shell integration should be disabled). It does works in the JS debug terminal which is a terminal profile that sets an environment. If the terminal is launched without args this would be an issue on the terminal. how do I create a python debug console?


Question: readonly initialCwd?: string; readonly os?: OperatingSystem; readonly capabilities: ITerminalCapabilityStore; <ins> readonly usedShellIntegrationInjection: boolean; </ins> readonly statusList: ITerminalStatusList;

in a Python file the "Debug Python File" action from the editor drop down opens a python debug console
And Python debugger creates the "Python Debug Console" here: It is unrelated to the "JS debug terminal". It is just an integrated terminal where the debugger or runtime is launched.
Looks like the path and args should be supported because they're the defaults: Unless they were configured specially. We'll need to look into this.
I noticed I have the same problem
same here with using powershell core ! but i can't reproduce it with fresh system and latest vs code and latest powershel core using windows sandbox !
Getting the same error for the past couple weeks, on ssh terminal. !
what's the terminal profile you're using there? Is it a custom ssh one defined in your settings?
i'm new to vs and having this issue as well.. anyone please help~ thanks~!!
Can you share your file and also do a trace log while creating a terminal? /label info-needed terminal
i've uploaded 3 images at 1) terminal 2) renderer log 3) file let me know if you need anything else.. thanks~!
thanks for that info. I see the when I create a terminal like you do does using pwsh instead as your default profile fix it?
I am using default bash terminal over ssh. I did not define any specific terminal profiles. Here's the installation printout when it says starting ssh terminal. I can do but I don't know what to grep to share.
thanks for helping but i've just tried.. and it doesn't work.. same error still there.. what else can i do??
this is working for me - anyone seeing this should closely inspect their default profile - that's the primary reason shell integration would not get activated !
Same issue, but my debugger is cppdbg. Really annoying, I hope someone fixes it soon
Same issue with npm + devcontainer
I was having the same problem, but removing and using instead in seem to work for me
, very interesting, I tried what you have described (I just used "terminal.integrated.defaultProfile", but not "terminal.integrated.automationProfile", since I couldn't find it in settings), and it partly worked on one computer (the debugger showed up in the terminal and worked as intended, however when pointing at the terminal it still said "shell integration failed to activate").


Question: readonly initialCwd?: string; readonly os?: OperatingSystem; readonly capabilities: ITerminalCapabilityStore; <ins> readonly usedShellIntegrationInjection: boolean; </ins> readonly statusList: ITerminalStatusList;

THE INTERESTING PART IS THIS: I tried the same , , , on a different computer and the debugger didn't work at all, with the same error. The only difference might be that on the first pc I used mingw from MSYS gcc , and on the second pc I use normal MinGW gcc. I will check later to see if that is the cause
If this issue is related to what we expect, then you should see a warning of the form can one of you pls check if you see this?
i can't see this warning, all i get is "Shell integration failed to activate" and nothing more.
the warning would show up in devtools' console I think. For this issue I'm going to fix the shell integration failed to activate message in the hover when we didn't even try to inject. For example: !
is there any solution to this issue?
Yes you should no longer see that message. Can you please verify that's true in insider's?
<!-- AUTHORVERIFICATIONREQUEST --This bug has been fixed in the latest release of ! you can help us out by commenting if things are now working as expected. If things still don't seem right, please ensure you're on version of Insiders (today's or later - you can use in the command palette to check), and leave a comment letting us know what isn't working as expected. Happy Coding!
the issue was fixed, thank you for your responsiveness,
When will this be resolved for regular users (not Insiders)? I am using 1.72.2
in abt a week


Question: "vscode-proxy-agent": "^0.12.0", "vscode-regexpp": "^3.1.0", "vscode-textmate": "7.0.1", <del> "xterm": "4.19.0-beta.24", "xterm-addon-search": "0.9.0-beta.21", </del> <ins> "xterm": "4.19.0-beta.25", "xterm-addon-search": "0.9.0-beta.22", </ins> "xterm-addon-serialize": "0.7.0-beta.12", "xterm-addon-unicode11": "0.4.0-beta.3", <del> "xterm-addon-webgl": "0.12.0-beta.28", "xterm-headless": "4.19.0-beta.24", </del> <ins> "xterm-addon-webgl": "0.12.0-beta.29", "xterm-headless": "4.19.0-beta.25", </ins> "yauzl": "^2.9.2", "yazl": "^2.4.3" },

<!-- Do Not Delete This! bugreporttemplate --<!-- Please read our Rules of Conduct: --<!-- Read our guide about submitting issues: --<!-- Search existing issues to avoid creating duplicates. --<!-- Test using the latest Insiders build to see if your issue has already been fixed: --<!-- Instead of creating your report here, use 'Report Issue' from the 'Help' menu in VS Code to pre-fill useful information. --<!-- Launch with to check. --Does this issue occur when all extensions are disabled?: Yes VS Code Version: 1.67.0-insiders OS Version: macOS 12.3.1 Steps to Reproduce: the terminal scrollback to 30'000 for With version 1.65.0 this was not a problem, but there was no highlighting. It would be enough if I can disable the highlighting or disable the highlighting automatically if the history is longer than x lines. https://user-
That doesn't look or feel slow to me - can you elaborate on what the issue you're dealing with is?
Is it Upstream or VSCode Core.
Happens for me too, when you search then type abs quickly and it takes ~5-10 seconds for the letters to appear, same goes with erasing letters.
Search is fast now but matches info is not being shown -