diff --git a/src/vs/workbench/browser/parts/editor/editor.contribution.ts b/src/vs/workbench/browser/parts/editor/editor.contribution.ts index 432513e41571b1..dfb20dc68fb6ba 100644 --- a/src/vs/workbench/browser/parts/editor/editor.contribution.ts +++ b/src/vs/workbench/browser/parts/editor/editor.contribution.ts @@ -11,7 +11,7 @@ import { TextCompareEditorActiveContext, ActiveEditorPinnedContext, EditorGroupEditorsCountContext, ActiveEditorStickyContext, ActiveEditorAvailableEditorIdsContext, EditorPartMultipleEditorGroupsContext, ActiveEditorDirtyContext, ActiveEditorGroupLockedContext, ActiveEditorCanSplitInGroupContext, SideBySideEditorActiveContext, EditorTabsVisibleContext, ActiveEditorLastInGroupContext, EditorPartMaximizedEditorGroupContext, MultipleEditorGroupsContext, InEditorZenModeContext, - IsAuxiliaryEditorPartContext, ActiveCompareEditorCanSwapContext, MultipleEditorsSelectedContext + IsAuxiliaryEditorPartContext, ActiveCompareEditorCanSwapContext, MultipleEditorsSelectedInGroupContext } from 'vs/workbench/common/contextkeys'; import { SideBySideEditorInput, SideBySideEditorInputSerializer } from 'vs/workbench/common/editor/sideBySideEditorInput'; import { TextResourceEditor } from 'vs/workbench/browser/parts/editor/textResourceEditor'; @@ -388,7 +388,7 @@ MenuRegistry.appendMenuItem(MenuId.EditorActionsPositionSubmenu, { command: { id // Editor Title Context Menu MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: CLOSE_EDITOR_COMMAND_ID, title: localize('close', "Close") }, group: '1_close', order: 10 }); MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: CLOSE_OTHER_EDITORS_IN_GROUP_COMMAND_ID, title: localize('closeOthers', "Close Others"), precondition: EditorGroupEditorsCountContext.notEqualsTo('1') }, group: '1_close', order: 20 }); -MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: CLOSE_EDITORS_TO_THE_RIGHT_COMMAND_ID, title: localize('closeRight', "Close to the Right"), precondition: ContextKeyExpr.and(ActiveEditorLastInGroupContext.toNegated(), MultipleEditorsSelectedContext.negate()) }, group: '1_close', order: 30, when: EditorTabsVisibleContext }); +MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: CLOSE_EDITORS_TO_THE_RIGHT_COMMAND_ID, title: localize('closeRight', "Close to the Right"), precondition: ContextKeyExpr.and(ActiveEditorLastInGroupContext.toNegated(), MultipleEditorsSelectedInGroupContext.negate()) }, group: '1_close', order: 30, when: EditorTabsVisibleContext }); MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: CLOSE_SAVED_EDITORS_COMMAND_ID, title: localize('closeAllSaved', "Close Saved") }, group: '1_close', order: 40 }); MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: CLOSE_EDITORS_IN_GROUP_COMMAND_ID, title: localize('closeAll', "Close All") }, group: '1_close', order: 50 }); MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: REOPEN_WITH_COMMAND_ID, title: localize('reopenWith', "Reopen Editor With...") }, group: '1_open', order: 10, when: ActiveEditorAvailableEditorIdsContext }); @@ -399,11 +399,11 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: SPLIT_ED MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: SPLIT_EDITOR_DOWN, title: localize('splitDown', "Split Down") }, group: '5_split', order: 20 }); MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: SPLIT_EDITOR_LEFT, title: localize('splitLeft', "Split Left") }, group: '5_split', order: 30 }); MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: SPLIT_EDITOR_RIGHT, title: localize('splitRight', "Split Right") }, group: '5_split', order: 40 }); -MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: SPLIT_EDITOR_IN_GROUP, title: localize('splitInGroup', "Split in Group"), precondition: MultipleEditorsSelectedContext.negate() }, group: '6_split_in_group', order: 10, when: ActiveEditorCanSplitInGroupContext }); -MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: JOIN_EDITOR_IN_GROUP, title: localize('joinInGroup', "Join in Group"), precondition: MultipleEditorsSelectedContext.negate() }, group: '6_split_in_group', order: 10, when: SideBySideEditorActiveContext }); +MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: SPLIT_EDITOR_IN_GROUP, title: localize('splitInGroup', "Split in Group"), precondition: MultipleEditorsSelectedInGroupContext.negate() }, group: '6_split_in_group', order: 10, when: ActiveEditorCanSplitInGroupContext }); +MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: JOIN_EDITOR_IN_GROUP, title: localize('joinInGroup', "Join in Group"), precondition: MultipleEditorsSelectedInGroupContext.negate() }, group: '6_split_in_group', order: 10, when: SideBySideEditorActiveContext }); MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: MOVE_EDITOR_INTO_NEW_WINDOW_COMMAND_ID, title: localize('moveToNewWindow', "Move into New Window") }, group: '7_new_window', order: 10 }); MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: COPY_EDITOR_INTO_NEW_WINDOW_COMMAND_ID, title: localize('copyToNewWindow', "Copy into New Window") }, group: '7_new_window', order: 20 }); -MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { submenu: MenuId.EditorTitleContextShare, title: localize('share', "Share"), group: '11_share', order: -1, when: MultipleEditorsSelectedContext.negate() }); +MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { submenu: MenuId.EditorTitleContextShare, title: localize('share', "Share"), group: '11_share', order: -1, when: MultipleEditorsSelectedInGroupContext.negate() }); // Editor Title Menu MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: TOGGLE_DIFF_SIDE_BY_SIDE, title: localize('inlineView', "Inline View"), toggled: ContextKeyExpr.equals('config.diffEditor.renderSideBySide', false) }, group: '1_diff', order: 10, when: ContextKeyExpr.has('isInDiffEditor') }); diff --git a/src/vs/workbench/browser/parts/editor/editor.ts b/src/vs/workbench/browser/parts/editor/editor.ts index 1bf1de4322072e..138541a6ecfb71 100644 --- a/src/vs/workbench/browser/parts/editor/editor.ts +++ b/src/vs/workbench/browser/parts/editor/editor.ts @@ -340,9 +340,9 @@ export interface IInternalEditorOpenOptions extends IInternalEditorTitleControlO readonly preserveWindowOrder?: boolean; /** - * Whether to add the editor to the selection or not. + * Inactive editors to select after opening the active selected editor. */ - readonly selected?: boolean; + readonly inactiveSelection?: EditorInput[]; } export interface IInternalEditorCloseOptions extends IInternalEditorTitleControlOptions { diff --git a/src/vs/workbench/browser/parts/editor/editorActions.ts b/src/vs/workbench/browser/parts/editor/editorActions.ts index 4ffd4ae70497c2..b4665e6d8b2804 100644 --- a/src/vs/workbench/browser/parts/editor/editorActions.ts +++ b/src/vs/workbench/browser/parts/editor/editorActions.ts @@ -13,7 +13,7 @@ import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/bro import { GoFilter, IHistoryService } from 'vs/workbench/services/history/common/history'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ICommandService } from 'vs/platform/commands/common/commands'; -import { CLOSE_EDITOR_COMMAND_ID, MOVE_ACTIVE_EDITOR_COMMAND_ID, ActiveEditorMoveCopyArguments, SPLIT_EDITOR_LEFT, SPLIT_EDITOR_RIGHT, SPLIT_EDITOR_UP, SPLIT_EDITOR_DOWN, splitEditor, LAYOUT_EDITOR_GROUPS_COMMAND_ID, UNPIN_EDITOR_COMMAND_ID, COPY_ACTIVE_EDITOR_COMMAND_ID, SPLIT_EDITOR, resolveCommandsContext, getCommandsContext, TOGGLE_MAXIMIZE_EDITOR_GROUP, MOVE_EDITOR_INTO_NEW_WINDOW_COMMAND_ID, COPY_EDITOR_INTO_NEW_WINDOW_COMMAND_ID, MOVE_EDITOR_GROUP_INTO_NEW_WINDOW_COMMAND_ID, COPY_EDITOR_GROUP_INTO_NEW_WINDOW_COMMAND_ID, NEW_EMPTY_EDITOR_WINDOW_COMMAND_ID as NEW_EMPTY_EDITOR_WINDOW_COMMAND_ID, getEditorsFromContext } from 'vs/workbench/browser/parts/editor/editorCommands'; +import { CLOSE_EDITOR_COMMAND_ID, MOVE_ACTIVE_EDITOR_COMMAND_ID, ActiveEditorMoveCopyArguments, SPLIT_EDITOR_LEFT, SPLIT_EDITOR_RIGHT, SPLIT_EDITOR_UP, SPLIT_EDITOR_DOWN, splitEditor, LAYOUT_EDITOR_GROUPS_COMMAND_ID, UNPIN_EDITOR_COMMAND_ID, COPY_ACTIVE_EDITOR_COMMAND_ID, SPLIT_EDITOR, resolveCommandsContext, getCommandsContext, TOGGLE_MAXIMIZE_EDITOR_GROUP, MOVE_EDITOR_INTO_NEW_WINDOW_COMMAND_ID, COPY_EDITOR_INTO_NEW_WINDOW_COMMAND_ID, MOVE_EDITOR_GROUP_INTO_NEW_WINDOW_COMMAND_ID, COPY_EDITOR_GROUP_INTO_NEW_WINDOW_COMMAND_ID, NEW_EMPTY_EDITOR_WINDOW_COMMAND_ID as NEW_EMPTY_EDITOR_WINDOW_COMMAND_ID, resolveEditorsContext, getEditorsContext } from 'vs/workbench/browser/parts/editor/editorCommands'; import { IEditorGroupsService, IEditorGroup, GroupsArrangement, GroupLocation, GroupDirection, preferredSideBySideGroupDirection, IFindGroupScope, GroupOrientation, EditorGroupLayout, GroupsOrder, MergeGroupMode } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -65,7 +65,8 @@ abstract class AbstractSplitEditorAction extends Action2 { const editorGroupService = accessor.get(IEditorGroupsService); const configurationService = accessor.get(IConfigurationService); - splitEditor(editorGroupService, this.getDirection(configurationService), [getCommandsContext(accessor, resourceOrContext, context)]); + const commandContext = getCommandsContext(accessor, resourceOrContext, context); + splitEditor(editorGroupService, this.getDirection(configurationService), commandContext ? [commandContext] : undefined); } } @@ -2514,23 +2515,19 @@ abstract class BaseMoveCopyEditorToNewWindowAction extends Action2 { override async run(accessor: ServicesAccessor, resourceOrContext?: URI | IEditorCommandsContext, context?: IEditorCommandsContext) { const editorGroupService = accessor.get(IEditorGroupsService); - const editors = getEditorsFromContext(accessor, resourceOrContext, context); - - // If there is no editor, do not create a new window - if (editors.length === 0) { + const editorsContext = resolveEditorsContext(getEditorsContext(accessor, resourceOrContext, context)); + if (editorsContext.length === 0) { return; } const auxiliaryEditorPart = await editorGroupService.createAuxiliaryEditorPart(); - for (const { editor, group } of editors) { - if (group && editor) { - if (this.move) { - group.moveEditor(editor, auxiliaryEditorPart.activeGroup); - } else { - group.copyEditor(editor, auxiliaryEditorPart.activeGroup); - } - } + const sourceGroup = editorsContext[0].group; // only single group supported for move/copy for now + const sourceEditors = editorsContext.filter(({ group }) => group === sourceGroup); + if (this.move) { + sourceGroup.moveEditors(sourceEditors, auxiliaryEditorPart.activeGroup); + } else { + sourceGroup.copyEditors(sourceEditors, auxiliaryEditorPart.activeGroup); } auxiliaryEditorPart.activeGroup.focus(); diff --git a/src/vs/workbench/browser/parts/editor/editorCommands.ts b/src/vs/workbench/browser/parts/editor/editorCommands.ts index 848096c73581e5..c8670de6fb066e 100644 --- a/src/vs/workbench/browser/parts/editor/editorCommands.ts +++ b/src/vs/workbench/browser/parts/editor/editorCommands.ts @@ -31,7 +31,7 @@ import { ActiveGroupEditorsByMostRecentlyUsedQuickAccess } from 'vs/workbench/br import { SideBySideEditor } from 'vs/workbench/browser/parts/editor/sideBySideEditor'; import { TextDiffEditor } from 'vs/workbench/browser/parts/editor/textDiffEditor'; import { ActiveEditorCanSplitInGroupContext, ActiveEditorGroupEmptyContext, ActiveEditorGroupLockedContext, ActiveEditorStickyContext, MultipleEditorGroupsContext, SideBySideEditorActiveContext, TextCompareEditorActiveContext } from 'vs/workbench/common/contextkeys'; -import { CloseDirection, EditorInputCapabilities, EditorsOrder, IEditorCommandsContext, IEditorIdentifier, IResourceDiffEditorInput, IUntitledTextResourceEditorInput, IVisibleEditorPane, isEditorCommandsContext, isEditorIdentifier, isEditorInputWithOptionsAndGroup } from 'vs/workbench/common/editor'; +import { CloseDirection, EditorInputCapabilities, EditorsOrder, IEditorCommandsContext, IEditorIdentifier, IResourceDiffEditorInput, IUntitledTextResourceEditorInput, IVisibleEditorPane, isEditorIdentifier, isEditorInputWithOptionsAndGroup } from 'vs/workbench/common/editor'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { SideBySideEditorInput } from 'vs/workbench/common/editor/sideBySideEditorInput'; @@ -656,48 +656,45 @@ function registerFocusEditorGroupAtIndexCommands(): void { } } -export function splitEditor(editorGroupService: IEditorGroupsService, direction: GroupDirection, contexts?: (IEditorCommandsContext | URI | undefined)[]): void { +export function splitEditor(editorGroupService: IEditorGroupsService, direction: GroupDirection, contexts?: IEditorCommandsContext[]): void { let newGroup: IEditorGroup | undefined; + let sourceGroup: IEditorGroup | undefined; for (const context of contexts ?? [undefined]) { - let sourceGroup: IEditorGroup | undefined; + let currentGroup: IEditorGroup | undefined; - const isEditorCommand = context && isEditorCommandsContext(context); - const isURI = context && URI.isUri(context); - - if (isEditorCommand) { - sourceGroup = editorGroupService.getGroup(context.groupId); - } else if (isURI) { - for (const group of editorGroupService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE)) { - if (isEqual(group.activeEditor?.resource, context)) { - sourceGroup = group; - break; - } - } + if (context) { + currentGroup = editorGroupService.getGroup(context.groupId); } else { - sourceGroup = editorGroupService.activeGroup; + currentGroup = editorGroupService.activeGroup; + } + + if (!currentGroup) { + continue; } if (!sourceGroup) { - return; + sourceGroup = currentGroup; + } else if (sourceGroup.id !== currentGroup.id) { + continue; // Only support splitting from the same group } // Add group if (!newGroup) { - newGroup = editorGroupService.addGroup(sourceGroup, direction); + newGroup = editorGroupService.addGroup(currentGroup, direction); } // Split editor (if it can be split) let editorToCopy: EditorInput | undefined; - if (isEditorCommand && typeof context.editorIndex === 'number') { - editorToCopy = sourceGroup.getEditorByIndex(context.editorIndex); + if (context && typeof context.editorIndex === 'number') { + editorToCopy = currentGroup.getEditorByIndex(context.editorIndex); } else { - editorToCopy = sourceGroup.activeEditor ?? undefined; + editorToCopy = currentGroup.activeEditor ?? undefined; } // Copy the editor to the new group, else create an empty group if (editorToCopy && !editorToCopy.hasCapability(EditorInputCapabilities.Singleton)) { - sourceGroup.copyEditor(editorToCopy, newGroup, { preserveFocus: isEditorCommand && context?.preserveFocus }); + currentGroup.copyEditor(editorToCopy, newGroup, { preserveFocus: context?.preserveFocus }); } } @@ -897,7 +894,7 @@ function registerCloseEditorCommands() { const editorResolverService = accessor.get(IEditorResolverService); const telemetryService = accessor.get(ITelemetryService); - const editorsAndGroup = getEditorsFromContext(accessor, resourceOrContext, context); + const editorsAndGroup = resolveEditorsContext(getEditorsContext(accessor, resourceOrContext, context)); const editorReplacements = new Map(); for (const { editor, group } of editorsAndGroup) { @@ -912,11 +909,13 @@ function registerCloseEditorCommands() { return; } - if (!editorReplacements.has(group)) { - editorReplacements.set(group, []); + let editorReplacementsInGroup = editorReplacements.get(group); + if (!editorReplacementsInGroup) { + editorReplacementsInGroup = []; + editorReplacements.set(group, editorReplacementsInGroup); } - editorReplacements.get(group)?.push({ + editorReplacementsInGroup.push({ editor: editor, replacement: resolvedEditor.editor, forceReplaceDirty: editor.resource?.scheme === Schemas.untitled, @@ -948,18 +947,11 @@ function registerCloseEditorCommands() { }); } - // Replace editor with resolved one - let group: IEditorGroup | undefined, replacements: IEditorReplacement[] | undefined; - for ([group, replacements] of editorReplacements) { + // Replace editor with resolved one and make active + for (const [group, replacements] of editorReplacements) { await group.replaceEditors(replacements); + await group.openEditor(replacements[0].replacement); } - - if (!group || !replacements) { - return; - } - - // Make sure it becomes active too - await group?.openEditor(replacements[0].replacement); } }); @@ -1301,7 +1293,7 @@ function registerOtherEditorCommands(): void { when: ActiveEditorStickyContext.toNegated(), primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.Shift | KeyCode.Enter), handler: async (accessor, resourceOrContext?: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => { - for (const { editor, group } of getEditorsFromContext(accessor, resourceOrContext, context)) { + for (const { editor, group } of resolveEditorsContext(getEditorsContext(accessor, resourceOrContext, context))) { group.stickEditor(editor); } } @@ -1340,7 +1332,7 @@ function registerOtherEditorCommands(): void { when: ActiveEditorStickyContext, primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.Shift | KeyCode.Enter), handler: async (accessor, resourceOrContext?: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => { - for (const { editor, group } of getEditorsFromContext(accessor, resourceOrContext, context)) { + for (const { editor, group } of resolveEditorsContext(getEditorsContext(accessor, resourceOrContext, context))) { group.unstickEditor(editor); } } @@ -1368,7 +1360,8 @@ function registerOtherEditorCommands(): void { }); } -function getEditorsContext(accessor: ServicesAccessor, resourceOrContext?: URI | IEditorCommandsContext, context?: IEditorCommandsContext): { editors: IEditorCommandsContext[]; groups: Array } { +type EditorsContext = { editors: IEditorCommandsContext[]; groups: Array }; +export function getEditorsContext(accessor: ServicesAccessor, resourceOrContext?: URI | IEditorCommandsContext, context?: IEditorCommandsContext): EditorsContext { const editorGroupService = accessor.get(IEditorGroupsService); const listService = accessor.get(IListService); @@ -1389,8 +1382,8 @@ function getEditorsContext(accessor: ServicesAccessor, resourceOrContext?: URI | }; } -export function getEditorsFromContext(accessor: ServicesAccessor, resourceOrContext?: URI | IEditorCommandsContext, context?: IEditorCommandsContext): { editor: EditorInput; group: IEditorGroup }[] { - const { editors, groups } = getEditorsContext(accessor, resourceOrContext, context); +export function resolveEditorsContext(context: EditorsContext): { editor: EditorInput; group: IEditorGroup }[] { + const { editors, groups } = context; const editorsAndGroup = editors.map(e => { if (e.editorIndex === undefined) { @@ -1404,14 +1397,14 @@ export function getEditorsFromContext(accessor: ServicesAccessor, resourceOrCont return { editor, group }; }); - return editorsAndGroup.filter(group => !!group); + return coalesce(editorsAndGroup); } export function getCommandsContext(accessor: ServicesAccessor, resourceOrContext?: URI | IEditorCommandsContext, context?: IEditorCommandsContext): IEditorCommandsContext | undefined { const isUri = URI.isUri(resourceOrContext); const editorCommandsContext = isUri ? context : resourceOrContext ? resourceOrContext : context; - if (editorCommandsContext && typeof editorCommandsContext.groupId === 'number') { + if (editorCommandsContext) { return editorCommandsContext; } @@ -1478,19 +1471,11 @@ export function getMultiSelectedEditorContexts(editorContext: IEditorCommandsCon // Check editors selected in the group (tabs) else { const group = editorContext ? editorGroupService.getGroup(editorContext.groupId) : editorGroupService.activeGroup; - if (group) { - const selectedEditors: EditorInput[] = []; - // If context provides an editor index, use it - if (editorContext && editorContext.editorIndex !== undefined) { - const editor = group?.getEditorByIndex(editorContext.editorIndex); - if (editor && group.isSelected(editor)) { - selectedEditors.push(...group.selectedEditors); - } - } else { - selectedEditors.push(...group.selectedEditors); - } - if (selectedEditors.length > 1) { - return selectedEditors.map(se => ({ groupId: group.id, editorIndex: group.getIndexOfEditor(se) })); + const editor = editorContext && editorContext.editorIndex !== undefined ? group?.getEditorByIndex(editorContext.editorIndex) : group?.activeEditor; + // If the editor is selected, return all selected editors otherwise only use the editors context + if (group && editor) { + if (group.isSelected(editor)) { + return group.selectedEditors.map(se => ({ groupId: group.id, editorIndex: group.getIndexOfEditor(se) })); } } } diff --git a/src/vs/workbench/browser/parts/editor/editorDropTarget.ts b/src/vs/workbench/browser/parts/editor/editorDropTarget.ts index 002d565c6cd186..463b527e3b667b 100644 --- a/src/vs/workbench/browser/parts/editor/editorDropTarget.ts +++ b/src/vs/workbench/browser/parts/editor/editorDropTarget.ts @@ -333,8 +333,8 @@ class DropOverlay extends Themable { { editor: draggedEditor.identifier.editor, options: fillActiveEditorViewState(sourceGroup, draggedEditor.identifier.editor, { - pinned: true, // always pin dropped editor - sticky: sourceGroup.isSticky(firstDraggedEditor.editor) // preserve sticky state + pinned: true, // always pin dropped editor + sticky: sourceGroup.isSticky(draggedEditor.identifier.editor) // preserve sticky state }) } )); diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index 330c90fd1ae849..14beefacad6855 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -6,7 +6,7 @@ import 'vs/css!./media/editorgroupview'; import { EditorGroupModel, IEditorOpenOptions, IGroupModelChangeEvent, ISerializedEditorGroupModel, isGroupEditorCloseEvent, isGroupEditorOpenEvent, isSerializedEditorGroupModel } from 'vs/workbench/common/editor/editorGroupModel'; import { GroupIdentifier, CloseDirection, IEditorCloseEvent, IEditorPane, SaveReason, IEditorPartOptionsChangeEvent, EditorsOrder, IVisibleEditorPane, EditorResourceAccessor, EditorInputCapabilities, IUntypedEditorInput, DEFAULT_EDITOR_ASSOCIATION, SideBySideEditor, EditorCloseContext, IEditorWillMoveEvent, IEditorWillOpenEvent, IMatchEditorOptions, GroupModelChangeKind, IActiveEditorChangeEvent, IFindEditorOptions, IToolbarActions, TEXT_DIFF_EDITOR_ID } from 'vs/workbench/common/editor'; -import { ActiveEditorGroupLockedContext, ActiveEditorDirtyContext, EditorGroupEditorsCountContext, ActiveEditorStickyContext, ActiveEditorPinnedContext, ActiveEditorLastInGroupContext, ActiveEditorFirstInGroupContext, ResourceContextKey, applyAvailableEditorIds, ActiveEditorAvailableEditorIdsContext, ActiveEditorCanSplitInGroupContext, SideBySideEditorActiveContext, MultipleEditorsSelectedContext, TwoEditorsSelectedContext, TextCompareEditorVisibleContext, TextCompareEditorActiveContext, ActiveEditorContext, ActiveEditorReadonlyContext, ActiveEditorCanRevertContext, ActiveEditorCanToggleReadonlyContext, ActiveCompareEditorCanSwapContext } from 'vs/workbench/common/contextkeys'; +import { ActiveEditorGroupLockedContext, ActiveEditorDirtyContext, EditorGroupEditorsCountContext, ActiveEditorStickyContext, ActiveEditorPinnedContext, ActiveEditorLastInGroupContext, ActiveEditorFirstInGroupContext, ResourceContextKey, applyAvailableEditorIds, ActiveEditorAvailableEditorIdsContext, ActiveEditorCanSplitInGroupContext, SideBySideEditorActiveContext, TextCompareEditorVisibleContext, TextCompareEditorActiveContext, ActiveEditorContext, ActiveEditorReadonlyContext, ActiveEditorCanRevertContext, ActiveEditorCanToggleReadonlyContext, ActiveCompareEditorCanSwapContext, MultipleEditorsSelectedInGroupContext, TwoEditorsSelectedInGroupContext } from 'vs/workbench/common/contextkeys'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { SideBySideEditorInput } from 'vs/workbench/common/editor/sideBySideEditorInput'; import { Emitter, Relay } from 'vs/base/common/event'; @@ -256,8 +256,8 @@ export class EditorGroupView extends Themable implements IEditorGroupView { const groupEditorsCountContext = this.editorPartsView.bind(EditorGroupEditorsCountContext, this); const groupLockedContext = this.editorPartsView.bind(ActiveEditorGroupLockedContext, this); - const multipleEditorsSelectedContext = MultipleEditorsSelectedContext.bindTo(this.scopedContextKeyService); - const twoEditorsSelectedContext = TwoEditorsSelectedContext.bindTo(this.scopedContextKeyService); + const multipleEditorsSelectedContext = MultipleEditorsSelectedInGroupContext.bindTo(this.scopedContextKeyService); + const twoEditorsSelectedContext = TwoEditorsSelectedInGroupContext.bindTo(this.scopedContextKeyService); const groupActiveEditorContext = this.editorPartsView.bind(ActiveEditorContext, this); const groupActiveEditorIsReadonly = this.editorPartsView.bind(ActiveEditorReadonlyContext, this); @@ -349,7 +349,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { groupActiveEditorStickyContext.set(this.model.isSticky(this.model.activeEditor)); } break; - case GroupModelChangeKind.EDITOR_SELECTION: + case GroupModelChangeKind.EDITORS_SELECTION: multipleEditorsSelectedContext.set(this.model.selectedEditors.length > 1); twoEditorsSelectedContext.set(this.model.selectedEditors.length === 2); break; @@ -599,8 +599,13 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // Handle within - if (e.kind === GroupModelChangeKind.GROUP_LOCKED) { - this.element.classList.toggle('locked', this.isLocked); + switch (e.kind) { + case GroupModelChangeKind.GROUP_LOCKED: + this.element.classList.toggle('locked', this.isLocked); + break; + case GroupModelChangeKind.EDITORS_SELECTION: + this.onDidChangeEditorSelection(); + break; } if (!e.editor) { @@ -630,9 +635,6 @@ export class EditorGroupView extends Themable implements IEditorGroupView { case GroupModelChangeKind.EDITOR_LABEL: this.onDidChangeEditorLabel(e.editor); break; - case GroupModelChangeKind.EDITOR_SELECTION: - this.onDidChangeEditorSelection(e.editor); - break; } } @@ -863,10 +865,10 @@ export class EditorGroupView extends Themable implements IEditorGroupView { this.titleControl.updateEditorLabel(editor); } - private onDidChangeEditorSelection(editor: EditorInput): void { + private onDidChangeEditorSelection(): void { // Forward to title control - this.titleControl.setEditorSelections([editor], this.model.isSelected(editor)); + this.titleControl.updateEditorSelections(); } private onDidVisibilityChange(visible: boolean): void { @@ -941,6 +943,11 @@ export class EditorGroupView extends Themable implements IEditorGroupView { setActive(isActive: boolean): void { this.active = isActive; + // Clear selection when group no longer active + if (!isActive && this.activeEditor && this.selectedEditors.length > 1) { + this.setSelection(this.activeEditor, []); + } + // Update container this.element.classList.toggle('active', isActive); this.element.classList.toggle('inactive', !isActive); @@ -1015,55 +1022,14 @@ export class EditorGroupView extends Themable implements IEditorGroupView { return this.model.isActive(editor); } - async selectEditor(editor: EditorInput, active?: boolean): Promise { - if (active) { - await this.doOpenEditor(editor, { activation: EditorActivation.ACTIVATE }, { selected: true }); + async setSelection(activeSelectedEditor: EditorInput, inactiveSelectedEditors: EditorInput[]): Promise { + if (!this.isActive(activeSelectedEditor)) { + // The active selected editor is not yet opened, so we go + // through `openEditor` to show it. We pass the inactive + // selection as internal options + await this.openEditor(activeSelectedEditor, { activation: EditorActivation.ACTIVATE }, { inactiveSelection: inactiveSelectedEditors }); } else { - this.model.selectEditor(editor, active); - } - } - - async selectEditors(editors: EditorInput[], activeEditor?: EditorInput): Promise { - for (const editor of editors) { - await this.selectEditor(editor, editor === activeEditor); - } - } - - async unSelectEditor(editor: EditorInput): Promise { - await this.unSelectEditors([editor]); - } - - async unSelectEditors(editors: EditorInput[]): Promise { - // Check if the active editor is unselected - const unselectingActiveEditor = !!editors.find(editor => this.model.isActive(editor)); - if (unselectingActiveEditor) { - editors = editors.filter(editor => !this.model.isActive(editor)); - } - - // Unselect all none active editors - for (const editor of editors) { - this.model.unselectEditor(editor); - } - - // if the active editor is unselected, make another selected editor active - if (unselectingActiveEditor) { - // do not allow to unselect the active editor if it is the last selected editor - if (this.selectedEditors.length === 1) { - console.warn('Cannot unselect the last selected editor of a group'); - return; - } - - const activeEditor = this.activeEditor; - // Find the next selected editor to make active based on MRU order - const recentEditors = this.model.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE); - for (let i = 1; i < recentEditors.length; i++) { // First one is the active editor - const recentEditor = recentEditors[i]; - if (this.isSelected(recentEditor)) { - await this.doOpenEditor(recentEditor, { activation: EditorActivation.ACTIVATE }, { selected: true }); - this.model.unselectEditor(activeEditor!); - break; - } - } + this.model.setSelection(activeSelectedEditor, inactiveSelectedEditors); } } @@ -1217,7 +1183,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { pinned, sticky: options?.sticky || (typeof options?.index === 'number' && this.model.isSticky(options.index)), transient: !!options?.transient, - selected: internalOptions?.selected, + inactiveSelection: internalOptions?.inactiveSelection, active: this.count === 0 || !options || !options.inactive, supportSideBySide: internalOptions?.supportSideBySide }; diff --git a/src/vs/workbench/browser/parts/editor/editorTabsControl.ts b/src/vs/workbench/browser/parts/editor/editorTabsControl.ts index e006ebd9040b01..e5e4f7774de4ad 100644 --- a/src/vs/workbench/browser/parts/editor/editorTabsControl.ts +++ b/src/vs/workbench/browser/parts/editor/editorTabsControl.ts @@ -86,7 +86,7 @@ export interface IEditorTabsControl extends IDisposable { stickEditor(editor: EditorInput): void; unstickEditor(editor: EditorInput): void; setActive(isActive: boolean): void; - setEditorSelections(editors: EditorInput[], selected: boolean): void; + updateEditorSelections(): void; updateEditorLabel(editor: EditorInput): void; updateEditorDirty(editor: EditorInput): void; layout(dimensions: IEditorTitleControlDimensions): Dimension; @@ -503,7 +503,7 @@ export abstract class EditorTabsControl extends Themable implements IEditorTabsC abstract setActive(isActive: boolean): void; - abstract setEditorSelections(editors: EditorInput[], selected: boolean): void; + abstract updateEditorSelections(): void; abstract updateEditorLabel(editor: EditorInput): void; diff --git a/src/vs/workbench/browser/parts/editor/editorTitleControl.ts b/src/vs/workbench/browser/parts/editor/editorTitleControl.ts index e24e15ed611dc2..d134e9b6175c6d 100644 --- a/src/vs/workbench/browser/parts/editor/editorTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/editorTitleControl.ts @@ -163,8 +163,8 @@ export class EditorTitleControl extends Themable { return this.editorTabsControl.setActive(isActive); } - setEditorSelections(editors: EditorInput[], selected: boolean): void { - this.editorTabsControl.setEditorSelections(editors, selected); + updateEditorSelections(): void { + this.editorTabsControl.updateEditorSelections(); } updateEditorLabel(editor: EditorInput): void { diff --git a/src/vs/workbench/browser/parts/editor/media/editortabscontrol.css b/src/vs/workbench/browser/parts/editor/media/editortabscontrol.css index bf44c02aee2354..24de0a1f3846df 100644 --- a/src/vs/workbench/browser/parts/editor/media/editortabscontrol.css +++ b/src/vs/workbench/browser/parts/editor/media/editortabscontrol.css @@ -46,4 +46,12 @@ border-radius: 10px; font-size: 12px; position: absolute; + /* + * Browsers apply an effect to the drag image when the div becomes too + * large which makes them unreadable. Use max width so it does not happen + */ + max-width: 120px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } diff --git a/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts b/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts index aa6a35fffcdec6..a54b4a431d3514 100644 --- a/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts +++ b/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts @@ -27,12 +27,12 @@ import { ScrollbarVisibility } from 'vs/base/common/scrollable'; import { getOrSet } from 'vs/base/common/map'; import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { TAB_INACTIVE_BACKGROUND, TAB_ACTIVE_BACKGROUND, TAB_BORDER, EDITOR_DRAG_AND_DROP_BACKGROUND, TAB_UNFOCUSED_ACTIVE_BACKGROUND, TAB_UNFOCUSED_ACTIVE_BORDER, TAB_ACTIVE_BORDER, TAB_HOVER_BACKGROUND, TAB_HOVER_BORDER, TAB_UNFOCUSED_HOVER_BACKGROUND, TAB_UNFOCUSED_HOVER_BORDER, EDITOR_GROUP_HEADER_TABS_BACKGROUND, WORKBENCH_BACKGROUND, TAB_ACTIVE_BORDER_TOP, TAB_UNFOCUSED_ACTIVE_BORDER_TOP, TAB_ACTIVE_MODIFIED_BORDER, TAB_INACTIVE_MODIFIED_BORDER, TAB_UNFOCUSED_ACTIVE_MODIFIED_BORDER, TAB_UNFOCUSED_INACTIVE_MODIFIED_BORDER, TAB_UNFOCUSED_INACTIVE_BACKGROUND, TAB_HOVER_FOREGROUND, TAB_UNFOCUSED_HOVER_FOREGROUND, EDITOR_GROUP_HEADER_TABS_BORDER, TAB_LAST_PINNED_BORDER, TAB_SELECTED_BORDER_TOP } from 'vs/workbench/common/theme'; -import { activeContrastBorder, contrastBorder, editorBackground } from 'vs/platform/theme/common/colorRegistry'; +import { activeContrastBorder, contrastBorder, editorBackground, listActiveSelectionBackground, listActiveSelectionForeground } from 'vs/platform/theme/common/colorRegistry'; import { ResourcesDropHandler, DraggedEditorIdentifier, DraggedEditorGroupIdentifier, extractTreeDropData, isWindowDraggedOver } from 'vs/workbench/browser/dnd'; import { Color } from 'vs/base/common/color'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { MergeGroupMode, IMergeGroupOptions } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { addDisposableListener, EventType, EventHelper, Dimension, scheduleAtNextAnimationFrame, findParentWithClass, clearNode, DragAndDropObserver, isMouseEvent, getWindow, $, getActiveDocument } from 'vs/base/browser/dom'; +import { addDisposableListener, EventType, EventHelper, Dimension, scheduleAtNextAnimationFrame, findParentWithClass, clearNode, DragAndDropObserver, isMouseEvent, getWindow } from 'vs/base/browser/dom'; import { localize } from 'vs/nls'; import { IEditorGroupsView, EditorServiceImpl, IEditorGroupView, IInternalEditorOpenOptions, IEditorPartsView } from 'vs/workbench/browser/parts/editor/editor'; import { CloseOneEditorAction, UnpinEditorAction } from 'vs/workbench/browser/parts/editor/editorActions'; @@ -57,6 +57,7 @@ import { StickyEditorGroupModel, UnstickyEditorGroupModel } from 'vs/workbench/c import { IReadonlyEditorGroupModel } from 'vs/workbench/common/editor/editorGroupModel'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { BugIndicatingError } from 'vs/base/common/errors'; +import { applyDragImage } from 'vs/base/browser/dnd'; interface IEditorInputLabel { readonly editor: EditorInput; @@ -683,7 +684,7 @@ export class MultiEditorTabsControl extends EditorTabsControl { this.layout(this.dimensions, { forceRevealActiveTab: true }); } - setEditorSelections(editors: EditorInput[], selected: boolean): void { + updateEditorSelections(): void { this.forEachTab((editor, tabIndex, tabContainer, tabLabelWidget, tabLabel, tabActionBar) => { this.redrawTabSelectedActiveAndDirty(this.groupsView.activeGroup === this.groupView, editor, tabContainer, tabActionBar); }); @@ -865,11 +866,11 @@ export class MultiEditorTabsControl extends EditorTabsControl { return this.groupView.getIndexOfEditor(editor); } - private lastSelectedEditor: EditorInput | undefined; + private lastSingleSelectSelectedEditor: EditorInput | undefined; private registerTabListeners(tab: HTMLElement, tabIndex: number, tabsContainer: HTMLElement, tabsScrollbar: ScrollableElement): IDisposable { const disposables = new DisposableStore(); - const handleClickOrTouch = (e: MouseEvent | GestureEvent, preserveFocus: boolean): void => { + const handleClickOrTouch = async (e: MouseEvent | GestureEvent, preserveFocus: boolean): Promise => { tab.blur(); // prevent flicker of focus outline on tab until editor got focus if (isMouseEvent(e) && (e.button !== 0 /* middle/right mouse button */ || (isMacintosh && e.ctrlKey /* macOS context menu */))) { @@ -877,7 +878,7 @@ export class MultiEditorTabsControl extends EditorTabsControl { e.preventDefault(); // required to prevent auto-scrolling (https://github.com/microsoft/vscode/issues/16690) } - return undefined; + return; } if (this.originatesFromTabActionBar(e)) { @@ -889,29 +890,31 @@ export class MultiEditorTabsControl extends EditorTabsControl { if (editor) { if (e.shiftKey) { let anchor; - if (this.lastSelectedEditor && this.groupView.isSelected(this.lastSelectedEditor)) { + if (this.lastSingleSelectSelectedEditor && this.tabsModel.isSelected(this.lastSingleSelectSelectedEditor)) { // The last selected editor is the anchor - anchor = this.lastSelectedEditor; + anchor = this.lastSingleSelectSelectedEditor; } else { // The active editor is the anchor - this.lastSelectedEditor = this.groupView.activeEditor!; + this.lastSingleSelectSelectedEditor = this.groupView.activeEditor!; anchor = this.groupView.activeEditor!; } - this.selectEditorsBetween(editor, anchor); + await this.selectEditorsBetween(editor, anchor); } else if ((e.ctrlKey && !isMacintosh) || (e.metaKey && isMacintosh)) { - if (this.groupView.isSelected(editor)) { - this.groupView.unSelectEditor(editor); + if (this.tabsModel.isSelected(editor)) { + await this.unselectEditor(editor); } else { - this.groupView.selectEditor(editor, true); - this.lastSelectedEditor = editor; + await this.selectEditor(editor); + this.lastSingleSelectSelectedEditor = editor; } } else { // Even if focus is preserved make sure to activate the group. - this.groupView.openEditor(editor, { preserveFocus, activation: EditorActivation.ACTIVATE }, { selected: this.groupView.isSelected(editor) /* Ensures drag and drop does not remove selection */ }); + // If a new active editor is selected, keep the current selection on key + // down such that drag and drop can operate over the selection. The selection + // is removed on key up in this case. + const inactiveSelection = this.tabsModel.isSelected(editor) ? this.groupView.selectedEditors.filter(e => !e.matches(editor)) : []; + await this.groupView.openEditor(editor, { preserveFocus, activation: EditorActivation.ACTIVATE }, { inactiveSelection, focusTabControl: true }); } } - - return undefined; }; const showContextMenu = (e: Event) => { @@ -932,26 +935,23 @@ export class MultiEditorTabsControl extends EditorTabsControl { tabsScrollbar.setScrollPosition({ scrollLeft: tabsScrollbar.getScrollPosition().scrollLeft - e.translationX }); })); - // Prevent flicker of focus outline on tab until editor got focus - disposables.add(addDisposableListener(tab, EventType.MOUSE_UP, e => { + // Update selection & prevent flicker of focus outline on tab until editor got focus + disposables.add(addDisposableListener(tab, EventType.MOUSE_UP, async e => { EventHelper.stop(e); tab.blur(); if (isMouseEvent(e) && (e.button !== 0 /* middle/right mouse button */ || (isMacintosh && e.ctrlKey /* macOS context menu */))) { - if (e.button === 1) { - e.preventDefault(); // required to prevent auto-scrolling (https://github.com/microsoft/vscode/issues/16690) - } - - return undefined; + return; } if (this.originatesFromTabActionBar(e)) { return; // not when clicking on actions } + const isCtrlCmd = (e.ctrlKey && !isMacintosh) || (e.metaKey && isMacintosh); if (!isCtrlCmd && !e.shiftKey && this.groupView.selectedEditors.length > 1) { - this.unselectAllEditors(); + await this.unselectAllEditors(); } })); @@ -1078,23 +1078,14 @@ export class MultiEditorTabsControl extends EditorTabsControl { } isNewWindowOperation = this.isNewWindowOperation(e); - - const draggedEditors = []; const selectedEditors = this.groupView.selectedEditors; - const isMultiSelected = this.groupView.isSelected(editor) && selectedEditors.length > 1; - if (isMultiSelected) { - draggedEditors.push(...selectedEditors); - } else { - draggedEditors.push(editor); - } - - this.editorTransfer.setData(draggedEditors.map(e => new DraggedEditorIdentifier({ editor: e, groupId: this.groupView.id })), DraggedEditorIdentifier.prototype); + this.editorTransfer.setData(selectedEditors.map(e => new DraggedEditorIdentifier({ editor: e, groupId: this.groupView.id })), DraggedEditorIdentifier.prototype); if (e.dataTransfer) { e.dataTransfer.effectAllowed = 'copyMove'; - if (isMultiSelected) { - const label = `${editor.getName()} + ${draggedEditors.length - 1}`; - setupMultiselectDragLabel(label, e.dataTransfer, tab); + if (selectedEditors.length > 1) { + const label = `${editor.getName()} + ${selectedEditors.length - 1}`; + applyDragImage(e, label, 'monaco-editor-group-drag-image', this.getColor(listActiveSelectionBackground), this.getColor(listActiveSelectionForeground)); } else { e.dataTransfer.setDragImage(tab, 0, 0); // top left corner of dragged tab set to cursor position to make room for drop-border feedback } @@ -1163,7 +1154,7 @@ export class MultiEditorTabsControl extends EditorTabsControl { } const targetGroup = auxiliaryEditorPart.activeGroup; - const editors = draggedEditors.map(de => ({ editor: de.identifier.editor, options: {} })); + const editors = draggedEditors.map(de => ({ editor: de.identifier.editor })); if (this.isMoveOperation(lastDragEvent ?? e, targetGroup.id, draggedEditors[0].identifier.editor)) { this.groupView.moveEditors(editors, targetGroup); } else { @@ -1192,7 +1183,7 @@ export class MultiEditorTabsControl extends EditorTabsControl { private isSupportedDropTransfer(e: DragEvent): boolean { if (this.groupTransfer.hasData(DraggedEditorGroupIdentifier.prototype)) { const data = this.groupTransfer.getData(DraggedEditorGroupIdentifier.prototype); - if (Array.isArray(data)) { + if (Array.isArray(data) && data.length > 0) { const group = data[0]; if (group.identifier === this.groupView.id) { return false; // groups cannot be dropped on group it originates from @@ -1282,7 +1273,15 @@ export class MultiEditorTabsControl extends EditorTabsControl { return { leftElement: tabBefore as HTMLElement, rightElement: tabAfter as HTMLElement }; } - private selectEditorsBetween(target: EditorInput, anchor: EditorInput): void { + private async selectEditor(editor: EditorInput): Promise { + if (this.groupView.isActive(editor)) { + return; + } + + await this.groupView.setSelection(editor, this.groupView.selectedEditors); + } + + private async selectEditorsBetween(target: EditorInput, anchor: EditorInput): Promise { const editorIndex = this.groupView.getIndexOfEditor(target); if (editorIndex === -1) { throw new BugIndicatingError(); @@ -1293,29 +1292,71 @@ export class MultiEditorTabsControl extends EditorTabsControl { throw new BugIndicatingError(); } + const selection = this.groupView.selectedEditors; + // Unselect editors on other side of anchor in relation to the target let currentIndex = anchorIndex; while (currentIndex >= 0 && currentIndex <= this.groupView.count - 1) { currentIndex = anchorIndex < editorIndex ? currentIndex - 1 : currentIndex + 1; + if (!this.tabsModel.isSelected(currentIndex)) { + break; + } + const currentEditor = this.groupView.getEditorByIndex(currentIndex); - if (!currentEditor || !this.groupView.isSelected(currentEditor)) { + if (!currentEditor) { break; } - this.groupView.unSelectEditor(currentEditor); + selection.filter(editor => !editor.matches(currentEditor)); } // Select editors between anchor and target const fromIndex = anchorIndex < editorIndex ? anchorIndex : editorIndex; const toIndex = anchorIndex < editorIndex ? editorIndex : anchorIndex; - const selectedEditors = this.groupView.getEditors(EditorsOrder.SEQUENTIAL).slice(fromIndex, toIndex + 1); - this.groupView.selectEditors(selectedEditors, target); + const editorsToSelect = this.groupView.getEditors(EditorsOrder.SEQUENTIAL).slice(fromIndex, toIndex + 1); + for (const editor of editorsToSelect) { + if (!this.tabsModel.isSelected(editor)) { + selection.push(editor); + } + } + + const inactiveSelectedEditors = selection.filter(editor => !editor.matches(target)); + await this.groupView.setSelection(target, inactiveSelectedEditors); } - private unselectAllEditors(): void { - this.groupView.unSelectEditors(this.groupView.selectedEditors.filter(editor => !this.groupView.isActive(editor))); + private async unselectEditor(editor: EditorInput): Promise { + const isUnselectingActiveEditor = this.groupView.isActive(editor); + + // If there is only one editor selected, do not unselect it + if (isUnselectingActiveEditor && this.groupView.selectedEditors.length === 1) { + return; + } + + let newActiveEditor = this.groupView.activeEditor!; + + // If active editor is bing unselected then find the most recently opened selected editor + // that is not the editor being unselected + if (isUnselectingActiveEditor) { + const recentEditors = this.groupView.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE); + for (let i = 1; i < recentEditors.length; i++) { // First one is the active editor + const recentEditor = recentEditors[i]; + if (this.tabsModel.isSelected(recentEditor)) { + newActiveEditor = recentEditor; + break; + } + } + } + + const inactiveSelectedEditors = this.groupView.selectedEditors.filter(e => !e.matches(editor) && !e.matches(newActiveEditor)); + await this.groupView.setSelection(newActiveEditor, inactiveSelectedEditors); + } + + private async unselectAllEditors(): Promise { + if (this.groupView.selectedEditors.length > 1) { + await this.groupView.setSelection(this.groupView.activeEditor!, []); + } } private computeTabLabels(): void { @@ -1604,7 +1645,7 @@ export class MultiEditorTabsControl extends EditorTabsControl { private doRedrawTabActive(isGroupActive: boolean, allowBorderTop: boolean, editor: EditorInput, tabContainer: HTMLElement, tabActionBar: ActionBar): void { const isActive = this.tabsModel.isActive(editor); - const isSelected = this.groupView.isSelected(editor); // TODO, move to model + const isSelected = this.tabsModel.isSelected(editor); tabContainer.classList.toggle('active', isActive); tabContainer.classList.toggle('selected', isSelected); @@ -1620,19 +1661,19 @@ export class MultiEditorTabsControl extends EditorTabsControl { } // Set border TOP if theme defined color - let color: string | null = null; + let tabBorderColorTop: string | null = null; if (allowBorderTop) { if (isActive) { - color = this.getColor(isGroupActive ? TAB_ACTIVE_BORDER_TOP : TAB_UNFOCUSED_ACTIVE_BORDER_TOP); + tabBorderColorTop = this.getColor(isGroupActive ? TAB_ACTIVE_BORDER_TOP : TAB_UNFOCUSED_ACTIVE_BORDER_TOP); } - if (color === null && isSelected) { - color = this.getColor(TAB_SELECTED_BORDER_TOP); + if (tabBorderColorTop === null && isSelected) { + tabBorderColorTop = this.getColor(TAB_SELECTED_BORDER_TOP); } } - tabContainer.classList.toggle('tab-border-top', !!color); - tabContainer.style.setProperty('--tab-border-top-color', color ?? ''); + tabContainer.classList.toggle('tab-border-top', !!tabBorderColorTop); + tabContainer.style.setProperty('--tab-border-top-color', tabBorderColorTop ?? ''); } private doRedrawTabDirty(isGroupActive: boolean, isTabActive: boolean, editor: EditorInput, tabContainer: HTMLElement): boolean { @@ -2158,7 +2199,7 @@ export class MultiEditorTabsControl extends EditorTabsControl { // Check for group transfer if (this.groupTransfer.hasData(DraggedEditorGroupIdentifier.prototype)) { const data = this.groupTransfer.getData(DraggedEditorGroupIdentifier.prototype); - if (Array.isArray(data)) { + if (Array.isArray(data) && data.length > 0) { const sourceGroup = this.editorPartsView.getGroup(data[0].identifier); if (sourceGroup) { const mergeGroupOptions: IMergeGroupOptions = { index: targetEditorIndex }; @@ -2177,18 +2218,21 @@ export class MultiEditorTabsControl extends EditorTabsControl { // Check for editor transfer else if (this.editorTransfer.hasData(DraggedEditorIdentifier.prototype)) { const data = this.editorTransfer.getData(DraggedEditorIdentifier.prototype); - if (Array.isArray(data)) { + if (Array.isArray(data) && data.length > 0) { + const sourceGroup = data.length ? this.editorPartsView.getGroup(data[0].identifier.groupId) : undefined; + const isLocalMove = sourceGroup === this.groupView; // Keep the same order when moving / copying editors within the same group for (const de of data) { const editor = de.identifier.editor; - const sourceGroup = this.editorPartsView.getGroup(de.identifier.groupId); - if (!sourceGroup) { + + // Only allow moving/copying from a single group source + if (!sourceGroup || sourceGroup.id !== de.identifier.groupId) { continue; } const sourceEditorIndex = sourceGroup.getIndexOfEditor(editor); - if (sourceGroup === this.groupView && sourceEditorIndex < targetEditorIndex) { + if (isLocalMove && sourceEditorIndex < targetEditorIndex) { targetEditorIndex--; } @@ -2209,7 +2253,7 @@ export class MultiEditorTabsControl extends EditorTabsControl { // Check for tree items else if (this.treeItemsTransfer.hasData(DraggedTreeItemsIdentifier.prototype)) { const data = this.treeItemsTransfer.getData(DraggedTreeItemsIdentifier.prototype); - if (Array.isArray(data)) { + if (Array.isArray(data) && data.length > 0) { const editors: IUntypedEditorInput[] = []; for (const id of data) { const dataTransferItem = await this.treeViewsDragAndDropService.removeDragOperationTransfer(id.identifier); @@ -2239,22 +2283,6 @@ export class MultiEditorTabsControl extends EditorTabsControl { } } -function setupMultiselectDragLabel(text: string, dataTransfer: DataTransfer, tab: HTMLElement) { - const dragImage = $('.monaco-drag-image'); - dragImage.textContent = text; - const getDragImageContainer = (e: HTMLElement | null) => { - while (e && !e.classList.contains('monaco-workbench')) { - e = e.parentElement; - } - return e || getActiveDocument().body; - }; - - const container = getDragImageContainer(tab); - container.appendChild(dragImage); - dataTransfer.setDragImage(dragImage, -10, -10); - setTimeout(() => container.removeChild(dragImage), 0); -} - registerThemingParticipant((theme, collector) => { // Add bottom border to tabs when wrapping diff --git a/src/vs/workbench/browser/parts/editor/multiRowEditorTabsControl.ts b/src/vs/workbench/browser/parts/editor/multiRowEditorTabsControl.ts index 50683f16fe0c16..908b7d85cfb801 100644 --- a/src/vs/workbench/browser/parts/editor/multiRowEditorTabsControl.ts +++ b/src/vs/workbench/browser/parts/editor/multiRowEditorTabsControl.ts @@ -163,12 +163,9 @@ export class MultiRowEditorControl extends Disposable implements IEditorTabsCont this.unstickyEditorTabsControl.setActive(isActive); } - setEditorSelections(editors: EditorInput[], selected: boolean): void { - const stickyEditors = editors.filter(e => this.model.isSticky(e)); - const unstickyEditors = editors.filter(e => !this.model.isSticky(e)); - - this.stickyEditorTabsControl.setEditorSelections(stickyEditors, selected); - this.unstickyEditorTabsControl.setEditorSelections(unstickyEditors, selected); + updateEditorSelections(): void { + this.stickyEditorTabsControl.updateEditorSelections(); + this.unstickyEditorTabsControl.updateEditorSelections(); } updateEditorLabel(editor: EditorInput): void { diff --git a/src/vs/workbench/browser/parts/editor/noEditorTabsControl.ts b/src/vs/workbench/browser/parts/editor/noEditorTabsControl.ts index 36a8ac0f0c2650..6a0859cd3ec48d 100644 --- a/src/vs/workbench/browser/parts/editor/noEditorTabsControl.ts +++ b/src/vs/workbench/browser/parts/editor/noEditorTabsControl.ts @@ -61,7 +61,7 @@ export class NoEditorTabsControl extends EditorTabsControl { setActive(isActive: boolean): void { } - setEditorSelections(editors: EditorInput[], selected: boolean): void { } + updateEditorSelections(): void { } updateEditorLabel(editor: EditorInput): void { } diff --git a/src/vs/workbench/browser/parts/editor/singleEditorTabsControl.ts b/src/vs/workbench/browser/parts/editor/singleEditorTabsControl.ts index 4b4e48163df0dc..b6b3dd436ac344 100644 --- a/src/vs/workbench/browser/parts/editor/singleEditorTabsControl.ts +++ b/src/vs/workbench/browser/parts/editor/singleEditorTabsControl.ts @@ -187,7 +187,7 @@ export class SingleEditorTabsControl extends EditorTabsControl { this.redraw(); } - setEditorSelections(editors: EditorInput[], selected: boolean): void { } + updateEditorSelections(): void { } updateEditorLabel(editor: EditorInput): void { this.ifEditorIsActive(editor, () => this.redraw()); diff --git a/src/vs/workbench/common/contextkeys.ts b/src/vs/workbench/common/contextkeys.ts index 63780b3830e872..97937218bbb36e 100644 --- a/src/vs/workbench/common/contextkeys.ts +++ b/src/vs/workbench/common/contextkeys.ts @@ -72,8 +72,8 @@ export const ActiveEditorGroupLastContext = new RawContextKey('activeEd export const ActiveEditorGroupLockedContext = new RawContextKey('activeEditorGroupLocked', false, localize('activeEditorGroupLocked', "Whether the active editor group is locked")); export const MultipleEditorGroupsContext = new RawContextKey('multipleEditorGroups', false, localize('multipleEditorGroups', "Whether there are multiple editor groups opened")); export const SingleEditorGroupsContext = MultipleEditorGroupsContext.toNegated(); -export const MultipleEditorsSelectedContext = new RawContextKey('multipleEditorsSelected', false, localize('multipleEditorsSelected', "Whether multiple editors have been selected")); -export const TwoEditorsSelectedContext = new RawContextKey('twoEditorsSelected', false, localize('twoEditorsSelected', "Whether two editors have been selected")); +export const MultipleEditorsSelectedInGroupContext = new RawContextKey('multipleEditorsSelectedInGroup', false, localize('multipleEditorsSelectedInGroup', "Whether multiple editors have been selected in an editor group")); +export const TwoEditorsSelectedInGroupContext = new RawContextKey('twoEditorsSelectedInGroup', false, localize('twoEditorsSelectedInGroup', "Whether exactly two editors have been selected in an editor group")); // Editor Part Context Keys export const EditorPartMultipleEditorGroupsContext = new RawContextKey('editorPartMultipleEditorGroups', false, localize('editorPartMultipleEditorGroups', "Whether there are multiple editor groups opened in an editor part")); diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index 91af41524fe82a..02aff158f4fee6 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -1168,6 +1168,9 @@ export const enum GroupModelChangeKind { GROUP_LABEL, GROUP_LOCKED, + /* Editors Change */ + EDITORS_SELECTION, + /* Editor Changes */ EDITOR_OPEN, EDITOR_CLOSE, @@ -1177,7 +1180,6 @@ export const enum GroupModelChangeKind { EDITOR_CAPABILITIES, EDITOR_PIN, EDITOR_TRANSIENT, - EDITOR_SELECTION, EDITOR_STICKY, EDITOR_DIRTY, EDITOR_WILL_DISPOSE diff --git a/src/vs/workbench/common/editor/editorGroupModel.ts b/src/vs/workbench/common/editor/editorGroupModel.ts index 146224c838a8ea..d8b7d956adc2df 100644 --- a/src/vs/workbench/common/editor/editorGroupModel.ts +++ b/src/vs/workbench/common/editor/editorGroupModel.ts @@ -25,7 +25,7 @@ export interface IEditorOpenOptions { readonly sticky?: boolean; readonly transient?: boolean; active?: boolean; - readonly selected?: boolean; + readonly inactiveSelection?: EditorInput[]; readonly index?: number; readonly supportSideBySide?: SideBySideEditor.ANY | SideBySideEditor.BOTH; } @@ -196,8 +196,7 @@ interface IEditorGroupModel extends IReadonlyEditorGroupModel { closeEditor(editor: EditorInput, context?: EditorCloseContext, openNext?: boolean): IEditorCloseResult | undefined; moveEditor(editor: EditorInput, toIndex: number): EditorInput | undefined; setActive(editor: EditorInput | undefined): EditorInput | undefined; - selectEditor(editor: EditorInput, active?: boolean): EditorInput | undefined; - unselectEditor(editor: EditorInput): EditorInput | undefined; + setSelection(activeSelectedEditor: EditorInput, inactiveSelectedEditors: EditorInput[]): void; } export class EditorGroupModel extends Disposable implements IEditorGroupModel { @@ -221,13 +220,13 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel { private locked = false; - private selected: EditorInput[] = []; // editors in selected state, first one is active + private selection: EditorInput[] = []; // editors in selected state, first one is active private set active(editor: EditorInput | null) { - this.selected = editor ? [editor] : []; + this.selection = editor ? [editor] : []; } private get active(): EditorInput | null { - return this.selected[0] ?? null; + return this.selection[0] ?? null; } private preview: EditorInput | null = null; // editor in preview state @@ -414,10 +413,8 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel { }; this._onDidModelChange.fire(event); - // Handle active - if (makeActive) { - this.doSetActive(newEditor, targetIndex, options?.selected); - } + // Handle active & selection + this.setSelection(makeActive ? newEditor : this.activeEditor, options?.inactiveSelection ?? []); return { editor: newEditor, @@ -437,10 +434,8 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel { this.doPin(existingEditor, existingEditorIndex); } - // Activate it - if (makeActive) { - this.doSetActive(existingEditor, existingEditorIndex, options?.selected); - } + // Activate / select it + this.setSelection(makeActive ? existingEditor : this.activeEditor, options?.inactiveSelection ?? []); // Respect index if (options && typeof options.index === 'number') { @@ -575,7 +570,8 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel { } } - this.doSetActive(newActive, this.editors.indexOf(newActive)); + const newSelection = this.selection.filter(selected => !selected.matches(newActive) && !selected.matches(editor)); + this.doSetSelection(newActive, this.editors.indexOf(newActive), newSelection); } // One Editor @@ -586,9 +582,10 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel { // Remove from selection else if (!isActiveEditor) { - const wasSelected = !!this.selected.find(selected => this.matches(selected, editor)); + const wasSelected = !!this.selection.find(selected => this.matches(selected, editor)); if (wasSelected) { - this.doSetSelected(editor, index, false); + const newSelection = this.selection.filter(selected => !selected.matches(editor)); + this.doSetSelection(this.activeEditor!, this.indexOf(this.activeEditor), newSelection); } } @@ -693,17 +690,13 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel { return editor; } - private doSetActive(editor: EditorInput, editorIndex: number, selected: boolean = false): void { + private doSetActive(editor: EditorInput, editorIndex: number): void { if (this.matches(this.active, editor)) { + this.selection = [editor]; return; // already active } - if (selected) { - this.selected = this.selected.filter(selected => selected !== editor); - this.selected.unshift(editor); - } else { - this.selected = [editor]; - } + this.active = editor; // Bring to front in MRU list const mruIndex = this.indexOf(editor, this.mru); @@ -717,13 +710,6 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel { editorIndex }; this._onDidModelChange.fire(event); - - const selectionEvent: IGroupEditorChangeEvent = { - kind: GroupModelChangeKind.EDITOR_SELECTION, - editor, - editorIndex - }; - this._onDidModelChange.fire(selectionEvent); } public get selectedEditors(): EditorInput[] { @@ -736,64 +722,33 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel { editor = this.editors[editor]; } - return !!this.selected.find(selectedEditor => this.matches(selectedEditor, editor)); + return !!this.selection.find(selectedEditor => this.matches(selectedEditor, editor)); } - selectEditor(candidate: EditorInput, active: boolean = false): EditorInput | undefined { - const res = this.findEditor(candidate); + setSelection(activeSelectedEditor: EditorInput, inactiveSelectedEditors: EditorInput[]): void { + const res = this.findEditor(activeSelectedEditor); if (!res) { - return; // not found - } - - const [editor, editorIndex] = res; - - this.doSetSelected(editor, editorIndex, true, active); - - return editor; - } - - unselectEditor(candidate: EditorInput): EditorInput | undefined { - const res = this.findEditor(candidate); - if (!res) { - return; // not found + return; } - const [editor, editorIndex] = res; - - this.doSetSelected(editor, editorIndex, false); + const [newActiveEditor, newActiveEditorIndex] = res; - return editor; + this.doSetSelection(newActiveEditor, newActiveEditorIndex, inactiveSelectedEditors); } - private doSetSelected(editor: EditorInput, editorIndex: number, select: boolean, active: boolean = false): void { - if (select) { - if (this.isSelected(editor)) { - return; - } - - if (active) { - this.doSetActive(editor, editorIndex, true); - } else { - this.selected.push(editor); - } - } else { - if (!this.isSelected(editor)) { - return; - } + private doSetSelection(newActiveEditor: EditorInput, activeEditorIndex: number, inactiveSelectedEditors: EditorInput[]): void { + this.doSetActive(newActiveEditor, activeEditorIndex); - if (this.matches(this.active, editor)) { - console.warn('Cannot unselect the active editor'); - return; + for (const candidate of inactiveSelectedEditors) { + const editor = this.findEditor(candidate)?.[0]; + if (editor && !this.isSelected(editor)) { + this.selection.push(editor); } - - this.selected.splice(this.selected.indexOf(editor), 1); } // Event - const event: IGroupEditorChangeEvent = { - kind: GroupModelChangeKind.EDITOR_SELECTION, - editor, - editorIndex + const event: IGroupModelChangeEvent = { + kind: GroupModelChangeKind.EDITORS_SELECTION, }; this._onDidModelChange.fire(event); } diff --git a/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts b/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts index 663156554b5b16..963e5c5692506b 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts @@ -20,7 +20,7 @@ import { CLOSE_SAVED_EDITORS_COMMAND_ID, CLOSE_EDITORS_IN_GROUP_COMMAND_ID, CLOS import { AutoSaveAfterShortDelayContext } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; import { WorkbenchListDoubleSelection } from 'vs/platform/list/browser/listService'; import { Schemas } from 'vs/base/common/network'; -import { DirtyWorkingCopiesContext, EnterMultiRootWorkspaceSupportContext, HasWebFileSystemAccess, WorkbenchStateContext, WorkspaceFolderCountContext, SidebarFocusContext, ActiveEditorCanRevertContext, ActiveEditorContext, ResourceContextKey, ActiveEditorAvailableEditorIdsContext, MultipleEditorsSelectedContext, TwoEditorsSelectedContext } from 'vs/workbench/common/contextkeys'; +import { DirtyWorkingCopiesContext, EnterMultiRootWorkspaceSupportContext, HasWebFileSystemAccess, WorkbenchStateContext, WorkspaceFolderCountContext, SidebarFocusContext, ActiveEditorCanRevertContext, ActiveEditorContext, ResourceContextKey, ActiveEditorAvailableEditorIdsContext, MultipleEditorsSelectedInGroupContext, TwoEditorsSelectedInGroupContext } from 'vs/workbench/common/contextkeys'; import { IsWebContext } from 'vs/platform/contextkey/common/contextkeys'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { ThemeIcon } from 'vs/base/common/themables'; @@ -160,7 +160,7 @@ appendEditorTitleContextMenuItem(COPY_RELATIVE_PATH_COMMAND_ID, copyRelativePath appendEditorTitleContextMenuItem(REVEAL_IN_EXPLORER_COMMAND_ID, nls.localize('revealInSideBar', "Reveal in Explorer View"), ResourceContextKey.IsFileSystemResource, '2_files', false, 1); export function appendEditorTitleContextMenuItem(id: string, title: string, when: ContextKeyExpression | undefined, group: string, supportsMultiSelect: boolean, order?: number): void { - const precondition = supportsMultiSelect !== true ? MultipleEditorsSelectedContext.negate() : undefined; + const precondition = supportsMultiSelect !== true ? MultipleEditorsSelectedInGroupContext.negate() : undefined; // Menu MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { @@ -420,7 +420,7 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { group: '3_compare', order: 30, command: compareSelectedCommand, - when: ContextKeyExpr.and(ResourceContextKey.HasResource, TwoEditorsSelectedContext, isFileOrUntitledResourceContextKey) + when: ContextKeyExpr.and(ResourceContextKey.HasResource, TwoEditorsSelectedInGroupContext, isFileOrUntitledResourceContextKey) }); MenuRegistry.appendMenuItem(MenuId.OpenEditorsContext, { diff --git a/src/vs/workbench/contrib/files/browser/files.ts b/src/vs/workbench/contrib/files/browser/files.ts index 7fc74195991ff6..f1dbd239f4e5db 100644 --- a/src/vs/workbench/contrib/files/browser/files.ts +++ b/src/vs/workbench/contrib/files/browser/files.ts @@ -137,12 +137,14 @@ export function getMultiSelectedResources(resource: URI | object | undefined, li } } + // Check for tabs multiselect. const activeGroup = editorGroupService.activeGroup; const selection = activeGroup.selectedEditors; - if (selection.length) { - const selectedResources = selection.map(editor => EditorResourceAccessor.getOriginalUri(editor)).filter(uri => !!uri) as URI[]; - if (selectedResources.some(r => r.toString() === resource?.toString())) { - return selectedResources; + if (selection.length > 1 && URI.isUri(resource)) { + // If the resource is part of the tabs selection, return all selected tabs/resources. + // It's possible that multiple tabs are selected but the action was applied to a resource that is not part of the selection. + if (selection.some(e => e.matches({ resource }))) { + return selection.map(editor => EditorResourceAccessor.getOriginalUri(editor)).filter(uri => !!uri); } } diff --git a/src/vs/workbench/services/editor/common/editorGroupsService.ts b/src/vs/workbench/services/editor/common/editorGroupsService.ts index 6ecc2b3d333501..8670f77ece2bef 100644 --- a/src/vs/workbench/services/editor/common/editorGroupsService.ts +++ b/src/vs/workbench/services/editor/common/editorGroupsService.ts @@ -774,31 +774,18 @@ export interface IEditorGroup { isActive(editor: EditorInput | IUntypedEditorInput): boolean; /** - * Selects the editor in the group. If active is set to true, - * it will be the active editor in the group. - */ - selectEditor(editor: EditorInput, active?: boolean): Promise; - - /** - * Selects the editors in the group. If activeEditor is provided, - * it will be the active editor in the group. - */ - selectEditors(editors: EditorInput[], activeEditor?: EditorInput): Promise; - - /** - * Unselects the editor in the group. If the editor is not specified, unselects the active editor. - */ - unSelectEditor(editor: EditorInput): Promise; - - /** - * Unselects the editors in the group. If the editor is not specified, unselects the active editor. + * Whether the editor is selected in the group. */ - unSelectEditors(editors: EditorInput[]): Promise; + isSelected(editor: EditorInput): boolean; /** - * Whether the editor is selected in the group. + * Set a new selection for this group. This will replace the current + * selection with the new selection. + * + * @param activeSelectedEditor the editor to set as active selected editor + * @param inactiveSelectedEditors the inactive editors to set as selected */ - isSelected(editor: EditorInput): boolean; + setSelection(activeSelectedEditor: EditorInput, inactiveSelectedEditors: EditorInput[]): Promise; /** * Find out if a certain editor is included in the group. diff --git a/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts b/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts index 56c29a69a4f3fe..f458db5c34f29b 100644 --- a/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts +++ b/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts @@ -1538,7 +1538,7 @@ suite('EditorGroupsService', () => { assert.strictEqual(group.getIndexOfEditor(inputSticky), 0); }); - test('selection: select/unselect, isSelected/getSelectedEditors', async () => { + test('selection: setSelection, isSelected, selectedEditors', async () => { const [part] = await createPart(); const group = part.activeGroup; @@ -1555,9 +1555,9 @@ suite('EditorGroupsService', () => { return inputs.length === group.selectedEditors.length; } + // Active: input1, Selected: input1 await group.openEditors([input1, input2, input3].map(editor => ({ editor, options: { pinned: true } }))); - // Active: input1, Selected: input1 assert.strictEqual(group.isActive(input1), true); assert.strictEqual(group.isSelected(input1), true); assert.strictEqual(group.isSelected(input2), false); @@ -1565,9 +1565,9 @@ suite('EditorGroupsService', () => { assert.strictEqual(isSelection([input1]), true); - await group.selectEditor(input3); - // Active: input1, Selected: input1, input3 + await group.setSelection(input1, [input3]); + assert.strictEqual(group.isActive(input1), true); assert.strictEqual(group.isSelected(input1), true); assert.strictEqual(group.isSelected(input2), false); @@ -1575,9 +1575,9 @@ suite('EditorGroupsService', () => { assert.strictEqual(isSelection([input1, input3]), true); - await group.selectEditor(input2, true); - // Active: input2, Selected: input1, input3 + await group.setSelection(input2, [input1, input3]); + assert.strictEqual(group.isSelected(input1), true); assert.strictEqual(group.isActive(input2), true); assert.strictEqual(group.isSelected(input2), true); @@ -1585,24 +1585,15 @@ suite('EditorGroupsService', () => { assert.strictEqual(isSelection([input1, input2, input3]), true); - await group.unSelectEditor(input2); + await group.setSelection(input1, []); // Selected: input3 assert.strictEqual(group.isActive(input1), true); assert.strictEqual(group.isSelected(input1), true); assert.strictEqual(group.isSelected(input2), false); - assert.strictEqual(group.isSelected(input3), true); - - assert.strictEqual(isSelection([input1, input3]), true); - - await group.unSelectEditors([input1]); - - // Selected: NONE - assert.strictEqual(group.isSelected(input1), false); - assert.strictEqual(group.isSelected(input2), false); - assert.strictEqual(group.isSelected(input3), true); + assert.strictEqual(group.isSelected(input3), false); - assert.strictEqual(isSelection([input3]), true); + assert.strictEqual(isSelection([input1]), true); }); test('moveEditor with context (across groups)', async () => { diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index f6edde59ddbac2..e8c445facde5a8 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -928,10 +928,7 @@ export class TestEditorGroupView implements IEditorGroupView { isSticky(_editor: EditorInput): boolean { return false; } isTransient(_editor: EditorInput): boolean { return false; } isActive(_editor: EditorInput | IUntypedEditorInput): boolean { return false; } - selectEditor(_editor: EditorInput, _active?: boolean): Promise { throw new Error('not implemented'); } - selectEditors(_editors: EditorInput[], _activeEditor?: EditorInput): Promise { throw new Error('not implemented'); } - unSelectEditor(_editor: EditorInput): Promise { throw new Error('not implemented'); } - unSelectEditors(_editors: EditorInput[]): Promise { throw new Error('not implemented'); } + setSelection(_activeSelectedEditor: EditorInput, _inactiveSelectedEditors: EditorInput[]): Promise { throw new Error('not implemented'); } isSelected(_editor: EditorInput): boolean { return false; } contains(candidate: EditorInput | IUntypedEditorInput): boolean { return false; } moveEditor(_editor: EditorInput, _target: IEditorGroup, _options?: IEditorOptions): boolean { return true; }