Path: blob/main/src/vs/editor/contrib/dropOrPasteInto/browser/copyPasteController.ts
5260 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { IAction } from '../../../../base/common/actions.js';6import { coalesce } from '../../../../base/common/arrays.js';7import { CancelablePromise, createCancelablePromise, DeferredPromise, raceCancellation } from '../../../../base/common/async.js';8import { CancellationToken, CancellationTokenSource } from '../../../../base/common/cancellation.js';9import { createStringDataTransferItem, IReadonlyVSDataTransfer, matchesMimeType, UriList, VSDataTransfer } from '../../../../base/common/dataTransfer.js';10import { isCancellationError } from '../../../../base/common/errors.js';11import { HierarchicalKind } from '../../../../base/common/hierarchicalKind.js';12import { Disposable, DisposableStore } from '../../../../base/common/lifecycle.js';13import { Mimes } from '../../../../base/common/mime.js';14import { upcast } from '../../../../base/common/types.js';15import { generateUuid } from '../../../../base/common/uuid.js';16import { localize } from '../../../../nls.js';17import { IClipboardService } from '../../../../platform/clipboard/common/clipboardService.js';18import { ICommandService } from '../../../../platform/commands/common/commands.js';19import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';20import { RawContextKey } from '../../../../platform/contextkey/common/contextkey.js';21import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';22import { ILogService } from '../../../../platform/log/common/log.js';23import { IProgressService, ProgressLocation } from '../../../../platform/progress/common/progress.js';24import { IQuickInputService, IQuickPickItem, IQuickPickSeparator } from '../../../../platform/quickinput/common/quickInput.js';25import { IClipboardCopyEvent, IClipboardPasteEvent, IWritableClipboardData } from '../../../browser/controller/editContext/clipboardUtils.js';26import { ICodeEditor, PastePayload } from '../../../browser/editorBrowser.js';27import { IBulkEditService } from '../../../browser/services/bulkEditService.js';28import { EditorOption } from '../../../common/config/editorOptions.js';29import { Selection } from '../../../common/core/selection.js';30import { Handler, IEditorContribution } from '../../../common/editorCommon.js';31import { DocumentPasteContext, DocumentPasteEdit, DocumentPasteEditProvider, DocumentPasteTriggerKind } from '../../../common/languages.js';32import { ITextModel } from '../../../common/model.js';33import { ILanguageFeaturesService } from '../../../common/services/languageFeatures.js';34import { CodeEditorStateFlag, EditorStateCancellationTokenSource } from '../../editorState/browser/editorState.js';35import { InlineProgressManager } from '../../inlineProgress/browser/inlineProgress.js';36import { MessageController } from '../../message/browser/messageController.js';37import { PreferredPasteConfiguration } from './copyPasteContribution.js';38import { DefaultTextPasteOrDropEditProvider } from './defaultProviders.js';39import { createCombinedWorkspaceEdit, sortEditsByYieldTo } from './edit.js';40import { PostEditWidgetManager } from './postEditWidget.js';4142export const changePasteTypeCommandId = 'editor.changePasteType';4344export const pasteAsPreferenceConfig = 'editor.pasteAs.preferences';4546export const pasteWidgetVisibleCtx = new RawContextKey<boolean>('pasteWidgetVisible', false, localize('pasteWidgetVisible', "Whether the paste widget is showing"));4748const vscodeClipboardMime = 'application/vnd.code.copymetadata';4950interface CopyMetadata {51readonly id?: string;52readonly providerCopyMimeTypes?: readonly string[];5354readonly defaultPastePayload: Omit<PastePayload, 'text'>;55}5657type PasteEditWithProvider = DocumentPasteEdit & {58provider: DocumentPasteEditProvider;59};606162interface DocumentPasteWithProviderEditsSession {63edits: readonly PasteEditWithProvider[];64dispose(): void;65}6667export type PastePreference =68| { readonly only: HierarchicalKind }69| { readonly preferences: readonly HierarchicalKind[] }70| { readonly providerId: string } // Only used internally71;7273interface CopyOperation {74readonly providerMimeTypes: readonly string[];75readonly operation: CancelablePromise<IReadonlyVSDataTransfer | undefined>;76}7778export class CopyPasteController extends Disposable implements IEditorContribution {7980public static readonly ID = 'editor.contrib.copyPasteActionController';8182public static get(editor: ICodeEditor): CopyPasteController | null {83return editor.getContribution<CopyPasteController>(CopyPasteController.ID);84}8586public static setConfigureDefaultAction(action: IAction) {87CopyPasteController._configureDefaultAction = action;88}8990private static _configureDefaultAction?: IAction;9192/**93* Global tracking the last copy operation.94*95* This is shared across all editors so that you can copy and paste between groups.96*97* TODO: figure out how to make this work with multiple windows98*/99private static _currentCopyOperation?: {100readonly handle: string;101readonly operations: ReadonlyArray<CopyOperation>;102};103104private readonly _editor: ICodeEditor;105106private _currentPasteOperation?: CancelablePromise<void>;107private _pasteAsActionContext?: { readonly preferred?: PastePreference };108109private readonly _pasteProgressManager: InlineProgressManager;110private readonly _postPasteWidgetManager: PostEditWidgetManager<PasteEditWithProvider>;111112constructor(113editor: ICodeEditor,114@IInstantiationService instantiationService: IInstantiationService,115@ILogService private readonly _logService: ILogService,116@IBulkEditService private readonly _bulkEditService: IBulkEditService,117@IClipboardService private readonly _clipboardService: IClipboardService,118@ICommandService private readonly _commandService: ICommandService,119@IConfigurationService private readonly _configService: IConfigurationService,120@ILanguageFeaturesService private readonly _languageFeaturesService: ILanguageFeaturesService,121@IQuickInputService private readonly _quickInputService: IQuickInputService,122@IProgressService private readonly _progressService: IProgressService,123) {124super();125126this._editor = editor;127128this._register(editor.onWillCopy(e => this.handleCopy(e)));129this._register(editor.onWillCut(e => this.handleCopy(e)));130this._register(editor.onWillPaste(e => this.handlePaste(e)));131132this._pasteProgressManager = this._register(new InlineProgressManager('pasteIntoEditor', editor, instantiationService));133134this._postPasteWidgetManager = this._register(instantiationService.createInstance(PostEditWidgetManager, 'pasteIntoEditor', editor, pasteWidgetVisibleCtx,135{ id: changePasteTypeCommandId, label: localize('postPasteWidgetTitle', "Show paste options...") },136() => CopyPasteController._configureDefaultAction ? [CopyPasteController._configureDefaultAction] : []137));138}139140public changePasteType() {141this._postPasteWidgetManager.tryShowSelector();142}143144public async pasteAs(preferred?: PastePreference) {145this._logService.trace('CopyPasteController.pasteAs');146this._editor.focus();147try {148this._logService.trace('Before calling editor.action.clipboardPasteAction');149this._pasteAsActionContext = { preferred };150await this._commandService.executeCommand('editor.action.clipboardPasteAction');151} finally {152this._pasteAsActionContext = undefined;153}154}155156public clearWidgets() {157this._postPasteWidgetManager.clear();158}159160private isPasteAsEnabled(): boolean {161return this._editor.getOption(EditorOption.pasteAs).enabled;162}163164public async finishedPaste(): Promise<void> {165await this._currentPasteOperation;166}167168private handleCopy(e: IClipboardCopyEvent) {169this._logService.trace('CopyPasteController#handleCopy');170if (!this._editor.hasTextFocus()) {171return;172}173174// Explicitly clear the clipboard internal state.175// This is needed because on web, the browser clipboard is faked out using an in-memory store.176// This means the resources clipboard is not properly updated when copying from the editor.177this._clipboardService.clearInternalState?.();178179if (!this.isPasteAsEnabled()) {180return;181}182183const model = this._editor.getModel();184const viewModel = this._editor._getViewModel();185const selections = this._editor.getSelections();186if (!model || !viewModel || !selections?.length) {187return;188}189190const defaultPastePayload = {191multicursorText: e.dataToCopy.multicursorText ?? null,192pasteOnNewLine: e.dataToCopy.isFromEmptySelection,193mode: null194};195196const providers = this._languageFeaturesService.documentPasteEditProvider197.ordered(model)198.filter(x => !!x.prepareDocumentPaste);199if (!providers.length) {200this.setCopyMetadata(e.clipboardData, { defaultPastePayload });201return;202}203204const dataTransfer = new VSDataTransfer();205const providerCopyMimeTypes = providers.flatMap(x => x.copyMimeTypes ?? []);206207// Save off a handle pointing to data that VS Code maintains.208const handle = generateUuid();209this.setCopyMetadata(e.clipboardData, {210id: handle,211providerCopyMimeTypes,212defaultPastePayload213});214215const operations = providers.map((provider): CopyOperation => {216return {217providerMimeTypes: provider.copyMimeTypes,218operation: createCancelablePromise(token =>219provider.prepareDocumentPaste!(model, e.dataToCopy.sourceRanges, dataTransfer, token)220.catch(err => {221console.error(err);222return undefined;223}))224};225});226227CopyPasteController._currentCopyOperation?.operations.forEach(entry => entry.operation.cancel());228CopyPasteController._currentCopyOperation = { handle, operations };229}230231private async handlePaste(e: IClipboardPasteEvent) {232this._logService.trace('CopyPasteController#handlePaste for id : ', e.metadata?.id);233234if (!this._editor.hasTextFocus()) {235return;236}237238const dataTransfer = e.toExternalVSDataTransfer();239if (!dataTransfer) {240return;241}242dataTransfer.delete(vscodeClipboardMime);243244MessageController.get(this._editor)?.closeMessage();245this._currentPasteOperation?.cancel();246this._currentPasteOperation = undefined;247248const model = this._editor.getModel();249const selections = this._editor.getSelections();250if (!selections?.length || !model) {251return;252}253254if (255this._editor.getOption(EditorOption.readOnly) // Never enabled if editor is readonly.256|| (!this.isPasteAsEnabled() && !this._pasteAsActionContext) // Or feature disabled (but still enable if paste was explicitly requested)257) {258return;259}260261const metadata = this.fetchCopyMetadata(e);262this._logService.trace('CopyPasteController#handlePaste with metadata : ', metadata?.id, ' and text.length : ', e.clipboardData.getData('text/plain').length);263264const fileTypes = Array.from(e.clipboardData.files).map(file => file.type);265266const allPotentialMimeTypes = [267...e.clipboardData.types,268...fileTypes,269...metadata?.providerCopyMimeTypes ?? [],270// TODO: always adds `uri-list` because this get set if there are resources in the system clipboard.271// However we can only check the system clipboard async. For this early check, just add it in.272// We filter providers again once we have the final dataTransfer we will use.273Mimes.uriList,274];275276const allProviders = this._languageFeaturesService.documentPasteEditProvider277.ordered(model)278.filter(provider => {279// Filter out providers that don't match the requested paste types280const preference = this._pasteAsActionContext?.preferred;281if (preference) {282if (!this.providerMatchesPreference(provider, preference)) {283return false;284}285}286287// And providers that don't handle any of mime types in the clipboard288return provider.pasteMimeTypes?.some(type => matchesMimeType(type, allPotentialMimeTypes));289});290if (!allProviders.length) {291if (this._pasteAsActionContext?.preferred) {292this.showPasteAsNoEditMessage(selections, this._pasteAsActionContext.preferred);293294// Also prevent default paste from applying295e.setHandled();296}297return;298}299300// Prevent the editor's default paste handler from running.301// Note that after this point, we are fully responsible for handling paste.302// If we can't provider a paste for any reason, we need to explicitly delegate pasting back to the editor.303e.setHandled();304305if (this._pasteAsActionContext) {306this.showPasteAsPick(this._pasteAsActionContext.preferred, allProviders, selections, dataTransfer, metadata);307} else {308this.doPasteInline(allProviders, selections, dataTransfer, metadata, e.browserEvent);309}310}311312private showPasteAsNoEditMessage(selections: readonly Selection[], preference: PastePreference) {313const kindLabel = 'only' in preference314? preference.only.value315: 'preferences' in preference316? (preference.preferences.length ? preference.preferences.map(preference => preference.value).join(', ') : localize('noPreferences', "empty"))317: preference.providerId;318319MessageController.get(this._editor)?.showMessage(localize('pasteAsError', "No paste edits for '{0}' found", kindLabel), selections[0].getStartPosition());320}321322private doPasteInline(allProviders: readonly DocumentPasteEditProvider[], selections: readonly Selection[], dataTransfer: VSDataTransfer, metadata: CopyMetadata | undefined, clipboardEvent: ClipboardEvent | undefined): void {323this._logService.trace('CopyPasteController#doPasteInline');324const editor = this._editor;325if (!editor.hasModel()) {326return;327}328329const editorStateCts = new EditorStateCancellationTokenSource(editor, CodeEditorStateFlag.Value | CodeEditorStateFlag.Selection, undefined);330331const p = createCancelablePromise(async (pToken) => {332const editor = this._editor;333if (!editor.hasModel()) {334return;335}336const model = editor.getModel();337338const disposables = new DisposableStore();339const cts = disposables.add(new CancellationTokenSource(pToken));340disposables.add(editorStateCts.token.onCancellationRequested(() => cts.cancel()));341342const token = cts.token;343try {344await this.mergeInDataFromCopy(allProviders, dataTransfer, metadata, token);345if (token.isCancellationRequested) {346return;347}348349const supportedProviders = allProviders.filter(provider => this.isSupportedPasteProvider(provider, dataTransfer));350if (!supportedProviders.length351|| (supportedProviders.length === 1 && supportedProviders[0] instanceof DefaultTextPasteOrDropEditProvider) // Only our default text provider is active352) {353return this.applyDefaultPasteHandler(dataTransfer, metadata, token, clipboardEvent);354}355356const context: DocumentPasteContext = {357triggerKind: DocumentPasteTriggerKind.Automatic,358};359360const editSession = await this.getPasteEdits(supportedProviders, dataTransfer, model, selections, context, token);361disposables.add(editSession);362if (token.isCancellationRequested) {363return;364}365366// If the only edit returned is our default text edit, use the default paste handler367if (editSession.edits.length === 1 && editSession.edits[0].provider instanceof DefaultTextPasteOrDropEditProvider) {368return this.applyDefaultPasteHandler(dataTransfer, metadata, token, clipboardEvent);369}370371if (editSession.edits.length) {372const canShowWidget = editor.getOption(EditorOption.pasteAs).showPasteSelector === 'afterPaste';373return this._postPasteWidgetManager.applyEditAndShowIfNeeded(selections, { activeEditIndex: this.getInitialActiveEditIndex(model, editSession.edits), allEdits: editSession.edits }, canShowWidget, async (edit, resolveToken) => {374if (!edit.provider.resolveDocumentPasteEdit) {375return edit;376}377378const resolveP = edit.provider.resolveDocumentPasteEdit(edit, resolveToken);379const showP = new DeferredPromise<void>();380const resolved = await this._pasteProgressManager.showWhile(selections[0].getEndPosition(), localize('resolveProcess', "Resolving paste edit for '{0}'. Click to cancel", edit.title), raceCancellation(Promise.race([showP.p, resolveP]), resolveToken), {381cancel: () => showP.cancel()382}, 0);383384if (resolved) {385edit.insertText = resolved.insertText;386edit.additionalEdit = resolved.additionalEdit;387}388return edit;389}, token);390}391392await this.applyDefaultPasteHandler(dataTransfer, metadata, token, clipboardEvent);393} finally {394disposables.dispose();395if (this._currentPasteOperation === p) {396this._currentPasteOperation = undefined;397}398}399});400401this._pasteProgressManager.showWhile(selections[0].getEndPosition(), localize('pasteIntoEditorProgress', "Running paste handlers. Click to cancel and do basic paste"), p, {402cancel: async () => {403p.cancel();404if (editorStateCts.token.isCancellationRequested) {405return;406}407408await this.applyDefaultPasteHandler(dataTransfer, metadata, editorStateCts.token, clipboardEvent);409}410}).finally(() => {411editorStateCts.dispose();412});413this._currentPasteOperation = p;414}415416private showPasteAsPick(preference: PastePreference | undefined, allProviders: readonly DocumentPasteEditProvider[], selections: readonly Selection[], dataTransfer: VSDataTransfer, metadata: CopyMetadata | undefined): void {417this._logService.trace('CopyPasteController#showPasteAsPick');418const p = createCancelablePromise(async (token) => {419const editor = this._editor;420if (!editor.hasModel()) {421return;422}423const model = editor.getModel();424425const disposables = new DisposableStore();426const tokenSource = disposables.add(new EditorStateCancellationTokenSource(editor, CodeEditorStateFlag.Value | CodeEditorStateFlag.Selection, undefined, token));427try {428await this.mergeInDataFromCopy(allProviders, dataTransfer, metadata, tokenSource.token);429if (tokenSource.token.isCancellationRequested) {430return;431}432433// Filter out any providers the don't match the full data transfer we will send them.434let supportedProviders = allProviders.filter(provider => this.isSupportedPasteProvider(provider, dataTransfer, preference));435if (preference) {436// We are looking for a specific edit437supportedProviders = supportedProviders.filter(provider => this.providerMatchesPreference(provider, preference));438}439440const context: DocumentPasteContext = {441triggerKind: DocumentPasteTriggerKind.PasteAs,442only: preference && 'only' in preference ? preference.only : undefined,443};444let editSession = disposables.add(await this.getPasteEdits(supportedProviders, dataTransfer, model, selections, context, tokenSource.token));445if (tokenSource.token.isCancellationRequested) {446return;447}448449// Filter out any edits that don't match the requested kind450if (preference) {451editSession = {452edits: editSession.edits.filter(edit => {453if ('only' in preference) {454return preference.only.contains(edit.kind);455} else if ('preferences' in preference) {456return preference.preferences.some(preference => preference.contains(edit.kind));457} else {458return preference.providerId === edit.provider.id;459}460}),461dispose: editSession.dispose462};463}464465if (!editSession.edits.length) {466if (preference) {467this.showPasteAsNoEditMessage(selections, preference);468}469return;470}471472let pickedEdit: DocumentPasteEdit | undefined;473if (preference) {474pickedEdit = editSession.edits.at(0);475} else {476type ItemWithEdit = IQuickPickItem & { edit?: DocumentPasteEdit };477const configureDefaultItem: ItemWithEdit = {478id: 'editor.pasteAs.default',479label: localize('pasteAsDefault', "Configure default paste action"),480edit: undefined,481};482483const selected = await this._quickInputService.pick<ItemWithEdit>(484[485...editSession.edits.map((edit): ItemWithEdit => ({486label: edit.title,487description: edit.kind?.value,488edit,489})),490...(CopyPasteController._configureDefaultAction ? [491upcast<IQuickPickSeparator>({ type: 'separator' }),492{493label: CopyPasteController._configureDefaultAction.label,494edit: undefined,495}496] : [])497], {498placeHolder: localize('pasteAsPickerPlaceholder', "Select Paste Action"),499});500501if (selected === configureDefaultItem) {502CopyPasteController._configureDefaultAction?.run();503return;504}505506pickedEdit = selected?.edit;507}508509if (!pickedEdit) {510return;511}512513const combinedWorkspaceEdit = createCombinedWorkspaceEdit(model.uri, selections, pickedEdit);514await this._bulkEditService.apply(combinedWorkspaceEdit, { editor: this._editor });515} finally {516disposables.dispose();517if (this._currentPasteOperation === p) {518this._currentPasteOperation = undefined;519}520}521});522523this._progressService.withProgress({524location: ProgressLocation.Window,525title: localize('pasteAsProgress', "Running paste handlers"),526}, () => p);527}528529private setCopyMetadata(clipboardData: IWritableClipboardData, metadata: CopyMetadata) {530this._logService.trace('CopyPasteController#setCopyMetadata new id : ', metadata.id);531clipboardData.setData(vscodeClipboardMime, JSON.stringify(metadata));532}533534private fetchCopyMetadata(e: IClipboardPasteEvent): CopyMetadata | undefined {535this._logService.trace('CopyPasteController#fetchCopyMetadata');536537// Prefer using the clipboard data we saved off538const rawMetadata = e.clipboardData.getData(vscodeClipboardMime);539if (rawMetadata) {540try {541return JSON.parse(rawMetadata);542} catch {543return undefined;544}545}546547if (e.metadata) {548return {549defaultPastePayload: {550mode: e.metadata.mode,551multicursorText: e.metadata.multicursorText ?? null,552pasteOnNewLine: !!e.metadata.isFromEmptySelection,553},554};555}556557return undefined;558}559560private async mergeInDataFromCopy(allProviders: readonly DocumentPasteEditProvider[], dataTransfer: VSDataTransfer, metadata: CopyMetadata | undefined, token: CancellationToken): Promise<void> {561this._logService.trace('CopyPasteController#mergeInDataFromCopy with metadata : ', metadata?.id);562if (metadata?.id && CopyPasteController._currentCopyOperation?.handle === metadata.id) {563// Only resolve providers that have data we may care about564const toResolve = CopyPasteController._currentCopyOperation.operations565.filter(op => allProviders.some(provider => provider.pasteMimeTypes.some(type => matchesMimeType(type, op.providerMimeTypes))))566.map(op => op.operation);567568const toMergeResults = await Promise.all(toResolve);569if (token.isCancellationRequested) {570return;571}572573// Values from higher priority providers should overwrite values from lower priority ones.574// Reverse the array to so that the calls to `DataTransfer.replace` later will do this575for (const toMergeData of toMergeResults.reverse()) {576if (toMergeData) {577for (const [key, value] of toMergeData) {578dataTransfer.replace(key, value);579}580}581}582}583584if (!dataTransfer.has(Mimes.uriList)) {585const resources = await this._clipboardService.readResources();586if (token.isCancellationRequested) {587return;588}589590if (resources.length) {591dataTransfer.append(Mimes.uriList, createStringDataTransferItem(UriList.create(resources)));592}593}594}595596private async getPasteEdits(providers: readonly DocumentPasteEditProvider[], dataTransfer: VSDataTransfer, model: ITextModel, selections: readonly Selection[], context: DocumentPasteContext, token: CancellationToken): Promise<DocumentPasteWithProviderEditsSession> {597const disposables = new DisposableStore();598599const results = await raceCancellation(600Promise.all(providers.map(async provider => {601try {602const edits = await provider.provideDocumentPasteEdits?.(model, selections, dataTransfer, context, token);603if (edits) {604disposables.add(edits);605}606return edits?.edits?.map(edit => ({ ...edit, provider }));607} catch (err) {608if (!isCancellationError(err)) {609console.error(err);610}611return undefined;612}613})),614token);615const edits = coalesce(results ?? []).flat().filter(edit => {616return !context.only || context.only.contains(edit.kind);617});618return {619edits: sortEditsByYieldTo(edits),620dispose: () => disposables.dispose()621};622}623624private async applyDefaultPasteHandler(dataTransfer: VSDataTransfer, metadata: CopyMetadata | undefined, token: CancellationToken, clipboardEvent: ClipboardEvent | undefined) {625const textDataTransfer = dataTransfer.get(Mimes.text) ?? dataTransfer.get('text');626const text = (await textDataTransfer?.asString()) ?? '';627if (token.isCancellationRequested) {628return;629}630631const payload: PastePayload = {632clipboardEvent,633text,634pasteOnNewLine: metadata?.defaultPastePayload.pasteOnNewLine ?? false,635multicursorText: metadata?.defaultPastePayload.multicursorText ?? null,636mode: null,637};638this._logService.trace('CopyPasteController#applyDefaultPasteHandler for id : ', metadata?.id);639this._editor.trigger('keyboard', Handler.Paste, payload);640}641642/**643* Filter out providers if they:644* - Don't handle any of the data transfer types we have645* - Don't match the preferred paste kind646*/647private isSupportedPasteProvider(provider: DocumentPasteEditProvider, dataTransfer: VSDataTransfer, preference?: PastePreference): boolean {648if (!provider.pasteMimeTypes?.some(type => dataTransfer.matches(type))) {649return false;650}651652return !preference || this.providerMatchesPreference(provider, preference);653}654655private providerMatchesPreference(provider: DocumentPasteEditProvider, preference: PastePreference): boolean {656if ('only' in preference) {657return provider.providedPasteEditKinds.some(providedKind => preference.only.contains(providedKind));658} else if ('preferences' in preference) {659return preference.preferences.some(providedKind => preference.preferences.some(preferredKind => preferredKind.contains(providedKind)));660} else {661return provider.id === preference.providerId;662}663}664665private getInitialActiveEditIndex(model: ITextModel, edits: readonly DocumentPasteEdit[]): number {666const preferredProviders = this._configService.getValue<PreferredPasteConfiguration[]>(pasteAsPreferenceConfig, { resource: model.uri });667for (const config of Array.isArray(preferredProviders) ? preferredProviders : []) {668const desiredKind = new HierarchicalKind(config);669const editIndex = edits.findIndex(edit => desiredKind.contains(edit.kind));670if (editIndex >= 0) {671return editIndex;672}673}674675return 0;676}677}678679680