From 68c2087f8f44ff8081122f649b3165f06d565687 Mon Sep 17 00:00:00 2001 From: Roland Grunberg Date: Wed, 20 Nov 2024 12:00:55 -0500 Subject: [PATCH 01/10] Expose adjustWhitespace to TextEditor API. - Disable adjustWhitespace in text edits with snippets so that code actions do not adjust snippet indentation by default - Adjust Emmet extension so that adjustWhitespace is enabled - Add testcase Signed-off-by: Roland Grunberg --- extensions/emmet/src/abbreviationActions.ts | 4 +-- .../src/singlefolder-tests/editor.test.ts | 31 +++++++++++++++++++ .../contrib/snippet/browser/snippetSession.ts | 2 +- .../workbench/api/browser/mainThreadEditor.ts | 5 +-- .../api/browser/mainThreadEditors.ts | 4 +-- .../workbench/api/common/extHost.protocol.ts | 3 ++ .../contrib/bulkEdit/browser/bulkTextEdits.ts | 2 +- src/vscode-dts/vscode.d.ts | 4 +++ 8 files changed, 47 insertions(+), 8 deletions(-) diff --git a/extensions/emmet/src/abbreviationActions.ts b/extensions/emmet/src/abbreviationActions.ts index 3326722905c6b..30ebfe56f93b8 100644 --- a/extensions/emmet/src/abbreviationActions.ts +++ b/extensions/emmet/src/abbreviationActions.ts @@ -636,7 +636,7 @@ async function expandAbbreviationInRange(editor: vscode.TextEditor, expandAbbrLi for (const expandAbbrInput of expandAbbrList) { const expandedText = expandAbbr(expandAbbrInput); if (expandedText) { - await editor.insertSnippet(new vscode.SnippetString(expandedText), expandAbbrInput.rangeToReplace, { undoStopBefore: false, undoStopAfter: false }); + await editor.insertSnippet(new vscode.SnippetString(expandedText), expandAbbrInput.rangeToReplace, { undoStopBefore: false, undoStopAfter: false, adjustWhitespace: true }); insertedSnippetsCount++; } } @@ -650,7 +650,7 @@ async function expandAbbreviationInRange(editor: vscode.TextEditor, expandAbbrLi const expandedText = expandAbbr(anyExpandAbbrInput); const allRanges = expandAbbrList.map(value => value.rangeToReplace); if (expandedText) { - return editor.insertSnippet(new vscode.SnippetString(expandedText), allRanges); + return editor.insertSnippet(new vscode.SnippetString(expandedText), allRanges, { undoStopBefore: true, undoStopAfter: true, adjustWhitespace: true }); } return false; } diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/editor.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/editor.test.ts index 070a169968447..8f3f0fb01ea5d 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/editor.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/editor.test.ts @@ -94,6 +94,37 @@ suite('vscode API - editors', () => { }); }); + /** + * Given : + * This is line 1 + * | + * + * Expect : + * This is line 1 + * This is line 2 + * This is line 3 + * + * The 3rd line should not be auto-indented, as the edit already + * contains the necessary adjustment. + */ + test('insert snippet with replacement, avoid adjusting indentation', () => { + const snippetString = new SnippetString() + .appendText('This is line 2\n This is line 3'); + + return withRandomFileEditor('This is line 1\n ', (editor, doc) => { + editor.selection = new Selection( + new Position(1, 3), + new Position(1, 3) + ); + + return editor.insertSnippet(snippetString, undefined, { undoStopAfter: false, undoStopBefore: false, adjustWhitespace: false }).then(inserted => { + assert.ok(inserted); + assert.strictEqual(doc.getText(), 'This is line 1\n This is line 2\n This is line 3'); + assert.ok(doc.isDirty); + }); + }); + }); + test('insert snippet with replacement, selection as argument', () => { const snippetString = new SnippetString() .appendText('has been'); diff --git a/src/vs/editor/contrib/snippet/browser/snippetSession.ts b/src/vs/editor/contrib/snippet/browser/snippetSession.ts index 79cf57b3abf39..e0bacc7662e5a 100644 --- a/src/vs/editor/contrib/snippet/browser/snippetSession.ts +++ b/src/vs/editor/contrib/snippet/browser/snippetSession.ts @@ -582,7 +582,7 @@ export class SnippetSession { } const newNodes = parser.parseFragment(template, snippet); - SnippetSession.adjustWhitespace(model, range.getStartPosition(), true, snippet, new Set(newNodes)); + SnippetSession.adjustWhitespace(model, range.getStartPosition(), adjustWhitespace, snippet, new Set(newNodes)); snippet.resolveVariables(resolver); const snippetText = snippet.toString(); diff --git a/src/vs/workbench/api/browser/mainThreadEditor.ts b/src/vs/workbench/api/browser/mainThreadEditor.ts index 7dd21444995d3..fa923804be1be 100644 --- a/src/vs/workbench/api/browser/mainThreadEditor.ts +++ b/src/vs/workbench/api/browser/mainThreadEditor.ts @@ -14,7 +14,7 @@ import { ITextModel, ITextModelUpdateOptions } from '../../../editor/common/mode import { ISingleEditOperation } from '../../../editor/common/core/editOperation.js'; import { IModelService } from '../../../editor/common/services/model.js'; import { SnippetController2 } from '../../../editor/contrib/snippet/browser/snippetController2.js'; -import { IApplyEditsOptions, IEditorPropertiesChangeData, IResolvedTextEditorConfiguration, ITextEditorConfigurationUpdate, IUndoStopOptions, TextEditorRevealType } from '../common/extHost.protocol.js'; +import { IApplyEditsOptions, IEditorPropertiesChangeData, IResolvedTextEditorConfiguration, ISnippetOptions, ITextEditorConfigurationUpdate, TextEditorRevealType } from '../common/extHost.protocol.js'; import { IEditorPane } from '../../common/editor.js'; import { equals } from '../../../base/common/arrays.js'; import { CodeEditorStateFlag, EditorState } from '../../../editor/contrib/editorState/browser/editorState.js'; @@ -509,7 +509,7 @@ export class MainThreadTextEditor { return true; } - async insertSnippet(modelVersionId: number, template: string, ranges: readonly IRange[], opts: IUndoStopOptions) { + async insertSnippet(modelVersionId: number, template: string, ranges: readonly IRange[], opts: ISnippetOptions) { if (!this._codeEditor || !this._codeEditor.hasModel()) { return false; @@ -542,6 +542,7 @@ export class MainThreadTextEditor { snippetController.apply(edits, { overwriteBefore: 0, overwriteAfter: 0, undoStopBefore: opts.undoStopBefore, undoStopAfter: opts.undoStopAfter, + adjustWhitespace: opts.adjustWhitespace, clipboardText }); diff --git a/src/vs/workbench/api/browser/mainThreadEditors.ts b/src/vs/workbench/api/browser/mainThreadEditors.ts index a61ba3c780107..41af1eeab6058 100644 --- a/src/vs/workbench/api/browser/mainThreadEditors.ts +++ b/src/vs/workbench/api/browser/mainThreadEditors.ts @@ -16,7 +16,7 @@ import { CommandsRegistry } from '../../../platform/commands/common/commands.js' import { ITextEditorOptions, IResourceEditorInput, EditorActivation, EditorResolution, ITextEditorDiffInformation, isTextEditorDiffInformationEqual, ITextEditorChange } from '../../../platform/editor/common/editor.js'; import { ServicesAccessor } from '../../../platform/instantiation/common/instantiation.js'; import { MainThreadTextEditor } from './mainThreadEditor.js'; -import { ExtHostContext, ExtHostEditorsShape, IApplyEditsOptions, ITextDocumentShowOptions, ITextEditorConfigurationUpdate, ITextEditorPositionData, IUndoStopOptions, MainThreadTextEditorsShape, TextEditorRevealType } from '../common/extHost.protocol.js'; +import { ExtHostContext, ExtHostEditorsShape, IApplyEditsOptions, ISnippetOptions, ITextDocumentShowOptions, ITextEditorConfigurationUpdate, ITextEditorPositionData, MainThreadTextEditorsShape, TextEditorRevealType } from '../common/extHost.protocol.js'; import { editorGroupToColumn, columnToEditorGroup, EditorGroupColumn } from '../../services/editor/common/editorGroupColumn.js'; import { IEditorService } from '../../services/editor/common/editorService.js'; import { IEditorGroupsService } from '../../services/editor/common/editorGroupsService.js'; @@ -347,7 +347,7 @@ export class MainThreadTextEditors implements MainThreadTextEditorsShape { return Promise.resolve(editor.applyEdits(modelVersionId, edits, opts)); } - $tryInsertSnippet(id: string, modelVersionId: number, template: string, ranges: readonly IRange[], opts: IUndoStopOptions): Promise { + $tryInsertSnippet(id: string, modelVersionId: number, template: string, ranges: readonly IRange[], opts: ISnippetOptions): Promise { const editor = this._editorLocator.getEditor(id); if (!editor) { return Promise.reject(illegalArgument(`TextEditor(${id})`)); diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 9155c298fc3c6..e0dfbc376fb85 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -261,6 +261,9 @@ export interface IApplyEditsOptions extends IUndoStopOptions { setEndOfLine?: EndOfLineSequence; } +export interface ISnippetOptions extends IUndoStopOptions { + adjustWhitespace?: boolean; +} export interface ITextDocumentShowOptions { position?: EditorGroupColumn; preserveFocus?: boolean; diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkTextEdits.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkTextEdits.ts index 269d2bccfe1ff..54bea70bd8cf1 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkTextEdits.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkTextEdits.ts @@ -156,7 +156,7 @@ class EditorEditTask extends ModelEditTask { }); } } - snippetCtrl.apply(snippetEdits, { undoStopBefore: false, undoStopAfter: false }); + snippetCtrl.apply(snippetEdits, { undoStopBefore: false, undoStopAfter: false, adjustWhitespace: false }); } else { // normal edit diff --git a/src/vscode-dts/vscode.d.ts b/src/vscode-dts/vscode.d.ts index f306830ba121a..83aca191ec30d 100644 --- a/src/vscode-dts/vscode.d.ts +++ b/src/vscode-dts/vscode.d.ts @@ -1312,6 +1312,10 @@ declare module 'vscode' { * Add undo stop after making the edits. */ readonly undoStopAfter: boolean; + /** + * Adjust the indentation of the snippet. + */ + readonly adjustWhitespace?: boolean; }): Thenable; /** From 4f39e1da783d187b0a83e7d427748037210ea6c5 Mon Sep 17 00:00:00 2001 From: Roland Grunberg Date: Thu, 12 Dec 2024 16:12:47 -0500 Subject: [PATCH 02/10] Respond to review comments. - Rename adjustWhitespace to keepWhitespace - Retain default behaviour of adjusting whitespace when keepWhitespace not defined Signed-off-by: Roland Grunberg --- extensions/emmet/src/abbreviationActions.ts | 4 ++-- .../vscode-api-tests/src/singlefolder-tests/editor.test.ts | 2 +- src/vs/workbench/api/browser/mainThreadEditor.ts | 2 +- src/vs/workbench/api/browser/mainThreadEditors.ts | 4 ++-- src/vs/workbench/api/common/extHost.protocol.ts | 2 +- src/vs/workbench/api/common/extHostTextEditor.ts | 5 ++++- src/vscode-dts/vscode.d.ts | 4 ++-- 7 files changed, 13 insertions(+), 10 deletions(-) diff --git a/extensions/emmet/src/abbreviationActions.ts b/extensions/emmet/src/abbreviationActions.ts index 30ebfe56f93b8..3326722905c6b 100644 --- a/extensions/emmet/src/abbreviationActions.ts +++ b/extensions/emmet/src/abbreviationActions.ts @@ -636,7 +636,7 @@ async function expandAbbreviationInRange(editor: vscode.TextEditor, expandAbbrLi for (const expandAbbrInput of expandAbbrList) { const expandedText = expandAbbr(expandAbbrInput); if (expandedText) { - await editor.insertSnippet(new vscode.SnippetString(expandedText), expandAbbrInput.rangeToReplace, { undoStopBefore: false, undoStopAfter: false, adjustWhitespace: true }); + await editor.insertSnippet(new vscode.SnippetString(expandedText), expandAbbrInput.rangeToReplace, { undoStopBefore: false, undoStopAfter: false }); insertedSnippetsCount++; } } @@ -650,7 +650,7 @@ async function expandAbbreviationInRange(editor: vscode.TextEditor, expandAbbrLi const expandedText = expandAbbr(anyExpandAbbrInput); const allRanges = expandAbbrList.map(value => value.rangeToReplace); if (expandedText) { - return editor.insertSnippet(new vscode.SnippetString(expandedText), allRanges, { undoStopBefore: true, undoStopAfter: true, adjustWhitespace: true }); + return editor.insertSnippet(new vscode.SnippetString(expandedText), allRanges); } return false; } diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/editor.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/editor.test.ts index 8f3f0fb01ea5d..106ef702d6934 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/editor.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/editor.test.ts @@ -117,7 +117,7 @@ suite('vscode API - editors', () => { new Position(1, 3) ); - return editor.insertSnippet(snippetString, undefined, { undoStopAfter: false, undoStopBefore: false, adjustWhitespace: false }).then(inserted => { + return editor.insertSnippet(snippetString, undefined, { undoStopAfter: false, undoStopBefore: false, keepWhitespace: true }).then(inserted => { assert.ok(inserted); assert.strictEqual(doc.getText(), 'This is line 1\n This is line 2\n This is line 3'); assert.ok(doc.isDirty); diff --git a/src/vs/workbench/api/browser/mainThreadEditor.ts b/src/vs/workbench/api/browser/mainThreadEditor.ts index fa923804be1be..257e99854526a 100644 --- a/src/vs/workbench/api/browser/mainThreadEditor.ts +++ b/src/vs/workbench/api/browser/mainThreadEditor.ts @@ -542,7 +542,7 @@ export class MainThreadTextEditor { snippetController.apply(edits, { overwriteBefore: 0, overwriteAfter: 0, undoStopBefore: opts.undoStopBefore, undoStopAfter: opts.undoStopAfter, - adjustWhitespace: opts.adjustWhitespace, + adjustWhitespace: !opts.keepWhitespace, clipboardText }); diff --git a/src/vs/workbench/api/browser/mainThreadEditors.ts b/src/vs/workbench/api/browser/mainThreadEditors.ts index 41af1eeab6058..a61ba3c780107 100644 --- a/src/vs/workbench/api/browser/mainThreadEditors.ts +++ b/src/vs/workbench/api/browser/mainThreadEditors.ts @@ -16,7 +16,7 @@ import { CommandsRegistry } from '../../../platform/commands/common/commands.js' import { ITextEditorOptions, IResourceEditorInput, EditorActivation, EditorResolution, ITextEditorDiffInformation, isTextEditorDiffInformationEqual, ITextEditorChange } from '../../../platform/editor/common/editor.js'; import { ServicesAccessor } from '../../../platform/instantiation/common/instantiation.js'; import { MainThreadTextEditor } from './mainThreadEditor.js'; -import { ExtHostContext, ExtHostEditorsShape, IApplyEditsOptions, ISnippetOptions, ITextDocumentShowOptions, ITextEditorConfigurationUpdate, ITextEditorPositionData, MainThreadTextEditorsShape, TextEditorRevealType } from '../common/extHost.protocol.js'; +import { ExtHostContext, ExtHostEditorsShape, IApplyEditsOptions, ITextDocumentShowOptions, ITextEditorConfigurationUpdate, ITextEditorPositionData, IUndoStopOptions, MainThreadTextEditorsShape, TextEditorRevealType } from '../common/extHost.protocol.js'; import { editorGroupToColumn, columnToEditorGroup, EditorGroupColumn } from '../../services/editor/common/editorGroupColumn.js'; import { IEditorService } from '../../services/editor/common/editorService.js'; import { IEditorGroupsService } from '../../services/editor/common/editorGroupsService.js'; @@ -347,7 +347,7 @@ export class MainThreadTextEditors implements MainThreadTextEditorsShape { return Promise.resolve(editor.applyEdits(modelVersionId, edits, opts)); } - $tryInsertSnippet(id: string, modelVersionId: number, template: string, ranges: readonly IRange[], opts: ISnippetOptions): Promise { + $tryInsertSnippet(id: string, modelVersionId: number, template: string, ranges: readonly IRange[], opts: IUndoStopOptions): Promise { const editor = this._editorLocator.getEditor(id); if (!editor) { return Promise.reject(illegalArgument(`TextEditor(${id})`)); diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index e0dfbc376fb85..68ba8bd88d134 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -262,7 +262,7 @@ export interface IApplyEditsOptions extends IUndoStopOptions { } export interface ISnippetOptions extends IUndoStopOptions { - adjustWhitespace?: boolean; + keepWhitespace?: boolean; } export interface ITextDocumentShowOptions { position?: EditorGroupColumn; diff --git a/src/vs/workbench/api/common/extHostTextEditor.ts b/src/vs/workbench/api/common/extHostTextEditor.ts index 0803130ffbd9a..075e511da10a8 100644 --- a/src/vs/workbench/api/common/extHostTextEditor.ts +++ b/src/vs/workbench/api/common/extHostTextEditor.ts @@ -495,7 +495,7 @@ export class ExtHostTextEditor { return that._applyEdit(edit); }, // --- snippet edit - insertSnippet(snippet: SnippetString, where?: Position | readonly Position[] | Range | readonly Range[], options: { undoStopBefore: boolean; undoStopAfter: boolean } = { undoStopBefore: true, undoStopAfter: true }): Promise { + insertSnippet(snippet: SnippetString, where?: Position | readonly Position[] | Range | readonly Range[], options: { undoStopBefore: boolean; undoStopAfter: boolean; keepWhitespace?: boolean } = { undoStopBefore: true, undoStopAfter: true, keepWhitespace: true }): Promise { if (that._disposed) { return Promise.reject(new Error('TextEditor#insertSnippet not possible on closed editors')); } @@ -521,6 +521,9 @@ export class ExtHostTextEditor { } } } + if (options.keepWhitespace === undefined) { + options.keepWhitespace = false; + } return _proxy.$tryInsertSnippet(id, document.value.version, snippet.value, ranges, options); }, setDecorations(decorationType: vscode.TextEditorDecorationType, ranges: Range[] | vscode.DecorationOptions[]): void { diff --git a/src/vscode-dts/vscode.d.ts b/src/vscode-dts/vscode.d.ts index 83aca191ec30d..99fdc6be76d41 100644 --- a/src/vscode-dts/vscode.d.ts +++ b/src/vscode-dts/vscode.d.ts @@ -1313,9 +1313,9 @@ declare module 'vscode' { */ readonly undoStopAfter: boolean; /** - * Adjust the indentation of the snippet. + * Keep whitespace of the {@link SnippetString.value} as is. */ - readonly adjustWhitespace?: boolean; + readonly keepWhitespace?: boolean; }): Thenable; /** From ed13a9538d3c5f872d43bf96a5429a7439eea835 Mon Sep 17 00:00:00 2001 From: Roland Grunberg Date: Fri, 13 Dec 2024 09:54:00 -0500 Subject: [PATCH 03/10] Remove incorrect method parameter default. Signed-off-by: Roland Grunberg --- src/vs/workbench/api/common/extHostTextEditor.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/api/common/extHostTextEditor.ts b/src/vs/workbench/api/common/extHostTextEditor.ts index 075e511da10a8..86a322348a8d2 100644 --- a/src/vs/workbench/api/common/extHostTextEditor.ts +++ b/src/vs/workbench/api/common/extHostTextEditor.ts @@ -495,7 +495,7 @@ export class ExtHostTextEditor { return that._applyEdit(edit); }, // --- snippet edit - insertSnippet(snippet: SnippetString, where?: Position | readonly Position[] | Range | readonly Range[], options: { undoStopBefore: boolean; undoStopAfter: boolean; keepWhitespace?: boolean } = { undoStopBefore: true, undoStopAfter: true, keepWhitespace: true }): Promise { + insertSnippet(snippet: SnippetString, where?: Position | readonly Position[] | Range | readonly Range[], options: { undoStopBefore: boolean; undoStopAfter: boolean; keepWhitespace?: boolean } = { undoStopBefore: true, undoStopAfter: true }): Promise { if (that._disposed) { return Promise.reject(new Error('TextEditor#insertSnippet not possible on closed editors')); } From 8f4b28aedd64cf149961f620405765ae3356b42a Mon Sep 17 00:00:00 2001 From: Roland Grunberg Date: Sat, 11 Jan 2025 21:52:35 -0500 Subject: [PATCH 04/10] Introduce 'keepWhitespace' property for snippet text edit abstractions. - Restore original behaviour of bulk text edits adjusting whitespace - Add testcase Signed-off-by: Roland Grunberg --- .../src/singlefolder-tests/workspace.test.ts | 21 +++++++++++++++++++ .../browser/services/bulkEditService.ts | 2 +- src/vs/editor/common/languages.ts | 2 +- .../contrib/snippet/browser/snippetSession.ts | 5 +++-- src/vs/monaco.d.ts | 1 + .../api/common/extHostTypeConverters.ts | 3 ++- src/vs/workbench/api/common/extHostTypes.ts | 5 ++++- .../contrib/bulkEdit/browser/bulkTextEdits.ts | 9 ++++---- src/vscode-dts/vscode.d.ts | 5 +++++ 9 files changed, 43 insertions(+), 10 deletions(-) diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts index b871093df39b0..2b5f586ebe1ac 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts @@ -1226,6 +1226,27 @@ suite('vscode API - workspace', () => { assert.deepStrictEqual(edt.selections, [new vscode.Selection(0, 0, 0, 3)]); }); + test('SnippetString in WorkspaceEdit with keepWhitespace', async function (): Promise { + const file = await createRandomFile('This is line 1\n '); + + const document = await vscode.workspace.openTextDocument(file); + const edt = await vscode.window.showTextDocument(document); + + assert.ok(edt === vscode.window.activeTextEditor); + + const snippetText = new vscode.SnippetTextEdit(new vscode.Range(1, 3, 1, 3), new vscode.SnippetString('This is line 2\n This is line 3')); + snippetText.keepWhitespace = true; + const we = new vscode.WorkspaceEdit(); + we.set(document.uri, [snippetText]); + const success = await vscode.workspace.applyEdit(we); + if (edt !== vscode.window.activeTextEditor) { + return this.skip(); + } + + assert.ok(success); + assert.strictEqual(document.getText(), 'This is line 1\n This is line 2\n This is line 3'); + }); + test('Support creating binary files in a WorkspaceEdit', async function (): Promise { const fileUri = vscode.Uri.parse(`${testFs.scheme}:/${rndName()}`); diff --git a/src/vs/editor/browser/services/bulkEditService.ts b/src/vs/editor/browser/services/bulkEditService.ts index cbc88fafc2f09..fa12a2ab99e5c 100644 --- a/src/vs/editor/browser/services/bulkEditService.ts +++ b/src/vs/editor/browser/services/bulkEditService.ts @@ -55,7 +55,7 @@ export class ResourceTextEdit extends ResourceEdit implements IWorkspaceTextEdit constructor( readonly resource: URI, - readonly textEdit: TextEdit & { insertAsSnippet?: boolean }, + readonly textEdit: TextEdit & { insertAsSnippet?: boolean; keepWhitespace?: boolean }, readonly versionId: number | undefined = undefined, metadata?: WorkspaceEditMetadata, ) { diff --git a/src/vs/editor/common/languages.ts b/src/vs/editor/common/languages.ts index bef68de579deb..7daaaabf59ef3 100644 --- a/src/vs/editor/common/languages.ts +++ b/src/vs/editor/common/languages.ts @@ -1795,7 +1795,7 @@ export interface IWorkspaceFileEdit { export interface IWorkspaceTextEdit { resource: URI; - textEdit: TextEdit & { insertAsSnippet?: boolean }; + textEdit: TextEdit & { insertAsSnippet?: boolean; keepWhitespace?: boolean }; versionId: number | undefined; metadata?: WorkspaceEditMetadata; } diff --git a/src/vs/editor/contrib/snippet/browser/snippetSession.ts b/src/vs/editor/contrib/snippet/browser/snippetSession.ts index e0bacc7662e5a..30e42dd539a6b 100644 --- a/src/vs/editor/contrib/snippet/browser/snippetSession.ts +++ b/src/vs/editor/contrib/snippet/browser/snippetSession.ts @@ -375,6 +375,7 @@ const _defaultOptions: ISnippetSessionInsertOptions = { export interface ISnippetEdit { range: Range; template: string; + keepWhitespace?: boolean; } export class SnippetSession { @@ -569,7 +570,7 @@ export class SnippetSession { let offset = 0; for (let i = 0; i < snippetEdits.length; i++) { - const { range, template } = snippetEdits[i]; + const { range, template, keepWhitespace } = snippetEdits[i]; // gaps between snippet edits are appended as text nodes. this // ensures placeholder-offsets are later correct @@ -582,7 +583,7 @@ export class SnippetSession { } const newNodes = parser.parseFragment(template, snippet); - SnippetSession.adjustWhitespace(model, range.getStartPosition(), adjustWhitespace, snippet, new Set(newNodes)); + SnippetSession.adjustWhitespace(model, range.getStartPosition(), keepWhitespace !== undefined ? !keepWhitespace : adjustWhitespace, snippet, new Set(newNodes)); snippet.resolveVariables(resolver); const snippetText = snippet.toString(); diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 5db429fff414e..f7b065aaaa8d6 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -7973,6 +7973,7 @@ declare namespace monaco.languages { resource: Uri; textEdit: TextEdit & { insertAsSnippet?: boolean; + keepWhitespace?: boolean; }; versionId: number | undefined; metadata?: WorkspaceEditMetadata; diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index b7f3d7e8b5eca..7381e90d0df3f 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -657,7 +657,8 @@ export namespace WorkspaceEdit { textEdit: { range: Range.from(entry.range), text: entry.edit.value, - insertAsSnippet: true + insertAsSnippet: true, + keepWhitespace: entry.keepWhitespace }, versionId: !toCreate.has(entry.uri) ? versionInfo?.getTextDocumentVersion(entry.uri) : undefined, metadata: entry.metadata diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index abb6a3e737b2b..1eb1860d421c2 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -766,6 +766,8 @@ export class SnippetTextEdit implements vscode.SnippetTextEdit { snippet: SnippetString; + keepWhitespace?: boolean; + constructor(range: Range, snippet: SnippetString) { this.range = range; this.snippet = snippet; @@ -809,6 +811,7 @@ export interface IFileSnippetTextEdit { readonly range: vscode.Range; readonly edit: vscode.SnippetString; readonly metadata?: vscode.WorkspaceEditEntryMetadata; + readonly keepWhitespace?: boolean; } export interface IFileCellEdit { @@ -938,7 +941,7 @@ export class WorkspaceEdit implements vscode.WorkspaceEdit { this.replaceNotebookCells(uri, edit.range, edit.newCells, metadata); } } else if (SnippetTextEdit.isSnippetTextEdit(edit)) { - this._edits.push({ _type: FileEditType.Snippet, uri, range: edit.range, edit: edit.snippet, metadata }); + this._edits.push({ _type: FileEditType.Snippet, uri, range: edit.range, edit: edit.snippet, metadata, keepWhitespace: edit.keepWhitespace }); } else { this._edits.push({ _type: FileEditType.Text, uri, edit, metadata }); diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkTextEdits.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkTextEdits.ts index 54bea70bd8cf1..4e4ec817f8bde 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkTextEdits.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkTextEdits.ts @@ -25,7 +25,7 @@ import { ISnippetEdit } from '../../../../editor/contrib/snippet/browser/snippet type ValidationResult = { canApply: true } | { canApply: false; reason: URI }; -type ISingleSnippetEditOperation = ISingleEditOperation & { insertAsSnippet?: boolean }; +type ISingleSnippetEditOperation = ISingleEditOperation & { insertAsSnippet?: boolean; keepWhitespace?: boolean }; class ModelEditTask implements IDisposable { @@ -80,7 +80,7 @@ class ModelEditTask implements IDisposable { } else { range = Range.lift(textEdit.range); } - this._edits.push({ ...EditOperation.replaceMove(range, textEdit.text), insertAsSnippet: textEdit.insertAsSnippet }); + this._edits.push({ ...EditOperation.replaceMove(range, textEdit.text), insertAsSnippet: textEdit.insertAsSnippet, keepWhitespace: textEdit.keepWhitespace }); } validate(): ValidationResult { @@ -152,11 +152,12 @@ class EditorEditTask extends ModelEditTask { if (edit.range && edit.text !== null) { snippetEdits.push({ range: Range.lift(edit.range), - template: edit.insertAsSnippet ? edit.text : SnippetParser.escape(edit.text) + template: edit.insertAsSnippet ? edit.text : SnippetParser.escape(edit.text), + keepWhitespace: edit.keepWhitespace }); } } - snippetCtrl.apply(snippetEdits, { undoStopBefore: false, undoStopAfter: false, adjustWhitespace: false }); + snippetCtrl.apply(snippetEdits, { undoStopBefore: false, undoStopAfter: false }); } else { // normal edit diff --git a/src/vscode-dts/vscode.d.ts b/src/vscode-dts/vscode.d.ts index 99fdc6be76d41..ed378dce6bad8 100644 --- a/src/vscode-dts/vscode.d.ts +++ b/src/vscode-dts/vscode.d.ts @@ -3780,6 +3780,11 @@ declare module 'vscode' { */ snippet: SnippetString; + /** + * Whether the snippet edit should be applied with existing whitespace preserved. + */ + keepWhitespace?: boolean; + /** * Create a new snippet edit. * From 9add868cda75ab3d669f24c15d2f3b5b81a13afe Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 31 Jan 2025 08:30:51 -0800 Subject: [PATCH 05/10] Add very basic tokenizer for command line Fixes #239018 --- .../src/terminalSuggestMain.ts | 35 ++++--------- .../terminal-suggest/src/test/tokens.test.ts | 52 +++++++++++++++++++ extensions/terminal-suggest/src/tokens.ts | 33 ++++++++++++ 3 files changed, 94 insertions(+), 26 deletions(-) create mode 100644 extensions/terminal-suggest/src/test/tokens.test.ts create mode 100644 extensions/terminal-suggest/src/tokens.ts diff --git a/extensions/terminal-suggest/src/terminalSuggestMain.ts b/extensions/terminal-suggest/src/terminalSuggestMain.ts index 3cd5854ca74c6..db52610be6c9f 100644 --- a/extensions/terminal-suggest/src/terminalSuggestMain.ts +++ b/extensions/terminal-suggest/src/terminalSuggestMain.ts @@ -12,6 +12,7 @@ import cdSpec from './completions/cd'; import codeInsidersCompletionSpec from './completions/code-insiders'; import { osIsWindows } from './helpers/os'; import { isExecutable } from './helpers/executable'; +import { getTokenType, TokenType } from './tokens'; const enum PwshCommandType { Alias = 1 @@ -136,7 +137,8 @@ export async function activate(context: vscode.ExtensionContext) { const prefix = getPrefix(terminalContext.commandLine, terminalContext.cursorPosition); const pathSeparator = isWindows ? '\\' : '/'; - const result = await getCompletionItemsFromSpecs(availableSpecs, terminalContext, commands, prefix, terminal.shellIntegration?.cwd, token); + const tokenType = getTokenType(terminalContext, shellType); + const result = await getCompletionItemsFromSpecs(availableSpecs, terminalContext, commands, prefix, tokenType, terminal.shellIntegration?.cwd, token); if (terminal.shellIntegration?.env) { const homeDirCompletion = result.items.find(i => i.label === '~'); if (homeDirCompletion && terminal.shellIntegration.env.HOME) { @@ -324,6 +326,7 @@ export async function getCompletionItemsFromSpecs( terminalContext: { commandLine: string; cursorPosition: number }, availableCommands: ICompletionResource[], prefix: string, + tokenType: TokenType, shellIntegrationCwd?: vscode.Uri, token?: vscode.CancellationToken ): Promise<{ items: vscode.TerminalCompletionItem[]; filesRequested: boolean; foldersRequested: boolean; cwd?: vscode.Uri }> { @@ -331,7 +334,6 @@ export async function getCompletionItemsFromSpecs( let filesRequested = false; let foldersRequested = false; - const firstCommand = getFirstCommand(terminalContext.commandLine); const precedingText = terminalContext.commandLine.slice(0, terminalContext.cursorPosition + 1); for (const spec of specs) { @@ -347,14 +349,10 @@ export async function getCompletionItemsFromSpecs( continue; } - if ( - // If the prompt is empty - !terminalContext.commandLine - // or the first command matches the command - || !!firstCommand && specLabel.startsWith(firstCommand) - ) { - // push it to the completion items + // push it to the completion items + if (tokenType === TokenType.Command) { items.push(createCompletionItem(terminalContext.cursorPosition, prefix, { label: specLabel }, getDescription(spec), availableCommand.detail)); + continue; } if (!terminalContext.commandLine.startsWith(specLabel)) { @@ -385,9 +383,7 @@ export async function getCompletionItemsFromSpecs( !filesRequested && !foldersRequested; - const shouldShowCommands = !terminalContext.commandLine.substring(0, terminalContext.cursorPosition).trimStart().includes(' '); - - if (shouldShowCommands && !filesRequested && !foldersRequested) { + if (tokenType === TokenType.Command) { // Include builitin/available commands in the results const labels = new Set(items.map((i) => i.label)); for (const command of availableCommands) { @@ -536,19 +532,6 @@ function getCompletionItemsFromArgs(args: Fig.SingleOrArray | undefined return { items, filesRequested, foldersRequested }; } - - -function getFirstCommand(commandLine: string): string | undefined { - const wordsOnLine = commandLine.split(' '); - let firstCommand: string | undefined = wordsOnLine[0]; - if (wordsOnLine.length > 1) { - firstCommand = undefined; - } else if (wordsOnLine.length === 0) { - firstCommand = commandLine; - } - return firstCommand; -} - function getFriendlyResourcePath(uri: vscode.Uri, pathSeparator: string, kind?: vscode.TerminalCompletionItemKind): string { let path = uri.fsPath; // Ensure drive is capitalized on Windows @@ -564,7 +547,7 @@ function getFriendlyResourcePath(uri: vscode.Uri, pathSeparator: string, kind?: } // TODO: remove once API is finalized -export enum TerminalShellType { +export const enum TerminalShellType { Sh = 1, Bash = 2, Fish = 3, diff --git a/extensions/terminal-suggest/src/test/tokens.test.ts b/extensions/terminal-suggest/src/test/tokens.test.ts new file mode 100644 index 0000000000000..621a9b653883e --- /dev/null +++ b/extensions/terminal-suggest/src/test/tokens.test.ts @@ -0,0 +1,52 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import 'mocha'; +import { strictEqual } from 'node:assert'; +import { getTokenType, TokenType } from '../tokens'; +import { TerminalShellType } from '../terminalSuggestMain'; + +suite('Terminal Suggest', () => { + test('simple command', () => { + strictEqual(getTokenType({ commandLine: 'echo', cursorPosition: 'echo'.length }, undefined), TokenType.Command); + }); + test('simple argument', () => { + strictEqual(getTokenType({ commandLine: 'echo hello', cursorPosition: 'echo hello'.length }, undefined), TokenType.Argument); + }); + test('simple command, cursor mid text', () => { + strictEqual(getTokenType({ commandLine: 'echo hello', cursorPosition: 'echo'.length }, undefined), TokenType.Command); + }); + test('simple argument, cursor mid text', () => { + strictEqual(getTokenType({ commandLine: 'echo hello', cursorPosition: 'echo hel'.length }, undefined), TokenType.Argument); + }); + suite('reset to command', () => { + test('|', () => { + strictEqual(getTokenType({ commandLine: 'echo hello | ', cursorPosition: 'echo hello | '.length }, undefined), TokenType.Command); + }); + test(';', () => { + strictEqual(getTokenType({ commandLine: 'echo hello; ', cursorPosition: 'echo hello; '.length }, undefined), TokenType.Command); + }); + test('&&', () => { + strictEqual(getTokenType({ commandLine: 'echo hello && ', cursorPosition: 'echo hello && '.length }, undefined), TokenType.Command); + }); + test('||', () => { + strictEqual(getTokenType({ commandLine: 'echo hello || ', cursorPosition: 'echo hello || '.length }, undefined), TokenType.Command); + }); + }); + suite('pwsh', () => { + test('simple command', () => { + strictEqual(getTokenType({ commandLine: 'Write-Host', cursorPosition: 'Write-Host'.length }, TerminalShellType.PowerShell), TokenType.Command); + }); + test('simple argument', () => { + strictEqual(getTokenType({ commandLine: 'Write-Host hello', cursorPosition: 'Write-Host hello'.length }, TerminalShellType.PowerShell), TokenType.Argument); + }); + test('reset char', () => { + strictEqual(getTokenType({ commandLine: `Write-Host hello -and `, cursorPosition: `Write-Host hello -and `.length }, TerminalShellType.PowerShell), TokenType.Command); + }); + test('arguments after reset char', () => { + strictEqual(getTokenType({ commandLine: `Write-Host hello -and $true `, cursorPosition: `Write-Host hello -and $true `.length }, TerminalShellType.PowerShell), TokenType.Argument); + }); + }); +}); diff --git a/extensions/terminal-suggest/src/tokens.ts b/extensions/terminal-suggest/src/tokens.ts new file mode 100644 index 0000000000000..9712d2b0bfee5 --- /dev/null +++ b/extensions/terminal-suggest/src/tokens.ts @@ -0,0 +1,33 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { TerminalShellType } from './terminalSuggestMain'; + +export const enum TokenType { + Command, + Argument, +} + +const shellTypeResetChars: { [key: number]: string[] | undefined } = { + [TerminalShellType.Bash]: ['>', '>>', '<', '2>', '2>>', '&>', '&>>', '|', '|&', '&&', '||', '&', ';', '(', '{', '<<'], + [TerminalShellType.Zsh]: ['>', '>>', '<', '2>', '2>>', '&>', '&>>', '<>', '|', '|&', '&&', '||', '&', ';', '(', '{', '<<', '<<<', '<('], + [TerminalShellType.PowerShell]: ['>', '>>', '<', '2>', '2>>', '*>', '*>>', '|', '-and', '-or', '-not', '!', '&', '-eq', '-ne', '-gt', '-lt', '-ge', '-le', '-like', '-notlike', '-match', '-notmatch', '-contains', '-notcontains', '-in', '-notin'] +}; + +const defaultShellTypeResetChars = shellTypeResetChars[TerminalShellType.Bash]!; + +export function getTokenType(ctx: { commandLine: string; cursorPosition: number }, shellType: TerminalShellType | undefined): TokenType { + const spaceIndex = ctx.commandLine.substring(0, ctx.cursorPosition).lastIndexOf(' '); + if (spaceIndex === -1) { + return TokenType.Command; + } + const tokenIndex = spaceIndex === -1 ? 0 : spaceIndex + 1; + const previousTokens = ctx.commandLine.substring(0, tokenIndex).trim(); + const commandResetChars = shellType === undefined ? defaultShellTypeResetChars : shellTypeResetChars[shellType] ?? defaultShellTypeResetChars; + if (commandResetChars.some(e => previousTokens.endsWith(e))) { + return TokenType.Command; + } + return TokenType.Argument; +} From 63c8c1557178f7b3d0fd923039cacf35e9d38e1f Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 31 Jan 2025 09:04:43 -0800 Subject: [PATCH 06/10] Pass token type into getCompletionItemsFromSpecs tests --- .../terminal-suggest/src/test/terminalSuggestMain.test.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/extensions/terminal-suggest/src/test/terminalSuggestMain.test.ts b/extensions/terminal-suggest/src/test/terminalSuggestMain.test.ts index d72996b8f702b..030e9d0db9953 100644 --- a/extensions/terminal-suggest/src/test/terminalSuggestMain.test.ts +++ b/extensions/terminal-suggest/src/test/terminalSuggestMain.test.ts @@ -12,6 +12,7 @@ import codeCompletionSpec from '../completions/code'; import codeInsidersCompletionSpec from '../completions/code-insiders'; import type { Uri } from 'vscode'; import { basename } from 'path'; +import { getTokenType } from '../tokens'; const fixtureDir = vscode.Uri.joinPath(vscode.Uri.file(__dirname), '../../testWorkspace'); const testCwdParent = vscode.Uri.joinPath(fixtureDir, 'parent'); @@ -150,7 +151,8 @@ suite('Terminal Suggest', () => { const prefix = commandLine.slice(0, cursorPosition).split(' ').at(-1) || ''; const filesRequested = testSpec.expectedResourceRequests?.type === 'files' || testSpec.expectedResourceRequests?.type === 'both'; const foldersRequested = testSpec.expectedResourceRequests?.type === 'folders' || testSpec.expectedResourceRequests?.type === 'both'; - const result = await getCompletionItemsFromSpecs(completionSpecs, { commandLine, cursorPosition }, availableCommands.map(c => { return { label: c }; }), prefix, testCwd); + const terminalContext = { commandLine, cursorPosition }; + const result = await getCompletionItemsFromSpecs(completionSpecs, terminalContext, availableCommands.map(c => { return { label: c }; }), prefix, getTokenType(terminalContext, undefined), testCwd); deepStrictEqual(result.items.map(i => i.label).sort(), (testSpec.expectedCompletions ?? []).sort()); strictEqual(result.filesRequested, filesRequested); strictEqual(result.foldersRequested, foldersRequested); From a3b6a78384d8f022cc2a81295b0959678ff2b201 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 31 Jan 2025 21:01:30 +0100 Subject: [PATCH 07/10] Opening a file without read permissions does not yield an error (fix #239298) (#239366) --- src/vs/platform/windows/electron-main/windowsMainService.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/vs/platform/windows/electron-main/windowsMainService.ts b/src/vs/platform/windows/electron-main/windowsMainService.ts index b47f1a17cb4f8..d569ef6547799 100644 --- a/src/vs/platform/windows/electron-main/windowsMainService.ts +++ b/src/vs/platform/windows/electron-main/windowsMainService.ts @@ -1164,13 +1164,15 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic this.workspacesHistoryMainService.removeRecentlyOpened([fileUri]); // assume this is a file that does not yet exist - if (options.ignoreFileNotFound) { + if (options.ignoreFileNotFound && error.code === 'ENOENT') { return { fileUri, type: FileType.File, exists: false }; } + + this.logService.error(`Invalid path provided: ${path}, ${error.message}`); } return undefined; From 3e048f17b0c29e9ee72aceae94a6c92c72d36609 Mon Sep 17 00:00:00 2001 From: Aaron Munger Date: Fri, 31 Jan 2025 12:44:55 -0800 Subject: [PATCH 08/10] Provide error message when it looks like node arch does not match the system (#239364) --- build/npm/preinstall.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/build/npm/preinstall.js b/build/npm/preinstall.js index 31821ee2393d1..41a17b016767f 100644 --- a/build/npm/preinstall.js +++ b/build/npm/preinstall.js @@ -23,6 +23,7 @@ if (process.env['npm_execpath'].includes('yarn')) { const path = require('path'); const fs = require('fs'); const cp = require('child_process'); +const os = require('os'); if (process.platform === 'win32') { if (!hasSupportedVisualStudioVersion()) { @@ -32,6 +33,11 @@ if (process.platform === 'win32') { installHeaders(); } +if (process.arch !== os.arch()) { + console.error(`\x1b[1;31m*** ARCHITECTURE MISMATCH: The node.js process is ${process.arch}, but your OS architecture is ${os.arch()}. ***\x1b[0;0m`); + console.error(`\x1b[1;31m*** This can greatly increase the build time of vs code. ***\x1b[0;0m`); +} + function hasSupportedVisualStudioVersion() { const fs = require('fs'); const path = require('path'); From 0baef062361c764793fe714ba850b4ebfffd4d43 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Fri, 31 Jan 2025 16:03:21 -0600 Subject: [PATCH 09/10] ensure space after command before providing options (#239378) fix #239249 --- extensions/terminal-suggest/src/terminalSuggestMain.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/terminal-suggest/src/terminalSuggestMain.ts b/extensions/terminal-suggest/src/terminalSuggestMain.ts index db52610be6c9f..69d474247fed7 100644 --- a/extensions/terminal-suggest/src/terminalSuggestMain.ts +++ b/extensions/terminal-suggest/src/terminalSuggestMain.ts @@ -355,7 +355,7 @@ export async function getCompletionItemsFromSpecs( continue; } - if (!terminalContext.commandLine.startsWith(specLabel)) { + if (!terminalContext.commandLine.startsWith(`${specLabel} `)) { // the spec label is not the first word in the command line, so do not provide options or args continue; } From 2aaf62826970e2cef80e9e90e61d8f5a1a89b235 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Fri, 31 Jan 2025 16:36:47 -0600 Subject: [PATCH 10/10] fix missing/incorrect info in editor and edits a11y help dialogs (#239377) fix #239325 --- src/vs/editor/common/standaloneStrings.ts | 2 +- .../contrib/chat/browser/actions/chatAccessibilityHelp.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/common/standaloneStrings.ts b/src/vs/editor/common/standaloneStrings.ts index f2edfd33f60a6..c181573190baf 100644 --- a/src/vs/editor/common/standaloneStrings.ts +++ b/src/vs/editor/common/standaloneStrings.ts @@ -36,7 +36,7 @@ export namespace AccessibilityHelpNLS { export const debugExecuteSelection = nls.localize('debugConsole.executeSelection', "The Debug: Execute Selection command{0} will execute the selected text in the debug console.", ''); export const chatEditorModification = nls.localize('chatEditorModification', "The editor contains pending modifications that have been made by chat."); export const chatEditorRequestInProgress = nls.localize('chatEditorRequestInProgress', "The editor is currently waiting for modifications to be made by chat."); - export const chatEditActions = nls.localize('chatEditing.navigation', 'Navigate between edits in the editor with navigate previous{0} and next{1} and accept{3} and reject the current change{4}.', '', '', '', ''); + export const chatEditActions = nls.localize('chatEditing.navigation', 'Navigate between edits in the editor with navigate previous{0} and next{1} and accept{2}, reject{3} or view the diff{4} for the current change.', '', '', '', '', ''); } export namespace InspectTokensNLS { diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts b/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts index 5ed43c1ba4334..bc485108df656 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts @@ -79,7 +79,7 @@ export function getAccessibilityHelpText(type: 'panelChat' | 'inlineChat' | 'qui content.push(localize('chatEditing.expectation', 'When a request is made, a progress indicator will play while the edits are being applied.')); content.push(localize('chatEditing.review', 'Once the edits are applied, focus the editor(s) to review, accept, and discard changes.')); content.push(localize('chatEditing.sections', 'Navigate between edits in the editor with navigate previous{0} and next{1}', '', '')); - content.push(localize('chatEditing.acceptHunk', 'In the editor, Accept{0} and Reject the current Change{1}.', '', '')); + content.push(localize('chatEditing.acceptHunk', 'In the editor, Accept{0}, Reject{1}, or Toggle the Diff{2} for the current Change.', '', '', '')); content.push(localize('chatEditing.helpfulCommands', 'When in the edits view, some helpful commands include:')); content.push(localize('workbench.action.chat.undoEdits', '- Undo Edits{0}.', '')); content.push(localize('workbench.action.chat.editing.attachFiles', '- Attach Files{0}.', ''));