Update text editor drop proposal (#151552)

This updates the text editor drop proposal (#142990). This change introduces `DocumentDropEdit` which removes the need for `SnippetTextEdit`. This interface may also be extended in the future with additional metadata
This commit is contained in:
Matt Bierner 2022-06-08 14:44:28 -07:00 committed by GitHub
parent 83b94a5c03
commit d7c90c2b2b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 79 additions and 78 deletions

View file

@ -25,16 +25,15 @@ const imageFileExtensions = new Set<string>([
export function registerDropIntoEditor(selector: vscode.DocumentSelector) {
return vscode.languages.registerDocumentOnDropEditProvider(selector, new class implements vscode.DocumentOnDropEditProvider {
async provideDocumentOnDropEdits(document: vscode.TextDocument, position: vscode.Position, dataTransfer: vscode.DataTransfer, token: vscode.CancellationToken): Promise<vscode.SnippetTextEdit | undefined> {
async provideDocumentOnDropEdits(document: vscode.TextDocument, _position: vscode.Position, dataTransfer: vscode.DataTransfer, token: vscode.CancellationToken): Promise<vscode.DocumentDropEdit | undefined> {
const enabled = vscode.workspace.getConfiguration('markdown', document).get('editor.drop.enabled', true);
if (!enabled) {
return undefined;
}
const replacementRange = new vscode.Range(position, position);
const snippet = await tryGetUriListSnippet(document, dataTransfer, token);
if (snippet) {
return new vscode.SnippetTextEdit(replacementRange, snippet);
return { insertText: snippet };
}
return undefined;
}

View file

@ -725,7 +725,7 @@ export interface CodeActionProvider {
* @internal
*/
export interface DocumentPasteEdit {
insertSnippet: string;
insertText: string | { snippet: string };
additionalEdit?: WorkspaceEdit;
}
@ -1134,11 +1134,6 @@ export interface DocumentSymbolProvider {
export type TextEdit = { range: IRange; text: string; eol?: model.EndOfLineSequence };
export interface SnippetTextEdit {
range: IRange;
snippet: string;
}
/**
* Interface used to format a model
*/
@ -1811,10 +1806,17 @@ export enum ExternalUriOpenerPriority {
Preferred = 3,
}
/**
* @internal
*/
export interface DocumentOnDropEdit {
insertText: string | { snippet: string };
additionalEdit?: WorkspaceEdit;
}
/**
* @internal
*/
export interface DocumentOnDropEditProvider {
provideDocumentOnDropEdits(model: model.ITextModel, position: IPosition, dataTransfer: VSDataTransfer, token: CancellationToken): ProviderResult<SnippetTextEdit>;
provideDocumentOnDropEdits(model: model.ITextModel, position: IPosition, dataTransfer: VSDataTransfer, token: CancellationToken): ProviderResult<DocumentOnDropEdit>;
}

View file

@ -21,6 +21,7 @@ import { ITextModel } from 'vs/editor/common/model';
import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures';
import { CodeEditorStateFlag, EditorStateCancellationTokenSource } from 'vs/editor/contrib/editorState/browser/editorState';
import { performSnippetEdit } from 'vs/editor/contrib/snippet/browser/snippetController2';
import { SnippetParser } from 'vs/editor/contrib/snippet/browser/snippetParser';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
@ -34,7 +35,7 @@ const defaultPasteEditProvider = new class implements DocumentPasteEditProvider
if (textDataTransfer) {
const text = await textDataTransfer.asString();
return {
insertSnippet: text
insertText: text
};
}
@ -177,7 +178,7 @@ export class CopyPasteController extends Disposable implements IEditorContributi
}
if (edit) {
performSnippetEdit(editor, edit.insertSnippet, selections);
performSnippetEdit(editor, typeof edit.insertText === 'string' ? SnippetParser.escape(edit.insertText) : edit.insertText.snippet, selections);
if (edit.additionalEdit) {
await this._bulkEditService.apply(ResourceEdit.convert(edit.additionalEdit), { editor });

View file

@ -13,14 +13,17 @@ import { URI } from 'vs/base/common/uri';
import { toVSDataTransfer } from 'vs/editor/browser/dnd';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { registerEditorContribution } from 'vs/editor/browser/editorExtensions';
import { IBulkEditService, ResourceEdit } from 'vs/editor/browser/services/bulkEditService';
import { IPosition } from 'vs/editor/common/core/position';
import { Range } from 'vs/editor/common/core/range';
import { Selection, SelectionDirection } from 'vs/editor/common/core/selection';
import { IEditorContribution } from 'vs/editor/common/editorCommon';
import { DocumentOnDropEditProvider, SnippetTextEdit } from 'vs/editor/common/languages';
import { DocumentOnDropEdit, DocumentOnDropEditProvider } from 'vs/editor/common/languages';
import { ITextModel } from 'vs/editor/common/model';
import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures';
import { CodeEditorStateFlag, EditorStateCancellationTokenSource } from 'vs/editor/contrib/editorState/browser/editorState';
import { performSnippetEdit } from 'vs/editor/contrib/snippet/browser/snippetController2';
import { SnippetParser } from 'vs/editor/contrib/snippet/browser/snippetParser';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { extractEditorsDropData } from 'vs/platform/dnd/browser/dnd';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
@ -37,6 +40,7 @@ export class DropIntoEditorController extends Disposable implements IEditorContr
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@ILanguageFeaturesService private readonly _languageFeaturesService: ILanguageFeaturesService,
@IConfigurationService private readonly _configurationService: IConfigurationService,
@IBulkEditService private readonly _bulkEditService: IBulkEditService,
) {
super();
@ -87,7 +91,12 @@ export class DropIntoEditorController extends Disposable implements IEditorContr
}
if (edit) {
performSnippetEdit(editor, edit);
const range = new Range(position.lineNumber, position.column, position.lineNumber, position.column);
performSnippetEdit(editor, typeof edit.insertText === 'string' ? SnippetParser.escape(edit.insertText) : edit.insertText.snippet, [Selection.fromRange(range, SelectionDirection.LTR)]);
if (edit.additionalEdit) {
await this._bulkEditService.apply(ResourceEdit.convert(edit.additionalEdit), { editor });
}
return;
}
}
@ -121,24 +130,26 @@ class DefaultOnDropProvider implements DocumentOnDropEditProvider {
@IWorkspaceContextService private readonly _workspaceContextService: IWorkspaceContextService,
) { }
async provideDocumentOnDropEdits(model: ITextModel, position: IPosition, dataTransfer: VSDataTransfer, _token: CancellationToken): Promise<SnippetTextEdit | undefined> {
const range = new Range(position.lineNumber, position.column, position.lineNumber, position.column);
async provideDocumentOnDropEdits(_model: ITextModel, _position: IPosition, dataTransfer: VSDataTransfer, _token: CancellationToken): Promise<DocumentOnDropEdit | undefined> {
const urlListEntry = dataTransfer.get('text/uri-list');
if (urlListEntry) {
const urlList = await urlListEntry.asString();
return this.doUriListDrop(range, urlList);
const snippet = this.getUriListInsertText(urlList);
if (snippet) {
return { insertText: snippet };
}
}
const textEntry = dataTransfer.get('text') ?? dataTransfer.get(Mimes.text);
if (textEntry) {
const text = await textEntry.asString();
return { range, snippet: text };
return { insertText: text };
}
return undefined;
}
private doUriListDrop(range: Range, urlList: string): SnippetTextEdit | undefined {
private getUriListInsertText(urlList: string): string | undefined {
const uris: URI[] = [];
for (const resource of urlList.split('\n')) {
try {
@ -152,7 +163,7 @@ class DefaultOnDropProvider implements DocumentOnDropEditProvider {
return;
}
const snippet = uris
return uris
.map(uri => {
const root = this._workspaceContextService.getWorkspaceFolder(uri);
if (root) {
@ -164,8 +175,6 @@ class DefaultOnDropProvider implements DocumentOnDropEditProvider {
return uri.fsPath;
})
.join(' ');
return { range, snippet };
}
}

View file

@ -12,7 +12,7 @@ import { Range } from 'vs/editor/common/core/range';
import { ISelection } from 'vs/editor/common/core/selection';
import { IEditorContribution } from 'vs/editor/common/editorCommon';
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
import { CompletionItem, CompletionItemKind, CompletionItemProvider, SnippetTextEdit } from 'vs/editor/common/languages';
import { CompletionItem, CompletionItemKind, CompletionItemProvider } from 'vs/editor/common/languages';
import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry';
import { ITextModel } from 'vs/editor/common/model';
import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures';
@ -336,20 +336,13 @@ registerEditorCommand(new CommandCtor({
// ---
export function performSnippetEdit(editor: ICodeEditor, snippet: string, selections: ISelection[]): boolean;
export function performSnippetEdit(editor: ICodeEditor, edit: SnippetTextEdit): boolean;
export function performSnippetEdit(editor: ICodeEditor, editOrSnippet: string | SnippetTextEdit, selections?: ISelection[]): boolean {
export function performSnippetEdit(editor: ICodeEditor, snippet: string, selections: ISelection[]): boolean {
const controller = SnippetController2.get(editor);
if (!controller) {
return false;
}
editor.focus();
if (typeof editOrSnippet === 'string') {
editor.setSelections(selections ?? []);
controller.insert(editOrSnippet);
} else {
editor.setSelection(editOrSnippet.range);
controller.insert(editOrSnippet.snippet);
}
editor.setSelections(selections ?? []);
controller.insert(snippet);
return controller.isInSnippet();
}

5
src/vs/monaco.d.ts vendored
View file

@ -6758,11 +6758,6 @@ declare namespace monaco.languages {
eol?: editor.EndOfLineSequence;
};
export interface SnippetTextEdit {
range: IRange;
snippet: string;
}
/**
* Interface used to format a model
*/

View file

@ -400,7 +400,7 @@ export class MainThreadLanguageFeatures extends Disposable implements MainThread
}
return {
insertSnippet: result.insertSnippet,
insertText: result.insertText,
additionalEdit: result.additionalEdit ? reviveWorkspaceEditDto(result.additionalEdit) : undefined,
};
}
@ -935,11 +935,18 @@ class MainThreadDocumentOnDropEditProvider implements languages.DocumentOnDropEd
private readonly _proxy: ExtHostLanguageFeaturesShape,
) { }
async provideDocumentOnDropEdits(model: ITextModel, position: IPosition, dataTransfer: VSDataTransfer, token: CancellationToken): Promise<languages.SnippetTextEdit | null | undefined> {
async provideDocumentOnDropEdits(model: ITextModel, position: IPosition, dataTransfer: VSDataTransfer, token: CancellationToken): Promise<languages.DocumentOnDropEdit | null | undefined> {
const request = this.dataTransfers.add(dataTransfer);
try {
const dataTransferDto = await typeConvert.DataTransfer.toDataTransferDTO(dataTransfer);
return await this._proxy.$provideDocumentOnDropEdits(this.handle, request.id, model.uri, position, dataTransferDto, token);
const edit = await this._proxy.$provideDocumentOnDropEdits(this.handle, request.id, model.uri, position, dataTransferDto, token);
if (!edit) {
return undefined;
}
return {
insertText: edit.insertText,
additionalEdit: reviveWorkspaceEditDto(edit.additionalEdit),
};
} finally {
request.dispose();
}

View file

@ -1267,7 +1267,6 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
SignatureHelpTriggerKind: extHostTypes.SignatureHelpTriggerKind,
SignatureInformation: extHostTypes.SignatureInformation,
SnippetString: extHostTypes.SnippetString,
SnippetTextEdit: extHostTypes.SnippetTextEdit,
SourceBreakpoint: extHostTypes.SourceBreakpoint,
StandardTokenType: extHostTypes.StandardTokenType,
StatusBarAlignment: extHostTypes.StatusBarAlignment,

View file

@ -1714,7 +1714,12 @@ export interface IInlineValueContextDto {
export type ITypeHierarchyItemDto = Dto<TypeHierarchyItem>;
export interface IPasteEditDto {
insertSnippet: string;
insertText: string | { snippet: string };
additionalEdit?: IWorkspaceEditDto;
}
export interface IDocumentOnDropEditDto {
insertText: string | { snippet: string };
additionalEdit?: IWorkspaceEditDto;
}
@ -1776,7 +1781,7 @@ export interface ExtHostLanguageFeaturesShape {
$provideTypeHierarchySupertypes(handle: number, sessionId: string, itemId: string, token: CancellationToken): Promise<ITypeHierarchyItemDto[] | undefined>;
$provideTypeHierarchySubtypes(handle: number, sessionId: string, itemId: string, token: CancellationToken): Promise<ITypeHierarchyItemDto[] | undefined>;
$releaseTypeHierarchy(handle: number, sessionId: string): void;
$provideDocumentOnDropEdits(handle: number, requestId: number, resource: UriComponents, position: IPosition, dataTransferDto: DataTransferDTO, token: CancellationToken): Promise<Dto<languages.SnippetTextEdit> | undefined>;
$provideDocumentOnDropEdits(handle: number, requestId: number, resource: UriComponents, position: IPosition, dataTransferDto: DataTransferDTO, token: CancellationToken): Promise<IDocumentOnDropEditDto | undefined>;
}
export interface ExtHostQuickOpenShape {

View file

@ -34,7 +34,6 @@ import { StopWatch } from 'vs/base/common/stopwatch';
import { isCancellationError, NotImplementedError } from 'vs/base/common/errors';
import { raceCancellationError } from 'vs/base/common/async';
import { isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions';
import { Dto } from 'vs/workbench/services/extensions/common/proxyIdentifier';
// --- adapter
@ -524,7 +523,7 @@ class DocumentPasteEditProvider {
}
return {
insertSnippet: typeof edit.insertText === 'string' ? edit.insertText : edit.insertText.value,
insertText: typeof edit.insertText === 'string' ? edit.insertText : { snippet: edit.insertText.value },
additionalEdit: edit.additionalEdit ? typeConvert.WorkspaceEdit.from(edit.additionalEdit) : undefined,
};
}
@ -1798,7 +1797,7 @@ class DocumentOnDropEditAdapter {
private readonly _handle: number,
) { }
async provideDocumentOnDropEdits(requestId: number, uri: URI, position: IPosition, dataTransferDto: extHostProtocol.DataTransferDTO, token: CancellationToken): Promise<Dto<languages.SnippetTextEdit> | undefined> {
async provideDocumentOnDropEdits(requestId: number, uri: URI, position: IPosition, dataTransferDto: extHostProtocol.DataTransferDTO, token: CancellationToken): Promise<extHostProtocol.IDocumentOnDropEditDto | undefined> {
const doc = this._documents.getDocument(uri);
const pos = typeConvert.Position.to(position);
const dataTransfer = typeConvert.DataTransfer.toDataTransfer(dataTransferDto, async (index) => {
@ -1809,7 +1808,10 @@ class DocumentOnDropEditAdapter {
if (!edit) {
return undefined;
}
return typeConvert.SnippetTextEdit.from(edit);
return {
insertText: typeof edit.insertText === 'string' ? edit.insertText : { snippet: edit.insertText.value },
additionalEdit: edit.additionalEdit ? typeConvert.WorkspaceEdit.from(edit.additionalEdit) : undefined,
};
}
}
@ -2453,7 +2455,7 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF
return this._createDisposable(handle);
}
$provideDocumentOnDropEdits(handle: number, requestId: number, resource: UriComponents, position: IPosition, dataTransferDto: extHostProtocol.DataTransferDTO, token: CancellationToken): Promise<Dto<languages.SnippetTextEdit> | undefined> {
$provideDocumentOnDropEdits(handle: number, requestId: number, resource: UriComponents, position: IPosition, dataTransferDto: extHostProtocol.DataTransferDTO, token: CancellationToken): Promise<extHostProtocol.IDocumentOnDropEditDto | undefined> {
return this._withAdapter(handle, DocumentOnDropEditAdapter, adapter =>
Promise.resolve(adapter.provideDocumentOnDropEdits(requestId, URI.revive(resource), position, dataTransferDto, token)), undefined, undefined);
}

View file

@ -562,15 +562,6 @@ export namespace TextEdit {
}
}
export namespace SnippetTextEdit {
export function from(edit: vscode.SnippetTextEdit): languages.SnippetTextEdit {
return {
range: Range.from(edit.range),
snippet: edit.snippet.value
};
}
}
export namespace WorkspaceEdit {
export interface IVersionInformationProvider {

View file

@ -647,17 +647,6 @@ export class NotebookEdit implements vscode.NotebookEdit {
}
}
export class SnippetTextEdit implements vscode.SnippetTextEdit {
range: vscode.Range;
snippet: vscode.SnippetString;
constructor(range: Range, snippet: SnippetString) {
this.range = range;
this.snippet = snippet;
}
}
export interface IFileOperationOptions {
overwrite?: boolean;
ignoreIfExists?: boolean;

View file

@ -7,12 +7,6 @@ declare module 'vscode' {
// https://github.com/microsoft/vscode/issues/142990
export class SnippetTextEdit {
snippet: SnippetString;
range: Range;
constructor(range: Range, snippet: SnippetString);
}
/**
* Provider which handles dropping of resources into a text editor.
*
@ -27,10 +21,25 @@ declare module 'vscode' {
* @param dataTransfer A {@link DataTransfer} object that holds data about what is being dragged and dropped.
* @param token A cancellation token.
*
* @return A {@link SnippetTextEdit} or a thenable that resolves to such. The lack of a result can be
* @return A {@link DocumentDropEdit} or a thenable that resolves to such. The lack of a result can be
* signaled by returning `undefined` or `null`.
*/
provideDocumentOnDropEdits(document: TextDocument, position: Position, dataTransfer: DataTransfer, token: CancellationToken): ProviderResult<SnippetTextEdit>;
provideDocumentOnDropEdits(document: TextDocument, position: Position, dataTransfer: DataTransfer, token: CancellationToken): ProviderResult<DocumentDropEdit>;
}
/**
* An edit operation applied on drop.
*/
export interface DocumentDropEdit {
/**
* The text or snippet to insert at the drop location.
*/
readonly insertText: string | SnippetString;
/**
* An optional additional edit to apply on drop.
*/
readonly additionalEdit?: WorkspaceEdit;
}
export namespace languages {