Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Preserve original data transfer value if still in same ext host context #241764

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,20 @@ import FileConfigurationManager from './fileConfigurationManager';
import { conditionalRegistration, requireGlobalConfiguration, requireMinVersion, requireSomeCapability } from './util/dependentRegistration';

class CopyMetadata {

static parse(data: string): CopyMetadata | undefined {
try {

const parsedData = JSON.parse(data);
const resource = vscode.Uri.parse(parsedData.resource);
const ranges = parsedData.ranges.map((range: any) => new vscode.Range(range.start, range.end));
const copyOperation = parsedData.copyOperation ? Promise.resolve(parsedData.copyOperation) : undefined;
return new CopyMetadata(resource, ranges, copyOperation);
} catch (error) {
return undefined;
}
}

constructor(
public readonly resource: vscode.Uri,
public readonly ranges: readonly vscode.Range[],
Expand Down Expand Up @@ -213,7 +227,15 @@ class DocumentPasteProvider implements vscode.DocumentPasteEditProvider<TsPasteE
return undefined;
}

return metadata instanceof CopyMetadata ? metadata : undefined;
if (metadata instanceof CopyMetadata) {
return metadata;
}

if (typeof metadata === 'string') {
return CopyMetadata.parse(metadata);
}

return undefined;
}

private isEnabled(document: vscode.TextDocument) {
Expand Down
7 changes: 5 additions & 2 deletions src/vs/base/common/dataTransfer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,25 @@ export interface IDataTransferFile {
}

export interface IDataTransferItem {
id?: string;
asString(): Thenable<string>;
asFile(): IDataTransferFile | undefined;
value: any;
}

export function createStringDataTransferItem(stringOrPromise: string | Promise<string>): IDataTransferItem {
export function createStringDataTransferItem(stringOrPromise: string | Promise<string>, id?: string): IDataTransferItem {
return {
id,
asString: async () => stringOrPromise,
asFile: () => undefined,
value: typeof stringOrPromise === 'string' ? stringOrPromise : undefined,
};
}

export function createFileDataTransferItem(fileName: string, uri: URI | undefined, data: () => Promise<Uint8Array>): IDataTransferItem {
export function createFileDataTransferItem(fileName: string, uri: URI | undefined, data: () => Promise<Uint8Array>, id?: string): IDataTransferItem {
const file = { id: generateUuid(), name: fileName, uri, data };
return {
id,
asString: async () => '',
asFile: () => file,
value: undefined,
Expand Down
2 changes: 1 addition & 1 deletion src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1051,7 +1051,7 @@ class MainThreadPasteEditProvider implements languages.DocumentPasteEditProvider

const dataTransferOut = new VSDataTransfer();
for (const [type, item] of newDataTransfer.items) {
dataTransferOut.replace(type, createStringDataTransferItem(item.asString));
dataTransferOut.replace(type, createStringDataTransferItem(item.asString, item.id));
}
return dataTransferOut;
};
Expand Down
3 changes: 2 additions & 1 deletion src/vs/workbench/api/common/extHost.protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1889,13 +1889,14 @@ export interface IDataTransferFileDTO {
}

export interface DataTransferItemDTO {
id: string;
readonly asString: string;
readonly fileData: IDataTransferFileDTO | undefined;
readonly uriListData?: ReadonlyArray<string | UriComponents>;
}

export interface DataTransferDTO {
readonly items: Array<[/* type */string, DataTransferItemDTO]>;
items: Array<readonly [/* type */string, DataTransferItemDTO]>;
}

export interface CheckboxUpdate {
Expand Down
55 changes: 42 additions & 13 deletions src/vs/workbench/api/common/extHostLanguageFeatures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import type * as vscode from 'vscode';
import { asArray, coalesce, isFalsyOrEmpty, isNonEmptyArray } from '../../../base/common/arrays.js';
import { raceCancellationError } from '../../../base/common/async.js';
import { VSBuffer } from '../../../base/common/buffer.js';
Expand All @@ -16,6 +17,7 @@ import { regExpLeadsToEndlessLoop } from '../../../base/common/strings.js';
import { assertType, isObject } from '../../../base/common/types.js';
import { URI, UriComponents } from '../../../base/common/uri.js';
import { IURITransformer } from '../../../base/common/uriIpc.js';
import { generateUuid } from '../../../base/common/uuid.js';
import { IPosition } from '../../../editor/common/core/position.js';
import { Range as EditorRange, IRange } from '../../../editor/common/core/range.js';
import { ISelection, Selection } from '../../../editor/common/core/selection.js';
Expand All @@ -25,17 +27,16 @@ import { encodeSemanticTokensDto } from '../../../editor/common/services/semanti
import { localize } from '../../../nls.js';
import { ExtensionIdentifier, IExtensionDescription } from '../../../platform/extensions/common/extensions.js';
import { ILogService } from '../../../platform/log/common/log.js';
import { checkProposedApiEnabled, isProposedApiEnabled } from '../../services/extensions/common/extensions.js';
import { Cache } from './cache.js';
import * as extHostProtocol from './extHost.protocol.js';
import { IExtHostApiDeprecationService } from './extHostApiDeprecationService.js';
import { CommandsConverter, ExtHostCommands } from './extHostCommands.js';
import { ExtHostDiagnostics } from './extHostDiagnostics.js';
import { ExtHostDocuments } from './extHostDocuments.js';
import { ExtHostTelemetry, IExtHostTelemetry } from './extHostTelemetry.js';
import * as typeConvert from './extHostTypeConverters.js';
import { CodeAction, CodeActionKind, CompletionList, Disposable, DocumentDropOrPasteEditKind, DocumentSymbol, InlineCompletionTriggerKind, InlineEditTriggerKind, InternalDataTransferItem, Location, NewSymbolNameTriggerKind, Range, SemanticTokens, SemanticTokensEdit, SemanticTokensEdits, SnippetString, SymbolInformation, SyntaxTokenType } from './extHostTypes.js';
import { checkProposedApiEnabled, isProposedApiEnabled } from '../../services/extensions/common/extensions.js';
import type * as vscode from 'vscode';
import { Cache } from './cache.js';
import * as extHostProtocol from './extHost.protocol.js';
import { CodeAction, CodeActionKind, CompletionList, DataTransfer, Disposable, DocumentDropOrPasteEditKind, DocumentSymbol, InlineCompletionTriggerKind, InlineEditTriggerKind, InternalDataTransferItem, Location, NewSymbolNameTriggerKind, Range, SemanticTokens, SemanticTokensEdit, SemanticTokensEdits, SnippetString, SymbolInformation, SyntaxTokenType } from './extHostTypes.js';

// --- adapter

Expand Down Expand Up @@ -586,7 +587,9 @@ class CodeActionAdapter {

class DocumentPasteEditProvider {

private readonly _cache = new Cache<vscode.DocumentPasteEdit>('DocumentPasteEdit');
private _cachedPrepare?: Map<string, vscode.DataTransferItem>;

private readonly _editsCache = new Cache<vscode.DocumentPasteEdit>('DocumentPasteEdit.edits');

constructor(
private readonly _proxy: extHostProtocol.MainThreadLanguageFeaturesShape,
Expand All @@ -601,6 +604,8 @@ class DocumentPasteEditProvider {
return;
}

this._cachedPrepare = undefined;

const doc = this._documents.getDocument(resource);
const vscodeRanges = ranges.map(range => typeConvert.Range.to(range));

Expand All @@ -613,8 +618,20 @@ class DocumentPasteEditProvider {
}

// Only send back values that have been added to the data transfer
const entries = Array.from(dataTransfer).filter(([, value]) => !(value instanceof InternalDataTransferItem));
return typeConvert.DataTransfer.from(entries);
const newEntries = Array.from(dataTransfer).filter(([, value]) => !(value instanceof InternalDataTransferItem));

// Store off original data transfer items so we can retrieve them on paste
const newCache = new Map<string, vscode.DataTransferItem>();

const items = await Promise.all(Array.from(newEntries, async ([mime, value]) => {
const id = generateUuid();
newCache.set(id, value);
return [mime, await typeConvert.DataTransferItem.from(mime, value, id)] as const;
}));

this._cachedPrepare = newCache;

return { items };
}

async providePasteEdits(requestId: number, resource: URI, ranges: IRange[], dataTransferDto: extHostProtocol.DataTransferDTO, context: extHostProtocol.IDocumentPasteContextDto, token: CancellationToken): Promise<extHostProtocol.IPasteEditDto[]> {
Expand All @@ -625,10 +642,22 @@ class DocumentPasteEditProvider {
const doc = this._documents.getDocument(resource);
const vscodeRanges = ranges.map(range => typeConvert.Range.to(range));

const dataTransfer = typeConvert.DataTransfer.toDataTransfer(dataTransferDto, async (id) => {
return (await this._proxy.$resolvePasteFileData(this._handle, requestId, id)).buffer;
const items = dataTransferDto.items.map(([mime, value]): [string, vscode.DataTransferItem] => {
const cached = this._cachedPrepare?.get(value.id);
if (cached) {
return [mime, cached];
}

return [
mime,
typeConvert.DataTransferItem.to(mime, value, async id => {
return (await this._proxy.$resolvePasteFileData(this._handle, requestId, id)).buffer;
})
];
});

const dataTransfer = new DataTransfer(items);

const edits = await this._provider.provideDocumentPasteEdits(doc, vscodeRanges, dataTransfer, {
only: context.only ? new DocumentDropOrPasteEditKind(context.only) : undefined,
triggerKind: context.triggerKind,
Expand All @@ -637,7 +666,7 @@ class DocumentPasteEditProvider {
return [];
}

const cacheId = this._cache.add(edits);
const cacheId = this._editsCache.add(edits);

return edits.map((edit, i): extHostProtocol.IPasteEditDto => ({
_cacheId: [cacheId, i],
Expand All @@ -651,7 +680,7 @@ class DocumentPasteEditProvider {

async resolvePasteEdit(id: extHostProtocol.ChainedCacheId, token: CancellationToken): Promise<{ insertText?: string | vscode.SnippetString; additionalEdit?: extHostProtocol.IWorkspaceEditDto }> {
const [sessionId, itemId] = id;
const item = this._cache.get(sessionId, itemId);
const item = this._editsCache.get(sessionId, itemId);
if (!item || !this._provider.resolveDocumentPasteEdit) {
return {}; // this should not happen...
}
Expand All @@ -664,7 +693,7 @@ class DocumentPasteEditProvider {
}

releasePasteEdits(id: number): any {
this._cache.delete(id);
this._editsCache.delete(id);
}
}

Expand Down
36 changes: 16 additions & 20 deletions src/vs/workbench/api/common/extHostTypeConverters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,17 @@ import { DisposableStore } from '../../../base/common/lifecycle.js';
import { ResourceMap, ResourceSet } from '../../../base/common/map.js';
import * as marked from '../../../base/common/marked/marked.js';
import { parse, revive } from '../../../base/common/marshalling.js';
import { MarshalledId } from '../../../base/common/marshallingIds.js';
import { Mimes } from '../../../base/common/mime.js';
import { cloneAndChange } from '../../../base/common/objects.js';
import { isWindows } from '../../../base/common/platform.js';
import { IPrefixTreeNode, WellDefinedPrefixTree } from '../../../base/common/prefixTree.js';
import { basename } from '../../../base/common/resources.js';
import { ThemeIcon } from '../../../base/common/themables.js';
import { isDefined, isEmptyObject, isNumber, isString, isUndefinedOrNull } from '../../../base/common/types.js';
import { URI, UriComponents, isUriComponents } from '../../../base/common/uri.js';
import { IURITransformer } from '../../../base/common/uriIpc.js';
import { generateUuid } from '../../../base/common/uuid.js';
import { RenderLineNumbersType } from '../../../editor/common/config/editorOptions.js';
import { IPosition } from '../../../editor/common/core/position.js';
import * as editorRange from '../../../editor/common/core/range.js';
Expand All @@ -37,30 +40,28 @@ import { ProgressLocation as MainProgressLocation } from '../../../platform/prog
import { DEFAULT_EDITOR_ASSOCIATION, SaveReason } from '../../common/editor.js';
import { IViewBadge } from '../../common/views.js';
import { ChatAgentLocation, IChatAgentRequest, IChatAgentResult } from '../../contrib/chat/common/chatAgents.js';
import { IChatRequestDraft } from '../../contrib/chat/common/chatEditingService.js';
import { IChatRequestVariableEntry } from '../../contrib/chat/common/chatModel.js';
import { IChatAgentMarkdownContentWithVulnerability, IChatCodeCitation, IChatCommandButton, IChatConfirmation, IChatContentInlineReference, IChatContentReference, IChatFollowup, IChatMarkdownContent, IChatMoveMessage, IChatProgressMessage, IChatResponseCodeblockUriPart, IChatTaskDto, IChatTaskResult, IChatTextEdit, IChatTreeData, IChatUserActionEvent, IChatWarningMessage } from '../../contrib/chat/common/chatService.js';
import { IToolData, IToolResult } from '../../contrib/chat/common/languageModelToolsService.js';
import * as chatProvider from '../../contrib/chat/common/languageModels.js';
import { IChatResponsePromptTsxPart, IChatResponseTextPart } from '../../contrib/chat/common/languageModels.js';
import { DebugTreeItemCollapsibleState, IDebugVisualizationTreeItem } from '../../contrib/debug/common/debug.js';
import * as notebooks from '../../contrib/notebook/common/notebookCommon.js';
import { CellEditType } from '../../contrib/notebook/common/notebookCommon.js';
import { ICellRange } from '../../contrib/notebook/common/notebookRange.js';
import * as search from '../../contrib/search/common/search.js';
import { TestId } from '../../contrib/testing/common/testId.js';
import { CoverageDetails, DetailType, ICoverageCount, IFileCoverage, ISerializedTestResults, ITestErrorMessage, ITestItem, ITestRunProfileReference, ITestTag, TestMessageType, TestResultItem, TestRunProfileBitset, denamespaceTestTag, namespaceTestTag } from '../../contrib/testing/common/testTypes.js';
import { EditorGroupColumn } from '../../services/editor/common/editorGroupColumn.js';
import { ACTIVE_GROUP, SIDE_GROUP } from '../../services/editor/common/editorService.js';
import { checkProposedApiEnabled } from '../../services/extensions/common/extensions.js';
import { Dto } from '../../services/extensions/common/proxyIdentifier.js';
import * as extHostProtocol from './extHost.protocol.js';
import { CommandsConverter } from './extHostCommands.js';
import { getPrivateApiFor } from './extHostTestingPrivateApi.js';
import * as types from './extHostTypes.js';
import { IChatResponseTextPart, IChatResponsePromptTsxPart } from '../../contrib/chat/common/languageModels.js';
import { LanguageModelTextPart, LanguageModelPromptTsxPart } from './extHostTypes.js';
import { MarshalledId } from '../../../base/common/marshallingIds.js';
import { IChatRequestDraft } from '../../contrib/chat/common/chatEditingService.js';
import { isWindows } from '../../../base/common/platform.js';
import { checkProposedApiEnabled } from '../../services/extensions/common/extensions.js';
import { CellEditType } from '../../contrib/notebook/common/notebookCommon.js';
import { LanguageModelPromptTsxPart, LanguageModelTextPart } from './extHostTypes.js';

export namespace Command {

Expand Down Expand Up @@ -2198,11 +2199,12 @@ export namespace DataTransferItem {
return new types.InternalDataTransferItem(item.asString);
}

export async function from(mime: string, item: vscode.DataTransferItem | IDataTransferItem): Promise<extHostProtocol.DataTransferItemDTO> {
export async function from(mime: string, item: vscode.DataTransferItem | IDataTransferItem, id: string = generateUuid()): Promise<extHostProtocol.DataTransferItemDTO> {
const stringValue = await item.asString();

if (mime === Mimes.uriList) {
return {
id,
asString: stringValue,
fileData: undefined,
uriListData: serializeUriList(stringValue),
Expand All @@ -2211,6 +2213,7 @@ export namespace DataTransferItem {

const fileValue = item.asFile();
return {
id,
asString: stringValue,
fileData: fileValue ? {
name: fileValue.name,
Expand Down Expand Up @@ -2251,19 +2254,12 @@ export namespace DataTransfer {
return new types.DataTransfer(init);
}

export async function from(dataTransfer: Iterable<readonly [string, vscode.DataTransferItem | IDataTransferItem]>): Promise<extHostProtocol.DataTransferDTO> {
const newDTO: extHostProtocol.DataTransferDTO = { items: [] };

const promises: Promise<any>[] = [];
for (const [mime, value] of dataTransfer) {
promises.push((async () => {
newDTO.items.push([mime, await DataTransferItem.from(mime, value)]);
})());
}

await Promise.all(promises);
export async function from(dataTransfer: Iterable<readonly [string, IDataTransferItem]>): Promise<extHostProtocol.DataTransferDTO> {
const items = await Promise.all(Array.from(dataTransfer, async ([mime, value]) => {
return [mime, await DataTransferItem.from(mime, value, value.id)] as const;
}));

return newDTO;
return { items };
}
}

Expand Down
10 changes: 5 additions & 5 deletions src/vs/workbench/api/common/extHostTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2902,9 +2902,9 @@ export class DataTransferFile implements vscode.DataTransferFile {

@es5ClassCompat
export class DataTransfer implements vscode.DataTransfer {
#items = new Map<string, DataTransferItem[]>();
#items = new Map<string, vscode.DataTransferItem[]>();

constructor(init?: Iterable<readonly [string, DataTransferItem]>) {
constructor(init?: Iterable<readonly [string, vscode.DataTransferItem]>) {
for (const [mime, item] of init ?? []) {
const existing = this.#items.get(this.#normalizeMime(mime));
if (existing) {
Expand All @@ -2915,17 +2915,17 @@ export class DataTransfer implements vscode.DataTransfer {
}
}

get(mimeType: string): DataTransferItem | undefined {
get(mimeType: string): vscode.DataTransferItem | undefined {
return this.#items.get(this.#normalizeMime(mimeType))?.[0];
}

set(mimeType: string, value: DataTransferItem): void {
set(mimeType: string, value: vscode.DataTransferItem): void {
// This intentionally overwrites all entries for a given mimetype.
// This is similar to how the DOM DataTransfer type works
this.#items.set(this.#normalizeMime(mimeType), [value]);
}

forEach(callbackfn: (value: DataTransferItem, key: string, dataTransfer: DataTransfer) => void, thisArg?: unknown): void {
forEach(callbackfn: (value: vscode.DataTransferItem, key: string, dataTransfer: DataTransfer) => void, thisArg?: unknown): void {
for (const [mime, items] of this.#items) {
for (const item of items) {
callbackfn.call(thisArg, item, mime, this);
Expand Down
Loading