Path: blob/main/src/vs/workbench/contrib/files/browser/fileCommands.ts
3296 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 * as nls from '../../../../nls.js';6import { URI } from '../../../../base/common/uri.js';7import { EditorResourceAccessor, IEditorCommandsContext, SideBySideEditor, IEditorIdentifier, SaveReason, EditorsOrder, EditorInputCapabilities } from '../../../common/editor.js';8import { SideBySideEditorInput } from '../../../common/editor/sideBySideEditorInput.js';9import { IWindowOpenable, IOpenWindowOptions, isWorkspaceToOpen, IOpenEmptyWindowOptions } from '../../../../platform/window/common/window.js';10import { IHostService } from '../../../services/host/browser/host.js';11import { ServicesAccessor, IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';12import { IWorkspaceContextService, UNTITLED_WORKSPACE_NAME } from '../../../../platform/workspace/common/workspace.js';13import { ExplorerFocusCondition, TextFileContentProvider, VIEWLET_ID, ExplorerCompressedFocusContext, ExplorerCompressedFirstFocusContext, ExplorerCompressedLastFocusContext, FilesExplorerFocusCondition, ExplorerFolderContext, VIEW_ID } from '../common/files.js';14import { ExplorerViewPaneContainer } from './explorerViewlet.js';15import { IClipboardService } from '../../../../platform/clipboard/common/clipboardService.js';16import { toErrorMessage } from '../../../../base/common/errorMessage.js';17import { CommandsRegistry, ICommandHandler, ICommandService } from '../../../../platform/commands/common/commands.js';18import { IContextKey, IContextKeyService, ContextKeyExpr } from '../../../../platform/contextkey/common/contextkey.js';19import { IFileService } from '../../../../platform/files/common/files.js';20import { KeybindingsRegistry, KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js';21import { KeyMod, KeyCode, KeyChord } from '../../../../base/common/keyCodes.js';22import { isWeb, isWindows } from '../../../../base/common/platform.js';23import { ITextModelService } from '../../../../editor/common/services/resolverService.js';24import { getResourceForCommand, getMultiSelectedResources, getOpenEditorsViewMultiSelection, IExplorerService } from './files.js';25import { IWorkspaceEditingService } from '../../../services/workspaces/common/workspaceEditing.js';26import { resolveCommandsContext } from '../../../browser/parts/editor/editorCommandsContext.js';27import { Schemas } from '../../../../base/common/network.js';28import { INotificationService, Severity } from '../../../../platform/notification/common/notification.js';29import { EditorContextKeys } from '../../../../editor/common/editorContextKeys.js';30import { IEditorService, SIDE_GROUP, ISaveEditorsOptions } from '../../../services/editor/common/editorService.js';31import { IEditorGroupsService, GroupsOrder, IEditorGroup } from '../../../services/editor/common/editorGroupsService.js';32import { ILabelService } from '../../../../platform/label/common/label.js';33import { basename, joinPath, isEqual } from '../../../../base/common/resources.js';34import { IDisposable, dispose } from '../../../../base/common/lifecycle.js';35import { IEnvironmentService } from '../../../../platform/environment/common/environment.js';36import { ICodeEditorService } from '../../../../editor/browser/services/codeEditorService.js';37import { EmbeddedCodeEditorWidget } from '../../../../editor/browser/widget/codeEditor/embeddedCodeEditorWidget.js';38import { ITextFileService } from '../../../services/textfile/common/textfiles.js';39import { IUriIdentityService } from '../../../../platform/uriIdentity/common/uriIdentity.js';40import { isCancellationError } from '../../../../base/common/errors.js';41import { IAction, toAction } from '../../../../base/common/actions.js';42import { EditorOpenSource, EditorResolution } from '../../../../platform/editor/common/editor.js';43import { hash } from '../../../../base/common/hash.js';44import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';45import { IPaneCompositePartService } from '../../../services/panecomposite/browser/panecomposite.js';46import { ViewContainerLocation } from '../../../common/views.js';47import { IViewsService } from '../../../services/views/common/viewsService.js';48import { OPEN_TO_SIDE_COMMAND_ID, COMPARE_WITH_SAVED_COMMAND_ID, SELECT_FOR_COMPARE_COMMAND_ID, ResourceSelectedForCompareContext, COMPARE_SELECTED_COMMAND_ID, COMPARE_RESOURCE_COMMAND_ID, COPY_PATH_COMMAND_ID, COPY_RELATIVE_PATH_COMMAND_ID, REVEAL_IN_EXPLORER_COMMAND_ID, OPEN_WITH_EXPLORER_COMMAND_ID, SAVE_FILE_COMMAND_ID, SAVE_FILE_WITHOUT_FORMATTING_COMMAND_ID, SAVE_FILE_AS_COMMAND_ID, SAVE_ALL_COMMAND_ID, SAVE_ALL_IN_GROUP_COMMAND_ID, SAVE_FILES_COMMAND_ID, REVERT_FILE_COMMAND_ID, REMOVE_ROOT_FOLDER_COMMAND_ID, PREVIOUS_COMPRESSED_FOLDER, NEXT_COMPRESSED_FOLDER, FIRST_COMPRESSED_FOLDER, LAST_COMPRESSED_FOLDER, NEW_UNTITLED_FILE_COMMAND_ID, NEW_UNTITLED_FILE_LABEL, NEW_FILE_COMMAND_ID } from './fileConstants.js';49import { IFileDialogService } from '../../../../platform/dialogs/common/dialogs.js';50import { RemoveRootFolderAction } from '../../../browser/actions/workspaceActions.js';51import { OpenEditorsView } from './views/openEditorsView.js';52import { ExplorerView } from './views/explorerView.js';53import { IListService } from '../../../../platform/list/browser/listService.js';5455export const openWindowCommand = (accessor: ServicesAccessor, toOpen: IWindowOpenable[], options?: IOpenWindowOptions) => {56if (Array.isArray(toOpen)) {57const hostService = accessor.get(IHostService);58const environmentService = accessor.get(IEnvironmentService);5960// rewrite untitled: workspace URIs to the absolute path on disk61toOpen = toOpen.map(openable => {62if (isWorkspaceToOpen(openable) && openable.workspaceUri.scheme === Schemas.untitled) {63return {64workspaceUri: joinPath(environmentService.untitledWorkspacesHome, openable.workspaceUri.path, UNTITLED_WORKSPACE_NAME)65};66}6768return openable;69});7071hostService.openWindow(toOpen, options);72}73};7475export const newWindowCommand = (accessor: ServicesAccessor, options?: IOpenEmptyWindowOptions) => {76const hostService = accessor.get(IHostService);77hostService.openWindow(options);78};7980// Command registration8182KeybindingsRegistry.registerCommandAndKeybindingRule({83weight: KeybindingWeight.WorkbenchContrib,84when: ExplorerFocusCondition,85primary: KeyMod.CtrlCmd | KeyCode.Enter,86mac: {87primary: KeyMod.WinCtrl | KeyCode.Enter88},89id: OPEN_TO_SIDE_COMMAND_ID, handler: async (accessor, resource: URI | object) => {90const editorService = accessor.get(IEditorService);91const fileService = accessor.get(IFileService);92const explorerService = accessor.get(IExplorerService);93const resources = getMultiSelectedResources(resource, accessor.get(IListService), editorService, accessor.get(IEditorGroupsService), explorerService);9495// Set side input96if (resources.length) {97const untitledResources = resources.filter(resource => resource.scheme === Schemas.untitled);98const fileResources = resources.filter(resource => resource.scheme !== Schemas.untitled);99100const items = await Promise.all(fileResources.map(async resource => {101const item = explorerService.findClosest(resource);102if (item) {103// Explorer already resolved the item, no need to go to the file service #109780104return item;105}106107return await fileService.stat(resource);108}));109const files = items.filter(i => !i.isDirectory);110const editors = files.map(f => ({111resource: f.resource,112options: { pinned: true }113})).concat(...untitledResources.map(untitledResource => ({ resource: untitledResource, options: { pinned: true } })));114115await editorService.openEditors(editors, SIDE_GROUP);116}117}118});119120KeybindingsRegistry.registerCommandAndKeybindingRule({121weight: KeybindingWeight.WorkbenchContrib + 10,122when: ContextKeyExpr.and(FilesExplorerFocusCondition, ExplorerFolderContext.toNegated()),123primary: KeyCode.Enter,124mac: {125primary: KeyMod.CtrlCmd | KeyCode.DownArrow126},127id: 'explorer.openAndPassFocus', handler: async (accessor, _resource: URI | object) => {128const editorService = accessor.get(IEditorService);129const explorerService = accessor.get(IExplorerService);130const resources = explorerService.getContext(true);131132if (resources.length) {133await editorService.openEditors(resources.map(r => ({ resource: r.resource, options: { preserveFocus: false, pinned: true } })));134}135}136});137138const COMPARE_WITH_SAVED_SCHEMA = 'showModifications';139let providerDisposables: IDisposable[] = [];140KeybindingsRegistry.registerCommandAndKeybindingRule({141id: COMPARE_WITH_SAVED_COMMAND_ID,142when: undefined,143weight: KeybindingWeight.WorkbenchContrib,144primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyCode.KeyD),145handler: async (accessor, resource: URI | object) => {146const instantiationService = accessor.get(IInstantiationService);147const textModelService = accessor.get(ITextModelService);148const editorService = accessor.get(IEditorService);149const fileService = accessor.get(IFileService);150const listService = accessor.get(IListService);151152// Register provider at first as needed153let registerEditorListener = false;154if (providerDisposables.length === 0) {155registerEditorListener = true;156157const provider = instantiationService.createInstance(TextFileContentProvider);158providerDisposables.push(provider);159providerDisposables.push(textModelService.registerTextModelContentProvider(COMPARE_WITH_SAVED_SCHEMA, provider));160}161162// Open editor (only resources that can be handled by file service are supported)163const uri = getResourceForCommand(resource, editorService, listService);164if (uri && fileService.hasProvider(uri)) {165const name = basename(uri);166const editorLabel = nls.localize('modifiedLabel', "{0} (in file) ↔ {1}", name, name);167168try {169await TextFileContentProvider.open(uri, COMPARE_WITH_SAVED_SCHEMA, editorLabel, editorService, { pinned: true });170// Dispose once no more diff editor is opened with the scheme171if (registerEditorListener) {172providerDisposables.push(editorService.onDidVisibleEditorsChange(() => {173if (!editorService.editors.some(editor => !!EditorResourceAccessor.getCanonicalUri(editor, { supportSideBySide: SideBySideEditor.SECONDARY, filterByScheme: COMPARE_WITH_SAVED_SCHEMA }))) {174providerDisposables = dispose(providerDisposables);175}176}));177}178} catch {179providerDisposables = dispose(providerDisposables);180}181}182}183});184185let globalResourceToCompare: URI | undefined;186let resourceSelectedForCompareContext: IContextKey<boolean>;187CommandsRegistry.registerCommand({188id: SELECT_FOR_COMPARE_COMMAND_ID,189handler: (accessor, resource: URI | object) => {190globalResourceToCompare = getResourceForCommand(resource, accessor.get(IEditorService), accessor.get(IListService));191if (!resourceSelectedForCompareContext) {192resourceSelectedForCompareContext = ResourceSelectedForCompareContext.bindTo(accessor.get(IContextKeyService));193}194resourceSelectedForCompareContext.set(true);195}196});197198CommandsRegistry.registerCommand({199id: COMPARE_SELECTED_COMMAND_ID,200handler: async (accessor, resource: URI | object) => {201const editorService = accessor.get(IEditorService);202const resources = getMultiSelectedResources(resource, accessor.get(IListService), editorService, accessor.get(IEditorGroupsService), accessor.get(IExplorerService));203204if (resources.length === 2) {205return editorService.openEditor({206original: { resource: resources[0] },207modified: { resource: resources[1] },208options: { pinned: true }209});210}211212return true;213}214});215216CommandsRegistry.registerCommand({217id: COMPARE_RESOURCE_COMMAND_ID,218handler: (accessor, resource: URI | object) => {219const editorService = accessor.get(IEditorService);220const rightResource = getResourceForCommand(resource, editorService, accessor.get(IListService));221if (globalResourceToCompare && rightResource) {222editorService.openEditor({223original: { resource: globalResourceToCompare },224modified: { resource: rightResource },225options: { pinned: true }226});227}228}229});230231async function resourcesToClipboard(resources: URI[], relative: boolean, clipboardService: IClipboardService, labelService: ILabelService, configurationService: IConfigurationService): Promise<void> {232if (resources.length) {233const lineDelimiter = isWindows ? '\r\n' : '\n';234235let separator: '/' | '\\' | undefined = undefined;236const copyRelativeOrFullPathSeparatorSection = relative ? 'explorer.copyRelativePathSeparator' : 'explorer.copyPathSeparator';237const copyRelativeOrFullPathSeparator: '/' | '\\' | undefined = configurationService.getValue(copyRelativeOrFullPathSeparatorSection);238if (copyRelativeOrFullPathSeparator === '/' || copyRelativeOrFullPathSeparator === '\\') {239separator = copyRelativeOrFullPathSeparator;240}241242const text = resources.map(resource => labelService.getUriLabel(resource, { relative, noPrefix: true, separator })).join(lineDelimiter);243await clipboardService.writeText(text);244}245}246247const copyPathCommandHandler: ICommandHandler = async (accessor, resource: URI | object) => {248const resources = getMultiSelectedResources(resource, accessor.get(IListService), accessor.get(IEditorService), accessor.get(IEditorGroupsService), accessor.get(IExplorerService));249await resourcesToClipboard(resources, false, accessor.get(IClipboardService), accessor.get(ILabelService), accessor.get(IConfigurationService));250};251252KeybindingsRegistry.registerCommandAndKeybindingRule({253weight: KeybindingWeight.WorkbenchContrib,254when: EditorContextKeys.focus.toNegated(),255primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KeyC,256win: {257primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KeyC258},259id: COPY_PATH_COMMAND_ID,260handler: copyPathCommandHandler261});262263KeybindingsRegistry.registerCommandAndKeybindingRule({264weight: KeybindingWeight.WorkbenchContrib,265when: EditorContextKeys.focus,266primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KeyC),267win: {268primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KeyC269},270id: COPY_PATH_COMMAND_ID,271handler: copyPathCommandHandler272});273274const copyRelativePathCommandHandler: ICommandHandler = async (accessor, resource: URI | object) => {275const resources = getMultiSelectedResources(resource, accessor.get(IListService), accessor.get(IEditorService), accessor.get(IEditorGroupsService), accessor.get(IExplorerService));276await resourcesToClipboard(resources, true, accessor.get(IClipboardService), accessor.get(ILabelService), accessor.get(IConfigurationService));277};278279KeybindingsRegistry.registerCommandAndKeybindingRule({280weight: KeybindingWeight.WorkbenchContrib,281when: EditorContextKeys.focus.toNegated(),282primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.Alt | KeyCode.KeyC,283win: {284primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyC)285},286id: COPY_RELATIVE_PATH_COMMAND_ID,287handler: copyRelativePathCommandHandler288});289290KeybindingsRegistry.registerCommandAndKeybindingRule({291weight: KeybindingWeight.WorkbenchContrib,292when: EditorContextKeys.focus,293primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.Alt | KeyCode.KeyC),294win: {295primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyC)296},297id: COPY_RELATIVE_PATH_COMMAND_ID,298handler: copyRelativePathCommandHandler299});300301KeybindingsRegistry.registerCommandAndKeybindingRule({302weight: KeybindingWeight.WorkbenchContrib,303when: undefined,304primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyCode.KeyP),305id: 'workbench.action.files.copyPathOfActiveFile',306handler: async accessor => {307const editorService = accessor.get(IEditorService);308const activeInput = editorService.activeEditor;309const resource = EditorResourceAccessor.getOriginalUri(activeInput, { supportSideBySide: SideBySideEditor.PRIMARY });310const resources = resource ? [resource] : [];311await resourcesToClipboard(resources, false, accessor.get(IClipboardService), accessor.get(ILabelService), accessor.get(IConfigurationService));312}313});314315CommandsRegistry.registerCommand({316id: REVEAL_IN_EXPLORER_COMMAND_ID,317handler: async (accessor, resource: URI | object) => {318const viewService = accessor.get(IViewsService);319const contextService = accessor.get(IWorkspaceContextService);320const explorerService = accessor.get(IExplorerService);321const editorService = accessor.get(IEditorService);322const listService = accessor.get(IListService);323const uri = getResourceForCommand(resource, editorService, listService);324325if (uri && contextService.isInsideWorkspace(uri)) {326const explorerView = await viewService.openView<ExplorerView>(VIEW_ID, false);327if (explorerView) {328const oldAutoReveal = explorerView.autoReveal;329// Disable autoreveal before revealing the explorer to prevent a race betwene auto reveal + selection330// Fixes #197268331explorerView.autoReveal = false;332explorerView.setExpanded(true);333await explorerService.select(uri, 'force');334explorerView.focus();335explorerView.autoReveal = oldAutoReveal;336}337} else {338// Do not reveal the open editors view if it's hidden explicitly339// See https://github.com/microsoft/vscode/issues/227378340const openEditorsView = viewService.getViewWithId(OpenEditorsView.ID);341if (openEditorsView) {342openEditorsView.setExpanded(true);343openEditorsView.focus();344}345}346}347});348349CommandsRegistry.registerCommand({350id: OPEN_WITH_EXPLORER_COMMAND_ID,351handler: async (accessor, resource: URI | object) => {352const editorService = accessor.get(IEditorService);353const listService = accessor.get(IListService);354const uri = getResourceForCommand(resource, editorService, listService);355if (uri) {356return editorService.openEditor({ resource: uri, options: { override: EditorResolution.PICK, source: EditorOpenSource.USER } });357}358359return undefined;360}361});362363// Save / Save As / Save All / Revert364365async function saveSelectedEditors(accessor: ServicesAccessor, options?: ISaveEditorsOptions): Promise<void> {366const editorGroupService = accessor.get(IEditorGroupsService);367const codeEditorService = accessor.get(ICodeEditorService);368const textFileService = accessor.get(ITextFileService);369370// Retrieve selected or active editor371let editors = getOpenEditorsViewMultiSelection(accessor);372if (!editors) {373const activeGroup = editorGroupService.activeGroup;374if (activeGroup.activeEditor) {375editors = [];376377// Special treatment for side by side editors: if the active editor378// has 2 sides, we consider both, to support saving both sides.379// We only allow this when saving, not for "Save As" and not if any380// editor is untitled which would bring up a "Save As" dialog too.381// In addition, we require the secondary side to be modified to not382// trigger a touch operation unexpectedly.383//384// See also https://github.com/microsoft/vscode/issues/4180385// See also https://github.com/microsoft/vscode/issues/106330386// See also https://github.com/microsoft/vscode/issues/190210387if (388activeGroup.activeEditor instanceof SideBySideEditorInput &&389!options?.saveAs && !(activeGroup.activeEditor.primary.hasCapability(EditorInputCapabilities.Untitled) || activeGroup.activeEditor.secondary.hasCapability(EditorInputCapabilities.Untitled)) &&390activeGroup.activeEditor.secondary.isModified()391) {392editors.push({ groupId: activeGroup.id, editor: activeGroup.activeEditor.primary });393editors.push({ groupId: activeGroup.id, editor: activeGroup.activeEditor.secondary });394} else {395editors.push({ groupId: activeGroup.id, editor: activeGroup.activeEditor });396}397}398}399400if (!editors || editors.length === 0) {401return; // nothing to save402}403404// Save editors405await doSaveEditors(accessor, editors, options);406407// Special treatment for embedded editors: if we detect that focus is408// inside an embedded code editor, we save that model as well if we409// find it in our text file models. Currently, only textual editors410// support embedded editors.411const focusedCodeEditor = codeEditorService.getFocusedCodeEditor();412if (focusedCodeEditor instanceof EmbeddedCodeEditorWidget && !focusedCodeEditor.isSimpleWidget) {413const resource = focusedCodeEditor.getModel()?.uri;414415// Check that the resource of the model was not saved already416if (resource && !editors.some(({ editor }) => isEqual(EditorResourceAccessor.getCanonicalUri(editor, { supportSideBySide: SideBySideEditor.PRIMARY }), resource))) {417const model = textFileService.files.get(resource);418if (!model?.isReadonly()) {419await textFileService.save(resource, options);420}421}422}423}424425function saveDirtyEditorsOfGroups(accessor: ServicesAccessor, groups: readonly IEditorGroup[], options?: ISaveEditorsOptions): Promise<void> {426const dirtyEditors: IEditorIdentifier[] = [];427for (const group of groups) {428for (const editor of group.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE)) {429if (editor.isDirty()) {430dirtyEditors.push({ groupId: group.id, editor });431}432}433}434435return doSaveEditors(accessor, dirtyEditors, options);436}437438async function doSaveEditors(accessor: ServicesAccessor, editors: IEditorIdentifier[], options?: ISaveEditorsOptions): Promise<void> {439const editorService = accessor.get(IEditorService);440const notificationService = accessor.get(INotificationService);441const instantiationService = accessor.get(IInstantiationService);442443try {444await editorService.save(editors, options);445} catch (error) {446if (!isCancellationError(error)) {447const actions: IAction[] = [toAction({ id: 'workbench.action.files.saveEditors', label: nls.localize('retry', "Retry"), run: () => instantiationService.invokeFunction(accessor => doSaveEditors(accessor, editors, options)) })];448const editorsToRevert = editors.filter(({ editor }) => !editor.hasCapability(EditorInputCapabilities.Untitled) /* all except untitled to prevent unexpected data-loss */);449if (editorsToRevert.length > 0) {450actions.push(toAction({ id: 'workbench.action.files.revertEditors', label: editorsToRevert.length > 1 ? nls.localize('revertAll', "Revert All") : nls.localize('revert', "Revert"), run: () => editorService.revert(editorsToRevert) }));451}452453notificationService.notify({454id: editors.map(({ editor }) => hash(editor.resource?.toString())).join(), // ensure unique notification ID per set of editor455severity: Severity.Error,456message: nls.localize({ key: 'genericSaveError', comment: ['{0} is the resource that failed to save and {1} the error message'] }, "Failed to save '{0}': {1}", editors.map(({ editor }) => editor.getName()).join(', '), toErrorMessage(error, false)),457actions: { primary: actions }458});459}460}461}462463KeybindingsRegistry.registerCommandAndKeybindingRule({464when: undefined,465weight: KeybindingWeight.WorkbenchContrib,466primary: KeyMod.CtrlCmd | KeyCode.KeyS,467id: SAVE_FILE_COMMAND_ID,468handler: accessor => {469return saveSelectedEditors(accessor, { reason: SaveReason.EXPLICIT, force: true /* force save even when non-dirty */ });470}471});472473KeybindingsRegistry.registerCommandAndKeybindingRule({474when: undefined,475weight: KeybindingWeight.WorkbenchContrib,476primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyCode.KeyS),477win: { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyS) },478id: SAVE_FILE_WITHOUT_FORMATTING_COMMAND_ID,479handler: accessor => {480return saveSelectedEditors(accessor, { reason: SaveReason.EXPLICIT, force: true /* force save even when non-dirty */, skipSaveParticipants: true });481}482});483484KeybindingsRegistry.registerCommandAndKeybindingRule({485id: SAVE_FILE_AS_COMMAND_ID,486weight: KeybindingWeight.WorkbenchContrib,487when: undefined,488primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyS,489handler: accessor => {490return saveSelectedEditors(accessor, { reason: SaveReason.EXPLICIT, saveAs: true });491}492});493494KeybindingsRegistry.registerCommandAndKeybindingRule({495when: undefined,496weight: KeybindingWeight.WorkbenchContrib,497primary: undefined,498mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KeyS },499win: { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyCode.KeyS) },500id: SAVE_ALL_COMMAND_ID,501handler: accessor => {502return saveDirtyEditorsOfGroups(accessor, accessor.get(IEditorGroupsService).getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE), { reason: SaveReason.EXPLICIT });503}504});505506CommandsRegistry.registerCommand({507id: SAVE_ALL_IN_GROUP_COMMAND_ID,508handler: (accessor, _: URI | object, editorContext: IEditorCommandsContext) => {509const editorGroupsService = accessor.get(IEditorGroupsService);510511const resolvedContext = resolveCommandsContext([editorContext], accessor.get(IEditorService), editorGroupsService, accessor.get(IListService));512513let groups: readonly IEditorGroup[] | undefined = undefined;514if (!resolvedContext.groupedEditors.length) {515groups = editorGroupsService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE);516} else {517groups = resolvedContext.groupedEditors.map(({ group }) => group);518}519520return saveDirtyEditorsOfGroups(accessor, groups, { reason: SaveReason.EXPLICIT });521}522});523524CommandsRegistry.registerCommand({525id: SAVE_FILES_COMMAND_ID,526handler: async accessor => {527const editorService = accessor.get(IEditorService);528529const res = await editorService.saveAll({ includeUntitled: false, reason: SaveReason.EXPLICIT });530return res.success;531}532});533534CommandsRegistry.registerCommand({535id: REVERT_FILE_COMMAND_ID,536handler: async accessor => {537const editorGroupService = accessor.get(IEditorGroupsService);538const editorService = accessor.get(IEditorService);539540// Retrieve selected or active editor541let editors = getOpenEditorsViewMultiSelection(accessor);542if (!editors) {543const activeGroup = editorGroupService.activeGroup;544if (activeGroup.activeEditor) {545editors = [{ groupId: activeGroup.id, editor: activeGroup.activeEditor }];546}547}548549if (!editors || editors.length === 0) {550return; // nothing to revert551}552553try {554await editorService.revert(editors.filter(({ editor }) => !editor.hasCapability(EditorInputCapabilities.Untitled) /* all except untitled */), { force: true });555} catch (error) {556const notificationService = accessor.get(INotificationService);557notificationService.error(nls.localize('genericRevertError', "Failed to revert '{0}': {1}", editors.map(({ editor }) => editor.getName()).join(', '), toErrorMessage(error, false)));558}559}560});561562CommandsRegistry.registerCommand({563id: REMOVE_ROOT_FOLDER_COMMAND_ID,564handler: (accessor, resource: URI | object) => {565const contextService = accessor.get(IWorkspaceContextService);566const uriIdentityService = accessor.get(IUriIdentityService);567const workspace = contextService.getWorkspace();568const resources = getMultiSelectedResources(resource, accessor.get(IListService), accessor.get(IEditorService), accessor.get(IEditorGroupsService), accessor.get(IExplorerService)).filter(resource =>569workspace.folders.some(folder => uriIdentityService.extUri.isEqual(folder.uri, resource)) // Need to verify resources are workspaces since multi selection can trigger this command on some non workspace resources570);571572if (resources.length === 0) {573const commandService = accessor.get(ICommandService);574// Show a picker for the user to choose which folder to remove575return commandService.executeCommand(RemoveRootFolderAction.ID);576}577578const workspaceEditingService = accessor.get(IWorkspaceEditingService);579return workspaceEditingService.removeFolders(resources);580}581});582583// Compressed item navigation584585KeybindingsRegistry.registerCommandAndKeybindingRule({586weight: KeybindingWeight.WorkbenchContrib + 10,587when: ContextKeyExpr.and(FilesExplorerFocusCondition, ExplorerCompressedFocusContext, ExplorerCompressedFirstFocusContext.negate()),588primary: KeyCode.LeftArrow,589id: PREVIOUS_COMPRESSED_FOLDER,590handler: accessor => {591const paneCompositeService = accessor.get(IPaneCompositePartService);592const viewlet = paneCompositeService.getActivePaneComposite(ViewContainerLocation.Sidebar);593594if (viewlet?.getId() !== VIEWLET_ID) {595return;596}597598const explorer = viewlet.getViewPaneContainer() as ExplorerViewPaneContainer;599const view = explorer.getExplorerView();600view.previousCompressedStat();601}602});603604KeybindingsRegistry.registerCommandAndKeybindingRule({605weight: KeybindingWeight.WorkbenchContrib + 10,606when: ContextKeyExpr.and(FilesExplorerFocusCondition, ExplorerCompressedFocusContext, ExplorerCompressedLastFocusContext.negate()),607primary: KeyCode.RightArrow,608id: NEXT_COMPRESSED_FOLDER,609handler: accessor => {610const paneCompositeService = accessor.get(IPaneCompositePartService);611const viewlet = paneCompositeService.getActivePaneComposite(ViewContainerLocation.Sidebar);612613if (viewlet?.getId() !== VIEWLET_ID) {614return;615}616617const explorer = viewlet.getViewPaneContainer() as ExplorerViewPaneContainer;618const view = explorer.getExplorerView();619view.nextCompressedStat();620}621});622623KeybindingsRegistry.registerCommandAndKeybindingRule({624weight: KeybindingWeight.WorkbenchContrib + 10,625when: ContextKeyExpr.and(FilesExplorerFocusCondition, ExplorerCompressedFocusContext, ExplorerCompressedFirstFocusContext.negate()),626primary: KeyCode.Home,627id: FIRST_COMPRESSED_FOLDER,628handler: accessor => {629const paneCompositeService = accessor.get(IPaneCompositePartService);630const viewlet = paneCompositeService.getActivePaneComposite(ViewContainerLocation.Sidebar);631632if (viewlet?.getId() !== VIEWLET_ID) {633return;634}635636const explorer = viewlet.getViewPaneContainer() as ExplorerViewPaneContainer;637const view = explorer.getExplorerView();638view.firstCompressedStat();639}640});641642KeybindingsRegistry.registerCommandAndKeybindingRule({643weight: KeybindingWeight.WorkbenchContrib + 10,644when: ContextKeyExpr.and(FilesExplorerFocusCondition, ExplorerCompressedFocusContext, ExplorerCompressedLastFocusContext.negate()),645primary: KeyCode.End,646id: LAST_COMPRESSED_FOLDER,647handler: accessor => {648const paneCompositeService = accessor.get(IPaneCompositePartService);649const viewlet = paneCompositeService.getActivePaneComposite(ViewContainerLocation.Sidebar);650651if (viewlet?.getId() !== VIEWLET_ID) {652return;653}654655const explorer = viewlet.getViewPaneContainer() as ExplorerViewPaneContainer;656const view = explorer.getExplorerView();657view.lastCompressedStat();658}659});660661KeybindingsRegistry.registerCommandAndKeybindingRule({662weight: KeybindingWeight.WorkbenchContrib,663when: null,664primary: isWeb ? (isWindows ? KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyCode.KeyN) : KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KeyN) : KeyMod.CtrlCmd | KeyCode.KeyN,665secondary: isWeb ? [KeyMod.CtrlCmd | KeyCode.KeyN] : undefined,666id: NEW_UNTITLED_FILE_COMMAND_ID,667metadata: {668description: NEW_UNTITLED_FILE_LABEL,669args: [670{671isOptional: true,672name: 'New Untitled Text File arguments',673description: 'The editor view type or language ID if known',674schema: {675'type': 'object',676'properties': {677'viewType': {678'type': 'string'679},680'languageId': {681'type': 'string'682}683}684}685}686]687},688handler: async (accessor, args?: { languageId?: string; viewType?: string }) => {689const editorService = accessor.get(IEditorService);690691await editorService.openEditor({692resource: undefined,693options: {694override: args?.viewType,695pinned: true696},697languageId: args?.languageId,698});699}700});701702CommandsRegistry.registerCommand({703id: NEW_FILE_COMMAND_ID,704handler: async (accessor, args?: { languageId?: string; viewType?: string; fileName?: string }) => {705const editorService = accessor.get(IEditorService);706const dialogService = accessor.get(IFileDialogService);707const fileService = accessor.get(IFileService);708709const createFileLocalized = nls.localize('newFileCommand.saveLabel', "Create File");710const defaultFileUri = joinPath(await dialogService.defaultFilePath(), args?.fileName ?? 'Untitled.txt');711712const saveUri = await dialogService.showSaveDialog({ saveLabel: createFileLocalized, title: createFileLocalized, defaultUri: defaultFileUri });713714if (!saveUri) {715return;716}717718await fileService.createFile(saveUri, undefined, { overwrite: true });719720await editorService.openEditor({721resource: saveUri,722options: {723override: args?.viewType,724pinned: true725},726languageId: args?.languageId,727});728}729});730731732