Merge pull request #151841 from microsoft/joh/cute-bobcat

This commit is contained in:
Johannes Rieken 2022-06-15 08:46:12 +02:00 committed by GitHub
commit 3d471d2ca9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 105 additions and 20 deletions

View file

@ -45,9 +45,9 @@ export class ResourceEdit {
export class ResourceTextEdit extends ResourceEdit {
constructor(
readonly resource: URI,
readonly textEdit: TextEdit,
readonly textEdit: TextEdit & { insertAsSnippet?: boolean },
readonly versionId?: number,
metadata?: WorkspaceEditMetadata
metadata?: WorkspaceEditMetadata,
) {
super(metadata);
}

View file

@ -16,7 +16,7 @@ import { CompletionItem, CompletionItemKind, CompletionItemProvider } from 'vs/e
import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry';
import { ITextModel } from 'vs/editor/common/model';
import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures';
import { Choice } from 'vs/editor/contrib/snippet/browser/snippetParser';
import { Choice, SnippetParser } from 'vs/editor/contrib/snippet/browser/snippetParser';
import { showSimpleSuggestions } from 'vs/editor/contrib/suggest/browser/suggest';
import { OvertypingCapturer } from 'vs/editor/contrib/suggest/browser/suggestOvertypingCapturer';
import { localize } from 'vs/nls';
@ -346,3 +346,46 @@ export function performSnippetEdit(editor: ICodeEditor, snippet: string, selecti
controller.insert(snippet);
return controller.isInSnippet();
}
export type ISnippetEdit = {
range: Range;
snippet: string;
};
// ---
export function performSnippetEdits(editor: ICodeEditor, edits: ISnippetEdit[]) {
if (!editor.hasModel()) {
return false;
}
if (edits.length === 0) {
return false;
}
const model = editor.getModel();
let newText = '';
let last: ISnippetEdit | undefined;
edits.sort((a, b) => Range.compareRangesUsingStarts(a.range, b.range));
for (const item of edits) {
if (last) {
const between = Range.fromPositions(last.range.getEndPosition(), item.range.getStartPosition());
const text = model.getValueInRange(between);
newText += SnippetParser.escape(text);
}
newText += item.snippet;
last = item;
}
const controller = SnippetController2.get(editor);
if (!controller) {
return false;
}
model.pushStackElement();
const range = Range.plusRange(edits[0].range, edits[edits.length - 1].range);
editor.setSelection(range);
controller.insert(newText, { undoStopBefore: false });
return controller.isInSnippet();
}

View file

@ -49,7 +49,7 @@ export class OneSnippet {
this._placeholderGroupsIdx = -1;
}
public initialize(textChange: TextChange): void {
initialize(textChange: TextChange): void {
this._offset = textChange.newPosition;
}

View file

@ -856,7 +856,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
return extHostWorkspace.saveAll(includeUntitled);
},
applyEdit(edit: vscode.WorkspaceEdit): Thenable<boolean> {
return extHostBulkEdits.applyWorkspaceEdit(edit);
return extHostBulkEdits.applyWorkspaceEdit(edit, extension);
},
createFileSystemWatcher: (pattern, ignoreCreate, ignoreChange, ignoreDelete): vscode.FileSystemWatcher => {
return extHostFileSystemEvent.createFileSystemWatcher(extHostWorkspace, extension, pattern, ignoreCreate, ignoreChange, ignoreDelete);

View file

@ -1602,7 +1602,7 @@ export interface IWorkspaceFileEditDto {
export interface IWorkspaceTextEditDto {
_type: WorkspaceEditType.Text;
resource: UriComponents;
edit: languages.TextEdit;
edit: languages.TextEdit & { insertAsSnippet?: boolean };
modelVersionId?: number;
metadata?: IWorkspaceEditEntryMetadataDto;
}

View file

@ -3,10 +3,12 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { MainContext, MainThreadBulkEditsShape } from 'vs/workbench/api/common/extHost.protocol';
import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors';
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
import { WorkspaceEdit } from 'vs/workbench/api/common/extHostTypeConverters';
import { isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions';
import type * as vscode from 'vscode';
export class ExtHostBulkEdits {
@ -26,8 +28,9 @@ export class ExtHostBulkEdits {
};
}
applyWorkspaceEdit(edit: vscode.WorkspaceEdit): Promise<boolean> {
const dto = WorkspaceEdit.from(edit, this._versionInformationProvider);
applyWorkspaceEdit(edit: vscode.WorkspaceEdit, extension: IExtensionDescription): Promise<boolean> {
const allowSnippetTextEdit = isProposedApiEnabled(extension, 'snippetWorkspaceEdit');
const dto = WorkspaceEdit.from(edit, this._versionInformationProvider, allowSnippetTextEdit);
return this._proxy.$tryApplyWorkspaceEdit(dto);
}
}

View file

@ -569,7 +569,7 @@ export namespace WorkspaceEdit {
getNotebookDocumentVersion(uri: URI): number | undefined;
}
export function from(value: vscode.WorkspaceEdit, versionInfo?: IVersionInformationProvider): extHostProtocol.IWorkspaceEditDto {
export function from(value: vscode.WorkspaceEdit, versionInfo?: IVersionInformationProvider, allowSnippetTextEdit?: boolean): extHostProtocol.IWorkspaceEditDto {
const result: extHostProtocol.IWorkspaceEditDto = {
edits: []
};
@ -598,14 +598,21 @@ export namespace WorkspaceEdit {
});
} else if (entry._type === types.FileEditType.Text) {
// text edits
result.edits.push(<extHostProtocol.IWorkspaceTextEditDto>{
const dto = <extHostProtocol.IWorkspaceTextEditDto>{
_type: extHostProtocol.WorkspaceEditType.Text,
resource: entry.uri,
edit: TextEdit.from(entry.edit),
modelVersionId: !toCreate.has(entry.uri) ? versionInfo?.getTextDocumentVersion(entry.uri) : undefined,
metadata: entry.metadata
});
};
if (allowSnippetTextEdit && entry.edit.newText2 instanceof types.SnippetString) {
dto.edit.insertAsSnippet = true;
dto.edit.text = entry.edit.newText2.value;
}
result.edits.push(dto);
} else if (entry._type === types.FileEditType.Cell) {
result.edits.push(<extHostProtocol.IWorkspaceCellEditDto>{
_type: extHostProtocol.WorkspaceEditType.Cell,

View file

@ -549,6 +549,7 @@ export class TextEdit {
protected _range: Range;
protected _newText: string | null;
newText2?: string | SnippetString;
protected _newEol?: EndOfLine;
get range(): Range {

View file

@ -12,6 +12,7 @@ import { SingleProxyRPCProtocol, TestRPCProtocol } from 'vs/workbench/api/test/c
import { NullLogService } from 'vs/platform/log/common/log';
import { assertType } from 'vs/base/common/types';
import { ExtHostBulkEdits } from 'vs/workbench/api/common/extHostBulkEdits';
import { nullExtensionDescription } from 'vs/workbench/services/extensions/common/extensions';
suite('ExtHostBulkEdits.applyWorkspaceEdit', () => {
@ -46,7 +47,7 @@ suite('ExtHostBulkEdits.applyWorkspaceEdit', () => {
test('uses version id if document available', async () => {
const edit = new extHostTypes.WorkspaceEdit();
edit.replace(resource, new extHostTypes.Range(0, 0, 0, 0), 'hello');
await bulkEdits.applyWorkspaceEdit(edit);
await bulkEdits.applyWorkspaceEdit(edit, nullExtensionDescription);
assert.strictEqual(workspaceResourceEdits.edits.length, 1);
const [first] = workspaceResourceEdits.edits;
assertType(first._type === WorkspaceEditType.Text);
@ -56,7 +57,7 @@ suite('ExtHostBulkEdits.applyWorkspaceEdit', () => {
test('does not use version id if document is not available', async () => {
const edit = new extHostTypes.WorkspaceEdit();
edit.replace(URI.parse('foo:bar2'), new extHostTypes.Range(0, 0, 0, 0), 'hello');
await bulkEdits.applyWorkspaceEdit(edit);
await bulkEdits.applyWorkspaceEdit(edit, nullExtensionDescription);
assert.strictEqual(workspaceResourceEdits.edits.length, 1);
const [first] = workspaceResourceEdits.edits;
assertType(first._type === WorkspaceEditType.Text);

View file

@ -19,6 +19,8 @@ import { ResourceMap } from 'vs/base/common/map';
import { IModelService } from 'vs/editor/common/services/model';
import { ResourceTextEdit } from 'vs/editor/browser/services/bulkEditService';
import { CancellationToken } from 'vs/base/common/cancellation';
import { SnippetParser } from 'vs/editor/contrib/snippet/browser/snippetParser';
import { performSnippetEdits } from 'vs/editor/contrib/snippet/browser/snippetController2';
type ValidationResult = { canApply: true } | { canApply: false; reason: URI };
@ -27,7 +29,7 @@ class ModelEditTask implements IDisposable {
readonly model: ITextModel;
private _expectedModelVersionId: number | undefined;
protected _edits: ISingleEditOperation[];
protected _edits: (ISingleEditOperation & { insertAsSnippet?: boolean })[];
protected _newEol: EndOfLineSequence | undefined;
constructor(private readonly _modelReference: IReference<IResolvedTextEditorModel>) {
@ -75,7 +77,7 @@ class ModelEditTask implements IDisposable {
} else {
range = Range.lift(textEdit.range);
}
this._edits.push(EditOperation.replaceMove(range, textEdit.text));
this._edits.push({ ...EditOperation.replaceMove(range, textEdit.text), insertAsSnippet: textEdit.insertAsSnippet });
}
validate(): ValidationResult {
@ -91,7 +93,9 @@ class ModelEditTask implements IDisposable {
apply(): void {
if (this._edits.length > 0) {
this._edits = this._edits.sort((a, b) => Range.compareRangesUsingStarts(a.range, b.range));
this._edits = this._edits
.sort((a, b) => Range.compareRangesUsingStarts(a.range, b.range))
.map(edit => ({ ...edit, text: edit.text && SnippetParser.escape(edit.text) }));
this.model.pushEditOperations(null, this._edits, () => null);
}
if (this._newEol !== undefined) {
@ -121,10 +125,19 @@ class EditorEditTask extends ModelEditTask {
super.apply();
return;
}
if (this._edits.length > 0) {
this._edits = this._edits.sort((a, b) => Range.compareRangesUsingStarts(a.range, b.range));
this._editor.executeEdits('', this._edits);
const insertAsSnippet = this._edits.every(edit => edit.insertAsSnippet);
if (insertAsSnippet) {
// todo@jrieken what ABOUT EOL?
performSnippetEdits(this._editor, this._edits.map(edit => ({ range: Range.lift(edit.range!), snippet: edit.text! })));
} else {
this._edits = this._edits
.sort((a, b) => Range.compareRangesUsingStarts(a.range, b.range))
.map(edit => ({ ...edit, text: edit.text && SnippetParser.escape(edit.text) }));
this._editor.executeEdits('', this._edits);
}
}
if (this._newEol !== undefined) {
if (this._editor.hasModel()) {
@ -193,7 +206,7 @@ export class BulkTextEdits {
let makeMinimal = false;
if (this._editor?.getModel()?.uri.toString() === ref.object.textEditorModel.uri.toString()) {
task = new EditorEditTask(ref, this._editor);
makeMinimal = true;
makeMinimal = true && false; // todo@jrieken HACK
} else {
task = new ModelEditTask(ref);
}

View file

@ -50,6 +50,7 @@ export const allApiProposals = Object.freeze({
scmInput: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.scmInput.d.ts',
scmSelectedProvider: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.scmSelectedProvider.d.ts',
scmValidation: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.scmValidation.d.ts',
snippetWorkspaceEdit: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.snippetWorkspaceEdit.d.ts',
taskPresentationGroup: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.taskPresentationGroup.d.ts',
telemetry: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.telemetry.d.ts',
terminalDataWriteEvent: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.terminalDataWriteEvent.d.ts',

View file

@ -0,0 +1,16 @@
/*---------------------------------------------------------------------------------------------
* 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/145374
export interface TextEdit {
// will be merged with newText
// will NOT be supported everywhere, only: `workspace.applyEdit`
newText2?: string | SnippetString;
}
}