Add PasteEditProvider (#107283)

For #30066

This adds a new `documentPaste` api proposal that lets extensions hook into copy and paste.

This can be used to do things such as:

- Create link when pasting an image
- Bring along imports when copy and pasting code
This commit is contained in:
Matt Bierner
2022-05-25 03:27:58 -07:00
committed by GitHub
parent a6724dcc10
commit e4f7f6a9da
21 changed files with 512 additions and 51 deletions

View File

@@ -457,6 +457,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
registerCodeActionsProvider(selector: vscode.DocumentSelector, provider: vscode.CodeActionProvider, metadata?: vscode.CodeActionProviderMetadata): vscode.Disposable {
return extHostLanguageFeatures.registerCodeActionProvider(extension, checkSelector(selector), provider, metadata);
},
registerDocumentPasteEditProvider(selector: vscode.DocumentSelector, provider: vscode.DocumentPasteEditProvider): vscode.Disposable {
checkProposedApiEnabled(extension, 'documentPaste');
return extHostLanguageFeatures.registerDocumentPasteEditProvider(extension, checkSelector(selector), provider);
},
registerCodeLensProvider(selector: vscode.DocumentSelector, provider: vscode.CodeLensProvider): vscode.Disposable {
return extHostLanguageFeatures.registerCodeLensProvider(extension, checkSelector(selector), provider);
},

View File

@@ -372,6 +372,7 @@ export interface MainThreadLanguageFeaturesShape extends IDisposable {
$registerLinkedEditingRangeProvider(handle: number, selector: IDocumentFilterDto[]): void;
$registerReferenceSupport(handle: number, selector: IDocumentFilterDto[]): void;
$registerQuickFixSupport(handle: number, selector: IDocumentFilterDto[], metadata: ICodeActionProviderMetadataDto, displayName: string, supportsResolve: boolean): void;
$registerPasteEditProvider(handle: number, selector: IDocumentFilterDto[], supportsCopy: boolean): void;
$registerDocumentFormattingSupport(handle: number, selector: IDocumentFilterDto[], extensionId: ExtensionIdentifier, displayName: string): void;
$registerRangeFormattingSupport(handle: number, selector: IDocumentFilterDto[], extensionId: ExtensionIdentifier, displayName: string): void;
$registerOnTypeFormattingSupport(handle: number, selector: IDocumentFilterDto[], autoFormatTriggerCharacters: string[], extensionId: ExtensionIdentifier): void;
@@ -1730,6 +1731,8 @@ export interface ExtHostLanguageFeaturesShape {
$provideCodeActions(handle: number, resource: UriComponents, rangeOrSelection: IRange | ISelection, context: languages.CodeActionContext, token: CancellationToken): Promise<ICodeActionListDto | undefined>;
$resolveCodeAction(handle: number, id: ChainedCacheId, token: CancellationToken): Promise<IWorkspaceEditDto | undefined>;
$releaseCodeActions(handle: number, cacheId: number): void;
$prepareDocumentPaste(handle: number, uri: UriComponents, range: IRange, dataTransfer: DataTransferDTO, token: CancellationToken): Promise<DataTransferDTO | undefined>;
$providePasteEdits(handle: number, uri: UriComponents, range: IRange, dataTransfer: DataTransferDTO, token: CancellationToken): Promise<IWorkspaceEditDto | Dto<languages.SnippetTextEdit> | undefined>;
$provideDocumentFormattingEdits(handle: number, resource: UriComponents, options: languages.FormattingOptions, token: CancellationToken): Promise<ISingleEditOperation[] | undefined>;
$provideDocumentRangeFormattingEdits(handle: number, resource: UriComponents, range: IRange, options: languages.FormattingOptions, token: CancellationToken): Promise<ISingleEditOperation[] | undefined>;
$provideOnTypeFormattingEdits(handle: number, resource: UriComponents, position: IPosition, ch: string, options: languages.FormattingOptions, token: CancellationToken): Promise<ISingleEditOperation[] | undefined>;

View File

@@ -7,7 +7,7 @@ import { URI, UriComponents } from 'vs/base/common/uri';
import { mixin } from 'vs/base/common/objects';
import type * as vscode from 'vscode';
import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters';
import { Range, Disposable, CompletionList, SnippetString, CodeActionKind, SymbolInformation, DocumentSymbol, SemanticTokensEdits, SemanticTokens, SemanticTokensEdit, Location, InlineCompletionTriggerKindNew, InlineCompletionTriggerKind } from 'vs/workbench/api/common/extHostTypes';
import { Range, Disposable, CompletionList, SnippetString, CodeActionKind, SymbolInformation, DocumentSymbol, SemanticTokensEdits, SemanticTokens, SemanticTokensEdit, Location, InlineCompletionTriggerKindNew, InlineCompletionTriggerKind, WorkspaceEdit } from 'vs/workbench/api/common/extHostTypes';
import { ISingleEditOperation } from 'vs/editor/common/core/editOperation';
import * as languages from 'vs/editor/common/languages';
import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments';
@@ -31,7 +31,7 @@ import { IdGenerator } from 'vs/base/common/idGenerator';
import { IExtHostApiDeprecationService } from 'vs/workbench/api/common/extHostApiDeprecationService';
import { Cache } from './cache';
import { StopWatch } from 'vs/base/common/stopwatch';
import { isCancellationError } from 'vs/base/common/errors';
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';
@@ -485,6 +485,52 @@ class CodeActionAdapter {
}
}
class DocumentPasteEditProvider {
constructor(
private readonly _proxy: extHostProtocol.MainThreadLanguageFeaturesShape,
private readonly _documents: ExtHostDocuments,
private readonly _provider: vscode.DocumentPasteEditProvider,
private readonly _handle: number,
) { }
async prepareDocumentPaste(resource: URI, range: IRange, dataTransferDto: extHostProtocol.DataTransferDTO, token: CancellationToken): Promise<extHostProtocol.DataTransferDTO | undefined> {
if (!this._provider.prepareDocumentPaste) {
return undefined;
}
const doc = this._documents.getDocument(resource);
const vscodeRange = typeConvert.Range.to(range);
const dataTransfer = typeConvert.DataTransfer.toDataTransfer(dataTransferDto, () => {
throw new NotImplementedError();
});
await this._provider.prepareDocumentPaste(doc, vscodeRange, dataTransfer, token);
return typeConvert.DataTransfer.toDataTransferDTO(dataTransfer);
}
async providePasteEdits(requestId: number, resource: URI, range: IRange, dataTransferDto: extHostProtocol.DataTransferDTO, token: CancellationToken): Promise<undefined | extHostProtocol.IWorkspaceEditDto | Dto<languages.SnippetTextEdit>> {
const doc = this._documents.getDocument(resource);
const vscodeRange = typeConvert.Range.to(range);
const dataTransfer = typeConvert.DataTransfer.toDataTransfer(dataTransferDto, async (index) => {
return (await this._proxy.$resolveDocumentOnDropFileData(this._handle, requestId, index)).buffer;
});
const edit = await this._provider.provideDocumentPasteEdits(doc, vscodeRange, dataTransfer, token);
if (!edit) {
return;
}
if (edit instanceof WorkspaceEdit) {
return typeConvert.WorkspaceEdit.from(edit);
} else {
return typeConvert.SnippetTextEdit.from(edit as vscode.SnippetTextEdit);
}
}
}
class DocumentFormattingAdapter {
constructor(
@@ -1769,7 +1815,7 @@ class DocumentOnDropEditAdapter {
}
type Adapter = DocumentSymbolAdapter | CodeLensAdapter | DefinitionAdapter | HoverAdapter
| DocumentHighlightAdapter | ReferenceAdapter | CodeActionAdapter | DocumentFormattingAdapter
| DocumentHighlightAdapter | ReferenceAdapter | CodeActionAdapter | DocumentPasteEditProvider | DocumentFormattingAdapter
| RangeFormattingAdapter | OnTypeFormattingAdapter | NavigateTypeAdapter | RenameAdapter
| CompletionsAdapter | SignatureHelpAdapter | LinkProviderAdapter | ImplementationAdapter
| TypeDefinitionAdapter | ColorProviderAdapter | FoldingProviderAdapter | DeclarationAdapter
@@ -2413,6 +2459,23 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF
Promise.resolve(adapter.provideDocumentOnDropEdits(requestId, URI.revive(resource), position, dataTransferDto, token)), undefined, undefined);
}
// --- copy/paste actions
registerDocumentPasteEditProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.DocumentPasteEditProvider): vscode.Disposable {
const handle = this._nextHandle();
this._adapter.set(handle, new AdapterData(new DocumentPasteEditProvider(this._proxy, this._documents, provider, handle), extension));
this._proxy.$registerPasteEditProvider(handle, this._transformDocumentSelector(selector), !!provider.prepareDocumentPaste);
return this._createDisposable(handle);
}
$prepareDocumentPaste(handle: number, resource: UriComponents, range: IRange, dataTransfer: extHostProtocol.DataTransferDTO, token: CancellationToken): Promise<extHostProtocol.DataTransferDTO | undefined> {
return this._withAdapter(handle, DocumentPasteEditProvider, adapter => adapter.prepareDocumentPaste(URI.revive(resource), range, dataTransfer, token), undefined, token);
}
$providePasteEdits(handle: number, resource: UriComponents, range: IRange, dataTransferDto: extHostProtocol.DataTransferDTO, token: CancellationToken): Promise<extHostProtocol.IWorkspaceEditDto | Dto<languages.SnippetTextEdit> | undefined> {
return this._withAdapter(handle, DocumentPasteEditProvider, adapter => adapter.providePasteEdits(0, URI.revive(resource), range, dataTransferDto, token), undefined, token);
}
// --- configuration
private static _serializeRegExp(regExp: RegExp): extHostProtocol.IRegExpDto {