Add paste as default settings and enable js/ts paste with imports by default (#233031)

Fixes #184871
For #30066

Adds new settings that let you configure the default way to paste/drop.

Also enables js/ts paste with imports by default for 5.7+. However will not apply by default. Instead it will be shown as an option after pasting. You can then use the `editor.pasteAs.preferences` setting to make it apply automatically or use the `javascript.updateImportsOnPaste.enabled` settings to disable the feature entirely
This commit is contained in:
Matt Bierner 2024-11-04 16:30:09 -08:00 committed by GitHub
parent 49907c09c4
commit 54d81cd618
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 277 additions and 96 deletions

View File

@ -1506,23 +1506,17 @@
"description": "%typescript.tsserver.enableRegionDiagnostics%",
"scope": "window"
},
"javascript.experimental.updateImportsOnPaste": {
"javascript.updateImportsOnPaste.enabled": {
"scope": "window",
"type": "boolean",
"default": false,
"description": "%configuration.updateImportsOnPaste%",
"tags": [
"experimental"
]
"default": true,
"markdownDescription": "%configuration.updateImportsOnPaste%"
},
"typescript.experimental.updateImportsOnPaste": {
"typescript.updateImportsOnPaste.enabled": {
"scope": "window",
"type": "boolean",
"default": false,
"description": "%configuration.updateImportsOnPaste%",
"tags": [
"experimental"
]
"default": true,
"markdownDescription": "%configuration.updateImportsOnPaste%"
},
"typescript.experimental.expandableHover": {
"type": "boolean",

View File

@ -224,7 +224,7 @@
"configuration.tsserver.web.projectWideIntellisense.suppressSemanticErrors": "Suppresses semantic errors on web even when project wide IntelliSense is enabled. This is always on when project wide IntelliSense is not enabled or available. See `#typescript.tsserver.web.projectWideIntellisense.enabled#`",
"configuration.tsserver.web.typeAcquisition.enabled": "Enable/disable package acquisition on the web. This enables IntelliSense for imported packages. Requires `#typescript.tsserver.web.projectWideIntellisense.enabled#`. Currently not supported for Safari.",
"configuration.tsserver.nodePath": "Run TS Server on a custom Node installation. This can be a path to a Node executable, or 'node' if you want VS Code to detect a Node installation.",
"configuration.updateImportsOnPaste": "Automatically update imports when pasting code. Requires TypeScript 5.7+.",
"configuration.updateImportsOnPaste": "Enable updating imports when pasting code. Requires TypeScript 5.7+.\n\nBy default this shows a option to update imports after pasting. You can use the `#editor.pasteAs.preferences#` setting to update imports automatically when pasting: `\"editor.pasteAs.preferences\": [{ \"kind\": \"text.jsts.pasteWithImports\" }]`.",
"configuration.expandableHover": "Enable/disable expanding on hover.",
"walkthroughs.nodejsWelcome.title": "Get started with JavaScript and Node.js",
"walkthroughs.nodejsWelcome.description": "Make the most of Visual Studio Code's first-class JavaScript experience.",

View File

@ -38,7 +38,7 @@ class CopyMetadata {
}
}
const settingId = 'experimental.updateImportsOnPaste';
const enabledSettingId = 'updateImportsOnPaste.enabled';
class DocumentPasteProvider implements vscode.DocumentPasteEditProvider {
@ -127,6 +127,8 @@ class DocumentPasteProvider implements vscode.DocumentPasteEditProvider {
}
const edit = new vscode.DocumentPasteEdit('', vscode.l10n.t("Paste with imports"), DocumentPasteProvider.kind);
edit.yieldTo = [vscode.DocumentDropOrPasteEditKind.Empty.append('text', 'plain')];
const additionalEdit = new vscode.WorkspaceEdit();
for (const edit of response.body.edits) {
additionalEdit.set(this._client.toResource(edit.fileName), edit.textChanges.map(typeConverters.TextEdit.fromCodeEdit));
@ -146,7 +148,7 @@ class DocumentPasteProvider implements vscode.DocumentPasteEditProvider {
private isEnabled(document: vscode.TextDocument) {
const config = vscode.workspace.getConfiguration(this._modeId, document.uri);
return config.get(settingId, false);
return config.get(enabledSettingId, false);
}
}
@ -154,7 +156,7 @@ export function register(selector: DocumentSelector, language: LanguageDescripti
return conditionalRegistration([
requireSomeCapability(client, ClientCapability.Semantic),
requireMinVersion(client, API.v570),
requireGlobalConfiguration(language.id, settingId),
requireGlobalConfiguration(language.id, enabledSettingId),
], () => {
return vscode.languages.registerDocumentPasteEditProvider(selector.semantic, new DocumentPasteProvider(language.id, client), {
providedPasteEditKinds: [DocumentPasteProvider.kind],

View File

@ -6,14 +6,17 @@
import { HierarchicalKind } from '../../../../base/common/hierarchicalKind.js';
import { IJSONSchema, SchemaToType } from '../../../../base/common/jsonSchema.js';
import { KeyCode, KeyMod } from '../../../../base/common/keyCodes.js';
import * as nls from '../../../../nls.js';
import { Extensions as ConfigurationExtensions, ConfigurationScope, IConfigurationRegistry } from '../../../../platform/configuration/common/configurationRegistry.js';
import { KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js';
import { Registry } from '../../../../platform/registry/common/platform.js';
import { ICodeEditor } from '../../../browser/editorBrowser.js';
import { EditorAction, EditorCommand, EditorContributionInstantiation, ServicesAccessor, registerEditorAction, registerEditorCommand, registerEditorContribution } from '../../../browser/editorExtensions.js';
import { editorConfigurationBaseNode } from '../../../common/config/editorConfigurationSchema.js';
import { EditorContextKeys } from '../../../common/editorContextKeys.js';
import { registerEditorFeature } from '../../../common/editorFeatures.js';
import { CopyPasteController, changePasteTypeCommandId, pasteWidgetVisibleCtx } from './copyPasteController.js';
import { CopyPasteController, changePasteTypeCommandId, pasteWidgetVisibleCtx, pasteAsPreferenceConfig } from './copyPasteController.js';
import { DefaultPasteProvidersFeature, DefaultTextPasteOrDropEditProvider } from './defaultProviders.js';
import * as nls from '../../../../nls.js';
import { KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js';
registerEditorContribution(CopyPasteController.ID, CopyPasteController, EditorContributionInstantiation.Eager); // eager because it listens to events on the container dom node of the editor
registerEditorFeature(DefaultPasteProvidersFeature);
@ -105,3 +108,34 @@ registerEditorAction(class extends EditorAction {
return CopyPasteController.get(editor)?.pasteAs({ providerId: DefaultTextPasteOrDropEditProvider.id });
}
});
export type PreferredPasteConfiguration = ReadonlyArray<{ readonly kind: string; readonly mimeType?: string }>;
Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration).registerConfiguration({
...editorConfigurationBaseNode,
properties: {
[pasteAsPreferenceConfig]: {
type: 'array',
scope: ConfigurationScope.LANGUAGE_OVERRIDABLE,
description: nls.localize('preferredDescription', "Configures the preferred type of edit to use when pasting content.\n\nThis is an ordered list of edit kinds with optional mime types for the content being pasted. The first available edit of a preferred kind will be used."),
default: [],
items: {
type: 'object',
required: ['kind'],
properties: {
mimeType: {
type: 'string',
description: nls.localize('mimeType', "The optional mime type that this preference applies to. If not provided, the preference will be used for all mime types."),
},
kind: {
type: 'string',
description: nls.localize('kind', "The kind identifier of the paste edit."),
}
},
defaultSnippets: [
{ body: { kind: '$1' } }
]
}
},
}
});

View File

@ -4,15 +4,26 @@
*--------------------------------------------------------------------------------------------*/
import { addDisposableListener, getActiveDocument } from '../../../../base/browser/dom.js';
import { IAction } from '../../../../base/common/actions.js';
import { coalesce } from '../../../../base/common/arrays.js';
import { CancelablePromise, createCancelablePromise, DeferredPromise, raceCancellation } from '../../../../base/common/async.js';
import { CancellationToken, CancellationTokenSource } from '../../../../base/common/cancellation.js';
import { UriList, VSDataTransfer, createStringDataTransferItem, matchesMimeType } from '../../../../base/common/dataTransfer.js';
import { createStringDataTransferItem, matchesMimeType, UriList, VSDataTransfer } from '../../../../base/common/dataTransfer.js';
import { CancellationError, isCancellationError } from '../../../../base/common/errors.js';
import { HierarchicalKind } from '../../../../base/common/hierarchicalKind.js';
import { Disposable, DisposableStore } from '../../../../base/common/lifecycle.js';
import { Mimes } from '../../../../base/common/mime.js';
import * as platform from '../../../../base/common/platform.js';
import { upcast } from '../../../../base/common/types.js';
import { generateUuid } from '../../../../base/common/uuid.js';
import { localize } from '../../../../nls.js';
import { IClipboardService } from '../../../../platform/clipboard/common/clipboardService.js';
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
import { RawContextKey } from '../../../../platform/contextkey/common/contextkey.js';
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
import { IProgressService, ProgressLocation } from '../../../../platform/progress/common/progress.js';
import { IQuickInputService, IQuickPickItem, IQuickPickSeparator } from '../../../../platform/quickinput/common/quickInput.js';
import { ClipboardEventUtils } from '../../../browser/controller/editContext/textArea/textAreaEditContextInput.js';
import { toExternalVSDataTransfer, toVSDataTransfer } from '../../../browser/dnd.js';
import { ICodeEditor, PastePayload } from '../../../browser/editorBrowser.js';
import { IBulkEditService } from '../../../browser/services/bulkEditService.js';
@ -23,26 +34,21 @@ import { Handler, IEditorContribution } from '../../../common/editorCommon.js';
import { DocumentPasteContext, DocumentPasteEdit, DocumentPasteEditProvider, DocumentPasteTriggerKind } from '../../../common/languages.js';
import { ITextModel } from '../../../common/model.js';
import { ILanguageFeaturesService } from '../../../common/services/languageFeatures.js';
import { DefaultTextPasteOrDropEditProvider } from './defaultProviders.js';
import { createCombinedWorkspaceEdit, sortEditsByYieldTo } from './edit.js';
import { CodeEditorStateFlag, EditorStateCancellationTokenSource } from '../../editorState/browser/editorState.js';
import { InlineProgressManager } from '../../inlineProgress/browser/inlineProgress.js';
import { MessageController } from '../../message/browser/messageController.js';
import { localize } from '../../../../nls.js';
import { IClipboardService } from '../../../../platform/clipboard/common/clipboardService.js';
import { RawContextKey } from '../../../../platform/contextkey/common/contextkey.js';
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
import { IProgressService, ProgressLocation } from '../../../../platform/progress/common/progress.js';
import { IQuickInputService, IQuickPickItem } from '../../../../platform/quickinput/common/quickInput.js';
import { PreferredPasteConfiguration } from './copyPasteContribution.js';
import { DefaultTextPasteOrDropEditProvider } from './defaultProviders.js';
import { createCombinedWorkspaceEdit, sortEditsByYieldTo } from './edit.js';
import { PostEditWidgetManager } from './postEditWidget.js';
import { CancellationError, isCancellationError } from '../../../../base/common/errors.js';
import { ClipboardEventUtils } from '../../../browser/controller/editContext/textArea/textAreaEditContextInput.js';
export const changePasteTypeCommandId = 'editor.changePasteType';
export const pasteAsPreferenceConfig = 'editor.pasteAs.preferences';
export const pasteWidgetVisibleCtx = new RawContextKey<boolean>('pasteWidgetVisible', false, localize('pasteWidgetVisible', "Whether the paste widget is showing"));
const vscodeClipboardMime = 'application/vnd.code.copyMetadata';
const vscodeClipboardMime = 'application/vnd.code.copymetadata';
interface CopyMetadata {
readonly id?: string;
@ -73,6 +79,12 @@ export class CopyPasteController extends Disposable implements IEditorContributi
return editor.getContribution<CopyPasteController>(CopyPasteController.ID);
}
public static setConfigureDefaultAction(action: IAction) {
CopyPasteController._configureDefaultAction = action;
}
private static _configureDefaultAction?: IAction;
/**
* Global tracking the last copy operation.
*
@ -98,6 +110,7 @@ export class CopyPasteController extends Disposable implements IEditorContributi
@IInstantiationService instantiationService: IInstantiationService,
@IBulkEditService private readonly _bulkEditService: IBulkEditService,
@IClipboardService private readonly _clipboardService: IClipboardService,
@IConfigurationService private readonly _configService: IConfigurationService,
@ILanguageFeaturesService private readonly _languageFeaturesService: ILanguageFeaturesService,
@IQuickInputService private readonly _quickInputService: IQuickInputService,
@IProgressService private readonly _progressService: IProgressService,
@ -113,7 +126,10 @@ export class CopyPasteController extends Disposable implements IEditorContributi
this._pasteProgressManager = this._register(new InlineProgressManager('pasteIntoEditor', editor, instantiationService));
this._postPasteWidgetManager = this._register(instantiationService.createInstance(PostEditWidgetManager, 'pasteIntoEditor', editor, pasteWidgetVisibleCtx, { id: changePasteTypeCommandId, label: localize('postPasteWidgetTitle', "Show paste options...") }));
this._postPasteWidgetManager = this._register(instantiationService.createInstance(PostEditWidgetManager, 'pasteIntoEditor', editor, pasteWidgetVisibleCtx,
{ id: changePasteTypeCommandId, label: localize('postPasteWidgetTitle', "Show paste options...") },
() => CopyPasteController._configureDefaultAction ? [CopyPasteController._configureDefaultAction] : []
));
}
public changePasteType() {
@ -354,7 +370,7 @@ export class CopyPasteController extends Disposable implements IEditorContributi
if (editSession.edits.length) {
const canShowWidget = editor.getOption(EditorOption.pasteAs).showPasteSelector === 'afterPaste';
return this._postPasteWidgetManager.applyEditAndShowIfNeeded(selections, { activeEditIndex: 0, allEdits: editSession.edits }, canShowWidget, (edit, token) => {
return this._postPasteWidgetManager.applyEditAndShowIfNeeded(selections, { activeEditIndex: this.getInitialActiveEditIndex(model, editSession.edits), allEdits: editSession.edits }, canShowWidget, (edit, token) => {
return new Promise<PasteEditWithProvider>((resolve, reject) => {
(async () => {
try {
@ -464,14 +480,36 @@ export class CopyPasteController extends Disposable implements IEditorContributi
if (preference) {
pickedEdit = editSession.edits.at(0);
} else {
const selected = await this._quickInputService.pick(
editSession.edits.map((edit): IQuickPickItem & { edit: DocumentPasteEdit } => ({
label: edit.title,
description: edit.kind?.value,
edit,
})), {
type ItemWithEdit = IQuickPickItem & { edit?: DocumentPasteEdit };
const configureDefaultItem: ItemWithEdit = {
id: 'editor.pasteAs.default',
label: localize('pasteAsDefault', "Configure default paste action"),
edit: undefined,
};
const selected = await this._quickInputService.pick<ItemWithEdit>(
[
...editSession.edits.map((edit): ItemWithEdit => ({
label: edit.title,
description: edit.kind?.value,
edit,
})),
...(CopyPasteController._configureDefaultAction ? [
upcast<IQuickPickSeparator>({ type: 'separator' }),
{
label: CopyPasteController._configureDefaultAction.label,
edit: undefined,
}
] : [])
], {
placeHolder: localize('pasteAsPickerPlaceholder', "Select Paste Action"),
});
if (selected === configureDefaultItem) {
CopyPasteController._configureDefaultAction?.run();
return;
}
pickedEdit = selected?.edit;
}
@ -621,4 +659,18 @@ export class CopyPasteController extends Disposable implements IEditorContributi
return provider.id === preference.providerId;
}
}
private getInitialActiveEditIndex(model: ITextModel, edits: readonly DocumentPasteEdit[]) {
const preferredProviders = this._configService.getValue<PreferredPasteConfiguration>(pasteAsPreferenceConfig, { resource: model.uri });
for (const config of Array.isArray(preferredProviders) ? preferredProviders : []) {
const desiredKind = new HierarchicalKind(config.kind);
const editIndex = edits.findIndex(edit =>
desiredKind.contains(edit.kind)
&& (!config.mimeType || (edit.handledMimeType && matchesMimeType(config.mimeType, [edit.handledMimeType]))));
if (editIndex >= 0) {
return editIndex;
}
}
return 0;
}
}

View File

@ -4,16 +4,16 @@
*--------------------------------------------------------------------------------------------*/
import { KeyCode, KeyMod } from '../../../../base/common/keyCodes.js';
import * as nls from '../../../../nls.js';
import { Extensions as ConfigurationExtensions, ConfigurationScope, IConfigurationRegistry } from '../../../../platform/configuration/common/configurationRegistry.js';
import { KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js';
import { Registry } from '../../../../platform/registry/common/platform.js';
import { ICodeEditor } from '../../../browser/editorBrowser.js';
import { EditorCommand, EditorContributionInstantiation, ServicesAccessor, registerEditorCommand, registerEditorContribution } from '../../../browser/editorExtensions.js';
import { editorConfigurationBaseNode } from '../../../common/config/editorConfigurationSchema.js';
import { registerEditorFeature } from '../../../common/editorFeatures.js';
import { DefaultDropProvidersFeature } from './defaultProviders.js';
import * as nls from '../../../../nls.js';
import { Extensions as ConfigurationExtensions, ConfigurationScope, IConfigurationRegistry } from '../../../../platform/configuration/common/configurationRegistry.js';
import { KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js';
import { Registry } from '../../../../platform/registry/common/platform.js';
import { DropIntoEditorController, changeDropTypeCommandId, defaultProviderConfig, dropWidgetVisibleCtx } from './dropIntoEditorController.js';
import { DropIntoEditorController, changeDropTypeCommandId, dropAsPreferenceConfig, dropWidgetVisibleCtx } from './dropIntoEditorController.js';
registerEditorContribution(DropIntoEditorController.ID, DropIntoEditorController, EditorContributionInstantiation.BeforeFirstInteraction);
registerEditorFeature(DefaultDropProvidersFeature);
@ -52,17 +52,33 @@ registerEditorCommand(new class extends EditorCommand {
}
});
export type PreferredDropConfiguration = ReadonlyArray<{ readonly kind: string; readonly mimeType?: string }>;
Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration).registerConfiguration({
...editorConfigurationBaseNode,
properties: {
[defaultProviderConfig]: {
type: 'object',
[dropAsPreferenceConfig]: {
type: 'array',
scope: ConfigurationScope.LANGUAGE_OVERRIDABLE,
description: nls.localize('defaultProviderDescription', "Configures the default drop provider to use for content of a given mime type."),
default: {},
additionalProperties: {
type: 'string',
},
description: nls.localize('preferredDescription', "Configures the preferred type of edit to use when dropping content.\n\nThis is an ordered list of edit kinds with optional mime types for the content being dropped. The first available edit of a preferred kind will be used."),
default: [],
items: {
type: 'object',
required: ['kind'],
properties: {
mimeType: {
type: 'string',
description: nls.localize('mimeType', "The optional mime type that this preference applies to. If not provided, the preference will be used for all mime types."),
},
kind: {
type: 'string',
description: nls.localize('kind', "The kind identifier of the drop edit."),
}
},
defaultSnippets: [
{ body: { kind: '$1' } }
]
}
},
}
});

View File

@ -28,8 +28,12 @@ import { LocalSelectionTransfer } from '../../../../platform/dnd/browser/dnd.js'
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
import { sortEditsByYieldTo } from './edit.js';
import { PostEditWidgetManager } from './postEditWidget.js';
import { isCancellationError } from '../../../../base/common/errors.js';
import { CancellationToken } from '../../../../base/common/cancellation.js';
import { PreferredDropConfiguration } from './dropIntoEditorContribution.js';
import { IAction } from '../../../../base/common/actions.js';
export const defaultProviderConfig = 'editor.experimental.dropIntoEditor.defaultProvider';
export const dropAsPreferenceConfig = 'editor.dropIntoEditor.preferences';
export const changeDropTypeCommandId = 'editor.changeDropType';
@ -43,7 +47,18 @@ export class DropIntoEditorController extends Disposable implements IEditorContr
return editor.getContribution<DropIntoEditorController>(DropIntoEditorController.ID);
}
private _currentOperation?: CancelablePromise<void>;
public static setConfigureDefaultAction(action: IAction) {
this._configureDefaultAction = action;
}
private static _configureDefaultAction?: IAction;
/**
* Global tracking the current drop operation.
*
* TODO: figure out how to make this work with multiple windows
*/
private static _currentDropOperation?: CancelablePromise<void>;
private readonly _dropProgressManager: InlineProgressManager;
private readonly _postDropWidgetManager: PostEditWidgetManager<DocumentDropEdit>;
@ -60,7 +75,9 @@ export class DropIntoEditorController extends Disposable implements IEditorContr
super();
this._dropProgressManager = this._register(instantiationService.createInstance(InlineProgressManager, 'dropIntoEditor', editor));
this._postDropWidgetManager = this._register(instantiationService.createInstance(PostEditWidgetManager, 'dropIntoEditor', editor, dropWidgetVisibleCtx, { id: changeDropTypeCommandId, label: localize('postDropWidgetTitle', "Show drop options...") }));
this._postDropWidgetManager = this._register(instantiationService.createInstance(PostEditWidgetManager, 'dropIntoEditor', editor, dropWidgetVisibleCtx,
{ id: changeDropTypeCommandId, label: localize('postDropWidgetTitle', "Show drop options...") },
() => DropIntoEditorController._configureDefaultAction ? [DropIntoEditorController._configureDefaultAction] : []));
this._register(editor.onDropIntoEditor(e => this.onDropIntoEditor(editor, e.position, e.event)));
}
@ -78,7 +95,7 @@ export class DropIntoEditorController extends Disposable implements IEditorContr
return;
}
this._currentOperation?.cancel();
DropIntoEditorController._currentDropOperation?.cancel();
editor.focus();
editor.setPosition(position);
@ -108,7 +125,7 @@ export class DropIntoEditorController extends Disposable implements IEditorContr
return provider.dropMimeTypes.some(mime => ourDataTransfer.matches(mime));
});
const editSession = disposables.add(await this.getDropEdits(providers, model, position, ourDataTransfer, tokenSource));
const editSession = disposables.add(await this.getDropEdits(providers, model, position, ourDataTransfer, tokenSource.token));
if (tokenSource.token.isCancellationRequested) {
return;
}
@ -121,31 +138,34 @@ export class DropIntoEditorController extends Disposable implements IEditorContr
}
} finally {
disposables.dispose();
if (this._currentOperation === p) {
this._currentOperation = undefined;
if (DropIntoEditorController._currentDropOperation === p) {
DropIntoEditorController._currentDropOperation = undefined;
}
}
});
this._dropProgressManager.showWhile(position, localize('dropIntoEditorProgress', "Running drop handlers. Click to cancel"), p, { cancel: () => p.cancel() });
this._currentOperation = p;
DropIntoEditorController._currentDropOperation = p;
}
private async getDropEdits(providers: readonly DocumentDropEditProvider[], model: ITextModel, position: IPosition, dataTransfer: VSDataTransfer, tokenSource: EditorStateCancellationTokenSource) {
private async getDropEdits(providers: readonly DocumentDropEditProvider[], model: ITextModel, position: IPosition, dataTransfer: VSDataTransfer, token: CancellationToken) {
const disposables = new DisposableStore();
const results = await raceCancellation(Promise.all(providers.map(async provider => {
try {
const edits = await provider.provideDocumentDropEdits(model, position, dataTransfer, tokenSource.token);
const edits = await provider.provideDocumentDropEdits(model, position, dataTransfer, token);
if (edits) {
disposables.add(edits);
}
return edits?.edits.map(edit => ({ ...edit, providerId: provider.id }));
} catch (err) {
if (!isCancellationError(err)) {
console.error(err);
}
console.error(err);
}
return undefined;
})), tokenSource.token);
})), token);
const edits = coalesce(results ?? []).flat();
return {
@ -155,12 +175,12 @@ export class DropIntoEditorController extends Disposable implements IEditorContr
}
private getInitialActiveEditIndex(model: ITextModel, edits: ReadonlyArray<DocumentDropEdit & { readonly providerId?: string }>) {
const preferredProviders = this._configService.getValue<Record<string, string>>(defaultProviderConfig, { resource: model.uri });
for (const [configMime, desiredKindStr] of Object.entries(preferredProviders)) {
const desiredKind = new HierarchicalKind(desiredKindStr);
const preferredProviders = this._configService.getValue<PreferredDropConfiguration>(dropAsPreferenceConfig, { resource: model.uri });
for (const config of Array.isArray(preferredProviders) ? preferredProviders : []) {
const desiredKind = new HierarchicalKind(config.kind);
const editIndex = edits.findIndex(edit =>
desiredKind.value === edit.providerId
&& edit.handledMimeType && matchesMimeType(configMime, [edit.handledMimeType]));
(edit.kind && desiredKind.contains(edit.kind))
&& (!config.mimeType || (edit.handledMimeType && matchesMimeType(config.mimeType, [edit.handledMimeType]))));
if (editIndex >= 0) {
return editIndex;
}

View File

@ -5,25 +5,26 @@
import * as dom from '../../../../base/browser/dom.js';
import { Button } from '../../../../base/browser/ui/button/button.js';
import { toAction } from '../../../../base/common/actions.js';
import { IAction } from '../../../../base/common/actions.js';
import { CancellationToken } from '../../../../base/common/cancellation.js';
import { toErrorMessage } from '../../../../base/common/errorMessage.js';
import { isCancellationError } from '../../../../base/common/errors.js';
import { Event } from '../../../../base/common/event.js';
import { Disposable, MutableDisposable, toDisposable } from '../../../../base/common/lifecycle.js';
import './postEditWidget.css';
import { localize } from '../../../../nls.js';
import { ActionListItemKind, IActionListItem } from '../../../../platform/actionWidget/browser/actionList.js';
import { IActionWidgetService } from '../../../../platform/actionWidget/browser/actionWidget.js';
import { IContextKey, IContextKeyService, RawContextKey } from '../../../../platform/contextkey/common/contextkey.js';
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js';
import { INotificationService } from '../../../../platform/notification/common/notification.js';
import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IContentWidgetPosition } from '../../../browser/editorBrowser.js';
import { IBulkEditResult, IBulkEditService } from '../../../browser/services/bulkEditService.js';
import { Range } from '../../../common/core/range.js';
import { DocumentDropEdit, DocumentPasteEdit } from '../../../common/languages.js';
import { TrackedRangeStickiness } from '../../../common/model.js';
import { createCombinedWorkspaceEdit } from './edit.js';
import { localize } from '../../../../nls.js';
import { IContextKey, IContextKeyService, RawContextKey } from '../../../../platform/contextkey/common/contextkey.js';
import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js';
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js';
import { INotificationService } from '../../../../platform/notification/common/notification.js';
import './postEditWidget.css';
interface EditSet<Edit extends DocumentPasteEdit | DocumentDropEdit> {
@ -55,9 +56,10 @@ class PostEditWidget<T extends DocumentPasteEdit | DocumentDropEdit> extends Dis
private readonly range: Range,
private readonly edits: EditSet<T>,
private readonly onSelectNewEdit: (editIndex: number) => void,
@IContextMenuService private readonly _contextMenuService: IContextMenuService,
private readonly additionalActions: readonly IAction[],
@IContextKeyService contextKeyService: IContextKeyService,
@IKeybindingService private readonly _keybindingService: IKeybindingService,
@IActionWidgetService private readonly _actionWidgetService: IActionWidgetService,
) {
super();
@ -115,24 +117,31 @@ class PostEditWidget<T extends DocumentPasteEdit | DocumentDropEdit> extends Dis
}
showSelector() {
this._contextMenuService.showContextMenu({
getAnchor: () => {
const pos = dom.getDomNodePagePosition(this.button.element);
return { x: pos.left + pos.width, y: pos.top + pos.height };
},
getActions: () => {
return this.edits.allEdits.map((edit, i) => toAction({
id: '',
const pos = dom.getDomNodePagePosition(this.button.element);
const anchor = { x: pos.left + pos.width, y: pos.top + pos.height };
this._actionWidgetService.show('postEditWidget', false, [
...this.edits.allEdits.map((edit, i): IActionListItem<T> => {
return {
item: edit,
kind: ActionListItemKind.Action,
label: edit.title,
checked: i === this.edits.activeEditIndex,
run: () => {
if (i !== this.edits.activeEditIndex) {
return this.onSelectNewEdit(i);
}
},
}));
}
});
disabled: false,
canPreview: false,
hideIcon: true
};
})
], {
onHide: () => { },
onSelect: (item) => {
this._actionWidgetService.hide(false);
const i = this.edits.allEdits.findIndex(edit => edit === item);
if (i !== this.edits.activeEditIndex) {
return this.onSelectNewEdit(i);
}
},
}, anchor, this.editor.getDomNode() ?? undefined, this.additionalActions);
}
}
@ -145,6 +154,7 @@ export class PostEditWidgetManager<T extends DocumentPasteEdit | DocumentDropEdi
private readonly _editor: ICodeEditor,
private readonly _visibleContext: RawContextKey<boolean>,
private readonly _showCommand: ShowCommand,
private readonly _getAdditionalActions: () => readonly IAction[],
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@IBulkEditService private readonly _bulkEditService: IBulkEditService,
@INotificationService private readonly _notificationService: INotificationService,
@ -234,7 +244,7 @@ export class PostEditWidgetManager<T extends DocumentPasteEdit | DocumentDropEdi
this.clear();
if (this._editor.hasModel()) {
this._currentWidget.value = this._instantiationService.createInstance(PostEditWidget<T>, this._id, this._editor, this._visibleContext, this._showCommand, range, edits, onDidSelectEdit);
this._currentWidget.value = this._instantiationService.createInstance(PostEditWidget<T>, this._id, this._editor, this._visibleContext, this._showCommand, range, edits, onDidSelectEdit, this._getAdditionalActions());
}
}

