diff --git a/src/vs/workbench/browser/parts/editor/editor.contribution.ts b/src/vs/workbench/browser/parts/editor/editor.contribution.ts index 37061156a562b4791489b6276c6daeaceae8f4fe..1ac024c3c3f183351d12fbe91669b3b49108c887 100644 --- a/src/vs/workbench/browser/parts/editor/editor.contribution.ts +++ b/src/vs/workbench/browser/parts/editor/editor.contribution.ts @@ -30,7 +30,7 @@ import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { KeyMod, KeyChord, KeyCode } from 'vs/base/common/keyCodes'; import { - CloseEditorsInGroupAction, CloseEditorsInOtherGroupsAction, CloseAllEditorsAction, MoveGroupLeftAction, MoveGroupRightAction, SplitEditorAction, KeepEditorAction, CloseOtherEditorsInGroupAction, OpenToSideAction, RevertAndCloseEditorAction, + CloseEditorsInGroupAction, CloseEditorsInOtherGroupsAction, CloseAllEditorsAction, MoveGroupLeftAction, MoveGroupRightAction, SplitEditorAction, JoinTwoGroupsAction, KeepEditorAction, CloseOtherEditorsInGroupAction, OpenToSideAction, RevertAndCloseEditorAction, NavigateBetweenGroupsAction, FocusActiveGroupAction, FocusFirstGroupAction, FocusSecondGroupAction, FocusThirdGroupAction, EvenGroupWidthsAction, MaximizeGroupAction, MinimizeOtherGroupsAction, FocusPreviousGroup, FocusNextGroup, ShowEditorsInGroupOneAction, toEditorQuickOpenEntry, CloseLeftEditorsInGroupAction, CloseRightEditorsInGroupAction, OpenNextEditor, OpenPreviousEditor, NavigateBackwardsAction, NavigateForwardAction, ReopenClosedEditorAction, OpenPreviousRecentlyUsedEditorInGroupAction, NAVIGATE_IN_GROUP_ONE_PREFIX, OpenPreviousEditorFromHistoryAction, ShowAllEditorsAction, NAVIGATE_ALL_EDITORS_GROUP_PREFIX, ClearEditorHistoryAction, ShowEditorsInGroupTwoAction, MoveEditorRightInGroupAction, OpenNextEditorInGroup, OpenPreviousEditorInGroup, @@ -331,6 +331,7 @@ registry.registerWorkbenchAction(new SyncActionDescriptor(CloseEditorsInGroupAct registry.registerWorkbenchAction(new SyncActionDescriptor(CloseOtherEditorsInGroupAction, CloseOtherEditorsInGroupAction.ID, CloseOtherEditorsInGroupAction.LABEL, { primary: null, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_T } }), 'View: Close Other Editors', category); registry.registerWorkbenchAction(new SyncActionDescriptor(CloseEditorsInOtherGroupsAction, CloseEditorsInOtherGroupsAction.ID, CloseEditorsInOtherGroupsAction.LABEL), 'View: Close Editors in Other Groups', category); registry.registerWorkbenchAction(new SyncActionDescriptor(SplitEditorAction, SplitEditorAction.ID, SplitEditorAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.US_BACKSLASH }), 'View: Split Editor', category); +registry.registerWorkbenchAction(new SyncActionDescriptor(JoinTwoGroupsAction, JoinTwoGroupsAction.ID, JoinTwoGroupsAction.LABEL), 'View: Join Editors of Two Groups', category); registry.registerWorkbenchAction(new SyncActionDescriptor(NavigateBetweenGroupsAction, NavigateBetweenGroupsAction.ID, NavigateBetweenGroupsAction.LABEL), 'View: Navigate Between Editor Groups', category); registry.registerWorkbenchAction(new SyncActionDescriptor(FocusActiveGroupAction, FocusActiveGroupAction.ID, FocusActiveGroupAction.LABEL), 'View: Focus Active Editor Group', category); registry.registerWorkbenchAction(new SyncActionDescriptor(FocusFirstGroupAction, FocusFirstGroupAction.ID, FocusFirstGroupAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_1 }), 'View: Focus First Editor Group', category); diff --git a/src/vs/workbench/browser/parts/editor/editorActions.ts b/src/vs/workbench/browser/parts/editor/editorActions.ts index c36ccc0d608ce3f5b0480babac0e6a49d22430fd..40d0d470dde598c5eda9b8e9c94ebb5fab5de67a 100644 --- a/src/vs/workbench/browser/parts/editor/editorActions.ts +++ b/src/vs/workbench/browser/parts/editor/editorActions.ts @@ -106,6 +106,71 @@ export class SplitEditorAction extends Action { } } +export class JoinTwoGroupsAction extends Action { + + public static ID = 'workbench.action.joinTwoGroups'; + public static LABEL = nls.localize('joinTwoGroups', "Join Editors of Two Groups"); + + constructor( + id: string, + label: string, + @IWorkbenchEditorService private editorService: IWorkbenchEditorService, + @IEditorGroupService private editorGroupService: IEditorGroupService + ) { + super(id, label); + } + + public run(context?: IEditorContext): TPromise { + + const editorStacksModel = this.editorGroupService.getStacksModel(); + + // Return if has no other group to join to + if (editorStacksModel.groups.length < 2) { + return TPromise.as(true); + } + + let fromPosition: number; + let toPosition: number; + + // Joining group is from context, or the active group + if (context) { + fromPosition = editorStacksModel.positionOfGroup(context.group); + } else { + fromPosition = editorStacksModel.positionOfGroup(editorStacksModel.activeGroup); + } + + // Target group is next group if joining from position one, otherwise it is the previous group + if (fromPosition === Position.ONE) { + toPosition = fromPosition + 1; + } else { + toPosition = fromPosition - 1; + } + + const fromGroup = editorStacksModel.groupAt(fromPosition); + const toGroup = editorStacksModel.groupAt(toPosition); + + const activeEditor = fromGroup.activeEditor; + const fromGroupEditors = fromGroup.getEditors(); + + // Insert the editors to the start if moving to the next group, otherwise insert to the end + // If an editor exists in both groups, its index is respected as in the joining group + const movingToNextGroup = fromPosition < toPosition; + let index = movingToNextGroup ? 0 : toGroup.count; + + // Inactive and preserve focus options are used to prevent unnecessary switchings of active editor or group + fromGroupEditors.forEach(e => { + const inactive = e !== activeEditor; + this.editorGroupService.moveEditor(e, fromPosition, toPosition, { index, inactive, preserveFocus: inactive }); + index = movingToNextGroup ? index + 1 : toGroup.count; + }); + + // Focus may be lost when the joining group is closed, regain focus on the target group + this.editorGroupService.focusGroup(toGroup); + + return TPromise.as(true); + } +} + export class NavigateBetweenGroupsAction extends Action { public static ID = 'workbench.action.navigateEditorGroups'; diff --git a/src/vs/workbench/browser/parts/editor/editorCommands.ts b/src/vs/workbench/browser/parts/editor/editorCommands.ts index 0e13e3091a6f037487e5ec4c487ca776f5d53469..d538cc7c914ce3489821f13a8103f5edc0ad650d 100644 --- a/src/vs/workbench/browser/parts/editor/editorCommands.ts +++ b/src/vs/workbench/browser/parts/editor/editorCommands.ts @@ -115,7 +115,7 @@ function moveActiveTab(args: ActiveEditorMoveArguments, activeEditor: IEditor, a } index = index < 0 ? 0 : index >= editorGroup.count ? editorGroup.count - 1 : index; - editorGroupsService.moveEditor(activeEditor.input, editorGroup, editorGroup, index); + editorGroupsService.moveEditor(activeEditor.input, editorGroup, editorGroup, { index }); } function moveActiveEditorToGroup(args: ActiveEditorMoveArguments, activeEditor: IEditor, accessor: ServicesAccessor): void { diff --git a/src/vs/workbench/browser/parts/editor/editorPart.ts b/src/vs/workbench/browser/parts/editor/editorPart.ts index c342286bb7601c1bdb12a998a99a17c0bd0831aa..dab8cc6574295ecaa82fa417f033ce75f5e3be32 100644 --- a/src/vs/workbench/browser/parts/editor/editorPart.ts +++ b/src/vs/workbench/browser/parts/editor/editorPart.ts @@ -24,7 +24,7 @@ import { BaseEditor, EditorDescriptor } from 'vs/workbench/browser/parts/editor/ import { IEditorRegistry, Extensions as EditorExtensions, EditorInput, EditorOptions, ConfirmResult, IWorkbenchEditorConfiguration, IEditorDescriptor, TextEditorOptions, SideBySideEditorInput, TextCompareEditorVisible, TEXT_DIFF_EDITOR_ID } from 'vs/workbench/common/editor'; import { EditorGroupsControl, Rochade, IEditorGroupsControl, ProgressState } from 'vs/workbench/browser/parts/editor/editorGroupsControl'; import { WorkbenchProgressService } from 'vs/workbench/services/progress/browser/progressService'; -import { IEditorGroupService, GroupOrientation, GroupArrangement, ITabOptions } from 'vs/workbench/services/group/common/groupService'; +import { IEditorGroupService, GroupOrientation, GroupArrangement, ITabOptions, IMoveOptions } from 'vs/workbench/services/group/common/groupService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IEditorPart } from 'vs/workbench/services/editor/browser/editorService'; import { IPartService } from 'vs/workbench/services/part/common/partService'; @@ -808,9 +808,9 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService this._onEditorsMoved.fire(); } - public moveEditor(input: EditorInput, from: EditorGroup, to: EditorGroup, index?: number): void; - public moveEditor(input: EditorInput, from: Position, to: Position, index?: number): void; - public moveEditor(input: EditorInput, arg2: any, arg3: any, index?: number): void { + public moveEditor(input: EditorInput, from: EditorGroup, to: EditorGroup, moveOptions?: IMoveOptions): void; + public moveEditor(input: EditorInput, from: Position, to: Position, moveOptions?: IMoveOptions): void; + public moveEditor(input: EditorInput, arg2: any, arg3: any, moveOptions?: IMoveOptions): void { const fromGroup = (typeof arg2 === 'number') ? this.stacks.groupAt(arg2) : arg2; if (!fromGroup) { return; @@ -818,18 +818,20 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService // Move within group if (arg2 === arg3) { - this.doMoveEditorInsideGroups(input, fromGroup, index); + this.doMoveEditorInsideGroups(input, fromGroup, moveOptions); } // Move across groups else { const toPosition = (typeof arg3 === 'number') ? arg3 : this.stacks.positionOfGroup(arg3); - this.doMoveEditorAcrossGroups(input, fromGroup, toPosition, index); + this.doMoveEditorAcrossGroups(input, fromGroup, toPosition, moveOptions); } } - private doMoveEditorInsideGroups(input: EditorInput, group: EditorGroup, toIndex: number): void { + private doMoveEditorInsideGroups(input: EditorInput, group: EditorGroup, moveOptions?: IMoveOptions): void { + let toIndex = moveOptions && moveOptions.index; + if (typeof toIndex !== 'number') { return; // do nothing if we move into same group without index } @@ -844,7 +846,7 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService group.pin(input); } - private doMoveEditorAcrossGroups(input: EditorInput, fromGroup: EditorGroup, to: Position, index?: number): void { + private doMoveEditorAcrossGroups(input: EditorInput, fromGroup: EditorGroup, to: Position, moveOptions?: IMoveOptions): void { if (fromGroup.count === 1) { const toGroup = this.stacks.groupAt(to); if (!toGroup && this.stacks.positionOfGroup(fromGroup) < to) { @@ -852,13 +854,17 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService } } + const index = moveOptions && moveOptions.index; + const inactive = moveOptions && moveOptions.inactive; + const preserveFocus = moveOptions && moveOptions.preserveFocus; + // When moving an editor, try to preserve as much view state as possible by checking - // for th editor to be a text editor and creating the options accordingly if so - let options = EditorOptions.create({ pinned: true, index }); + // for the editor to be a text editor and creating the options accordingly if so + let options = EditorOptions.create({ pinned: true, index, inactive, preserveFocus }); const activeEditor = this.getActiveEditor(); const codeEditor = getCodeEditor(activeEditor); if (codeEditor && activeEditor.position === this.stacks.positionOfGroup(fromGroup) && input.matches(activeEditor.input)) { - options = TextEditorOptions.create({ pinned: true, index }); + options = TextEditorOptions.create({ pinned: true, index, inactive, preserveFocus }); (options).fromEditor(codeEditor); } diff --git a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts index 93709e62046a53061966916580c58774ec5d0024..babd86bda0963631fe2b5290f60c85024561e7c1 100644 --- a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts @@ -627,7 +627,7 @@ export class TabsTitleControl extends TitleControl { // Move editor to target position and index if (this.isMoveOperation(e, draggedEditor.group, group)) { - this.editorGroupService.moveEditor(draggedEditor.editor, draggedEditor.group, group, targetIndex); + this.editorGroupService.moveEditor(draggedEditor.editor, draggedEditor.group, group, { index: targetIndex }); } // Copy: just open editor at target index diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index 70e2d93d3262c2285c5f9c1e35b9fd16e2c42779..d895d26942f0d1c63edfb93df8919e2a60fc38d2 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -642,6 +642,7 @@ export class TextEditorOptions extends EditorOptions { options.revealIfVisible = settings.revealIfVisible; options.pinned = settings.pinned; options.index = settings.index; + options.inactive = settings.inactive; if (settings.selection) { options.startLineNumber = settings.selection.startLineNumber; diff --git a/src/vs/workbench/parts/files/browser/views/openEditorsViewer.ts b/src/vs/workbench/parts/files/browser/views/openEditorsViewer.ts index d93106babdcd12447f47161237a5fceb6e65f14a..c8e80348bcda1f2e50c3f0a32bbb910a812a528d 100644 --- a/src/vs/workbench/parts/files/browser/views/openEditorsViewer.ts +++ b/src/vs/workbench/parts/files/browser/views/openEditorsViewer.ts @@ -491,7 +491,7 @@ export class DragAndDrop extends DefaultDragAndDrop { if (draggedElement) { if (draggedElement instanceof OpenEditor) { - this.editorGroupService.moveEditor(draggedElement.editorInput, model.positionOfGroup(draggedElement.editorGroup), positionOfTargetGroup, index); + this.editorGroupService.moveEditor(draggedElement.editorInput, model.positionOfGroup(draggedElement.editorGroup), positionOfTargetGroup, { index }); } else { this.editorGroupService.moveGroup(model.positionOfGroup(draggedElement), positionOfTargetGroup); } diff --git a/src/vs/workbench/services/group/common/groupService.ts b/src/vs/workbench/services/group/common/groupService.ts index e9a3151930729aa2e51fb4eeff0609ace4a907b8..e0cee99277eb91f3ce46a6bf68482aed2232ef33 100644 --- a/src/vs/workbench/services/group/common/groupService.ts +++ b/src/vs/workbench/services/group/common/groupService.ts @@ -24,7 +24,13 @@ export interface ITabOptions { tabCloseButton?: 'left' | 'right' | 'off'; showIcons?: boolean; previewEditors?: boolean; -}; +} + +export interface IMoveOptions { + index?: number; + inactive?: boolean; + preserveFocus?: boolean; +} /** * The editor service allows to open editors and work on the active @@ -106,9 +112,10 @@ export interface IEditorGroupService { /** * Moves an editor from one group to another. The index in the group is optional. + * The inactive option is applied when moving across groups. */ - moveEditor(input: IEditorInput, from: IEditorGroup, to: IEditorGroup, index?: number): void; - moveEditor(input: IEditorInput, from: Position, to: Position, index?: number): void; + moveEditor(input: IEditorInput, from: IEditorGroup, to: IEditorGroup, moveOptions?: IMoveOptions): void; + moveEditor(input: IEditorInput, from: Position, to: Position, moveOptions?: IMoveOptions): void; /** * Provides access to the editor stacks model diff --git a/src/vs/workbench/test/workbenchTestServices.ts b/src/vs/workbench/test/workbenchTestServices.ts index 3035d7f7ec238d1d1c56f8df3e04042a23be7442..2b125ca8e1b84b24ccca03e8e33a6f5098721d4b 100644 --- a/src/vs/workbench/test/workbenchTestServices.ts +++ b/src/vs/workbench/test/workbenchTestServices.ts @@ -32,7 +32,7 @@ import { ILifecycleService, ShutdownEvent, ShutdownReason } from 'vs/platform/li import { EditorStacksModel } from 'vs/workbench/common/editor/editorStacksModel'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService'; -import { IEditorGroupService, GroupArrangement, GroupOrientation, ITabOptions } from 'vs/workbench/services/group/common/groupService'; +import { IEditorGroupService, GroupArrangement, GroupOrientation, ITabOptions, IMoveOptions } from 'vs/workbench/services/group/common/groupService'; import { TextFileService } from 'vs/workbench/services/textfile/common/textFileService'; import { FileOperationEvent, IFileService, IResolveContentOptions, IFileOperationResult, IFileStat, IImportResult, FileChangesEvent, IResolveFileOptions, IContent, IUpdateContentOptions, IStreamContent } from 'vs/platform/files/common/files'; import { IModelService } from 'vs/editor/common/services/modelService'; @@ -449,9 +449,9 @@ export class TestEditorGroupService implements IEditorGroupService { public unpinEditor(arg1: any, input: IEditorInput): void { } - public moveEditor(input: IEditorInput, from: IEditorGroup, to: IEditorGroup, index?: number): void; - public moveEditor(input: IEditorInput, from: Position, to: Position, index?: number): void; - public moveEditor(input: IEditorInput, from: any, to: any, index?: number): void { + public moveEditor(input: IEditorInput, from: IEditorGroup, to: IEditorGroup, moveOptions?: IMoveOptions): void; + public moveEditor(input: IEditorInput, from: Position, to: Position, moveOptions?: IMoveOptions): void; + public moveEditor(input: IEditorInput, from: any, to: any, moveOptions?: IMoveOptions): void { } public getStacksModel(): EditorStacksModel {