Skip to content
Merged
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
1 change: 1 addition & 0 deletions src/vs/base/browser/dom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1042,6 +1042,7 @@ export const EventType = {
UNLOAD: 'unload',
PAGE_SHOW: 'pageshow',
PAGE_HIDE: 'pagehide',
PASTE: 'paste',
ABORT: 'abort',
ERROR: 'error',
RESIZE: 'resize',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,12 +116,13 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({

const PASTE_FILE_ID = 'filesExplorer.paste';

KeybindingsRegistry.registerCommandAndKeybindingRule({
id: PASTE_FILE_ID,
CommandsRegistry.registerCommand(PASTE_FILE_ID, pasteFileHandler);

KeybindingsRegistry.registerKeybindingRule({
id: `^${PASTE_FILE_ID}`, // the `^` enables pasting files into the explorer by preventing default bubble up
weight: KeybindingWeight.WorkbenchContrib + explorerCommandsWeightBonus,
when: ContextKeyExpr.and(FilesExplorerFocusCondition, ExplorerResourceNotReadonlyContext),
primary: KeyMod.CtrlCmd | KeyCode.KeyV,
handler: pasteFileHandler
});

KeybindingsRegistry.registerCommandAndKeybindingRule({
Expand Down
43 changes: 40 additions & 3 deletions src/vs/workbench/contrib/files/browser/fileActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import * as nls from 'vs/nls';
import { isWindows, OperatingSystem, OS } from 'vs/base/common/platform';
import { extname, basename } from 'vs/base/common/path';
import { extname, basename, isAbsolute } from 'vs/base/common/path';
import * as resources from 'vs/base/common/resources';
import { URI } from 'vs/base/common/uri';
import { toErrorMessage } from 'vs/base/common/errorMessage';
Expand Down Expand Up @@ -1082,7 +1082,7 @@ CommandsRegistry.registerCommand({
handler: uploadFileHandler
});

export const pasteFileHandler = async (accessor: ServicesAccessor) => {
export const pasteFileHandler = async (accessor: ServicesAccessor, fileList?: FileList) => {
const clipboardService = accessor.get(IClipboardService);
const explorerService = accessor.get(IExplorerService);
const fileService = accessor.get(IFileService);
Expand All @@ -1093,7 +1093,34 @@ export const pasteFileHandler = async (accessor: ServicesAccessor) => {
const dialogService = accessor.get(IDialogService);

const context = explorerService.getContext(true);
const toPaste = resources.distinctParents(await clipboardService.readResources(), r => r);
const hasNativeFilesToPaste = fileList && fileList.length > 0;
const confirmPasteNative = hasNativeFilesToPaste && configurationService.getValue<boolean>('explorer.confirmPasteNative');

const toPaste = await getFilesToPaste(fileList, clipboardService);

if (confirmPasteNative) {
const message = toPaste.length > 1 ?
nls.localize('confirmMultiPasteNative', "Are you sure you want to paste the following {0} items?", toPaste.length) :
nls.localize('confirmPasteNative', "Are you sure you want to paste '{0}'?", basename(toPaste[0].fsPath));
const detail = toPaste.length > 1 ? getFileNamesMessage(toPaste) : undefined;
const confirmation = await dialogService.confirm({
message,
detail,
checkbox: {
label: nls.localize('doNotAskAgain', "Do not ask me again")
},
primaryButton: nls.localize({ key: 'pasteButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Paste")
});

if (!confirmation.confirmed) {
return;
}

// Check for confirmation checkbox
if (confirmation.checkboxChecked === true) {
await configurationService.updateValue('explorer.confirmPasteNative', false);
}
}
const element = context.length ? context[0] : explorerService.roots[0];
const incrementalNaming = configurationService.getValue<IFilesConfiguration>().explorer.incrementalNaming;

Expand Down Expand Up @@ -1175,6 +1202,16 @@ export const pasteFileHandler = async (accessor: ServicesAccessor) => {
}
};

async function getFilesToPaste(fileList: FileList | undefined, clipboardService: IClipboardService): Promise<readonly URI[]> {
if (fileList) {
// with a `fileList` we support natively pasting files from clipboard
return [...fileList].filter(file => !!file.path && isAbsolute(file.path)).map(file => URI.file(file.path));
} else {
// otherwise we fallback to reading resources from our clipboard service
return resources.distinctParents(await clipboardService.readResources(), resource => resource);
}
}

export const openFilePreserveFocusHandler = async (accessor: ServicesAccessor) => {
const editorService = accessor.get(IEditorService);
const explorerService = accessor.get(IExplorerService);
Expand Down
5 changes: 5 additions & 0 deletions src/vs/workbench/contrib/files/browser/files.contribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -444,6 +444,11 @@ configurationRegistry.registerConfiguration({
'description': nls.localize('confirmDragAndDrop', "Controls whether the Explorer should ask for confirmation to move files and folders via drag and drop."),
'default': true
},
'explorer.confirmPasteNative': {
'type': 'boolean',
'description': nls.localize('confirmPasteNative', "Controls whether the Explorer should ask for confirmation when pasting native files and folders."),
'default': true
},
'explorer.confirmDelete': {
'type': 'boolean',
'description': nls.localize('confirmDelete', "Controls whether the Explorer should ask for confirmation when deleting a file via the trash."),
Expand Down
9 changes: 9 additions & 0 deletions src/vs/workbench/contrib/files/browser/views/explorerView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,15 @@ export class ExplorerView extends ViewPane implements IExplorerView {
this.selectActiveFile(true);
}
}));

// Support for paste of files into explorer
this._register(DOM.addDisposableListener(DOM.getWindow(this.container), DOM.EventType.PASTE, async event => {
if (!this.hasFocus() || this.readonlyContext.get()) {
return;
}

await this.commandService.executeCommand('filesExplorer.paste', event.clipboardData?.files);
}));
}

override focus(): void {
Expand Down