View File

@ -37,6 +37,7 @@ export interface IActionListItem<T> {
readonly label?: string;
readonly keybinding?: ResolvedKeybinding;
canPreview?: boolean | undefined;
readonly hideIcon?: boolean;
}
interface IActionMenuTemplateData {
@ -118,6 +119,8 @@ class ActionItemRenderer<T> implements IListRenderer<IActionListItem<T>, IAction
return;
}
dom.setVisibility(!element.hideIcon, data.icon);
data.text.textContent = stripNewlines(element.label);
data.keybinding.set(element.keybinding);
@ -139,8 +142,8 @@ class ActionItemRenderer<T> implements IListRenderer<IActionListItem<T>, IAction
}
}
disposeTemplate(_templateData: IActionMenuTemplateData): void {
_templateData.keybinding.dispose();
disposeTemplate(templateData: IActionMenuTemplateData): void {
templateData.keybinding.dispose();
}
}

View File

@ -0,0 +1,46 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { toAction } from '../../../../base/common/actions.js';
import { CopyPasteController, pasteAsPreferenceConfig } from '../../../../editor/contrib/dropOrPasteInto/browser/copyPasteController.js';
import { dropAsPreferenceConfig, DropIntoEditorController } from '../../../../editor/contrib/dropOrPasteInto/browser/dropIntoEditorController.js';
import { localize } from '../../../../nls.js';
import { IWorkbenchContribution, registerWorkbenchContribution2, WorkbenchPhase } from '../../../common/contributions.js';
import { IPreferencesService } from '../../../services/preferences/common/preferences.js';
class DropOrPasteInto implements IWorkbenchContribution {
public static ID = 'workbench.contrib.dropOrPasteInto';
constructor(
@IPreferencesService private readonly _preferencesService: IPreferencesService
) {
CopyPasteController.setConfigureDefaultAction(toAction({
id: 'workbench.action.configurePreferredPasteAction',
label: localize('configureDefaultPaste.label', 'Configure preferred paste action...'),
run: () => this.configurePreferredPasteAction()
}));
DropIntoEditorController.setConfigureDefaultAction(toAction({
id: 'workbench.action.configurePreferredDropAction',
label: localize('configureDefaultDrop.label', 'Configure preferred drop action...'),
run: () => this.configurePreferredDropAction()
}));
}
private configurePreferredPasteAction() {
return this._preferencesService.openUserSettings({
jsonEditor: true,
revealSetting: { key: pasteAsPreferenceConfig, edit: true }
});
}
private configurePreferredDropAction() {
return this._preferencesService.openUserSettings({
jsonEditor: true,
revealSetting: { key: dropAsPreferenceConfig, edit: true }
});
}
}
registerWorkbenchContribution2(DropOrPasteInto.ID, DropOrPasteInto, WorkbenchPhase.Eventually);

View File

@ -398,4 +398,8 @@ import './contrib/scrollLocking/browser/scrollLocking.contribution.js';
// Inline Completions
import './contrib/inlineCompletions/browser/inlineCompletions.contribution.js';
// Drop or paste into
import './contrib/dropOrPasteInto/browser/dropOrPasteInto.contribution.js';
//#endregion