mirror of
https://github.com/Microsoft/vscode
synced 2024-09-18 01:58:27 +00:00
Merge pull request #151841 from microsoft/joh/cute-bobcat
This commit is contained in:
commit
3d471d2ca9
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -49,7 +49,7 @@ export class OneSnippet {
|
|||
this._placeholderGroupsIdx = -1;
|
||||
}
|
||||
|
||||
public initialize(textChange: TextChange): void {
|
||||
initialize(textChange: TextChange): void {
|
||||
this._offset = textChange.newPosition;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -549,6 +549,7 @@ export class TextEdit {
|
|||
|
||||
protected _range: Range;
|
||||
protected _newText: string | null;
|
||||
newText2?: string | SnippetString;
|
||||
protected _newEol?: EndOfLine;
|
||||
|
||||
get range(): Range {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,11 +125,20 @@ 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));
|
||||
|
||||
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()) {
|
||||
this._editor.getModel().pushEOL(this._newEol);
|
||||
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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',
|
||||
|
|
16
src/vscode-dts/vscode.proposed.snippetWorkspaceEdit.d.ts
vendored
Normal file
16
src/vscode-dts/vscode.proposed.snippetWorkspaceEdit.d.ts
vendored
Normal 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;
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue