提交 b6c47c23 编写于 作者: B Benjamin Pasero

Keep scroll position stable when closing a tab in the tab well (fixes #45527)

上级 e9d92ca9
......@@ -543,6 +543,50 @@ export class CloseEditorAction extends Action {
}
}
export class CloseOneEditorAction extends Action {
public static readonly ID = 'workbench.action.closeActiveEditor';
public static readonly LABEL = nls.localize('closeOneEditor', "Close");
constructor(
id: string,
label: string,
@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
@IEditorGroupService private editorGroupService: IEditorGroupService
) {
super(id, label, 'close-editor-action');
}
public run(context?: IEditorCommandsContext): TPromise<any> {
const model = this.editorGroupService.getStacksModel();
const group = context ? model.getGroup(context.groupId) : null;
const position = group ? model.positionOfGroup(group) : null;
// Close Active Editor
if (typeof position !== 'number') {
const activeEditor = this.editorService.getActiveEditor();
if (activeEditor) {
return this.editorService.closeEditor(activeEditor.position, activeEditor.input);
}
}
// Close Specific Editor
const editor = group && context && typeof context.editorIndex === 'number' ? group.getEditor(context.editorIndex) : null;
if (editor) {
return this.editorService.closeEditor(position, editor);
}
// Close First Editor at Position
const visibleEditors = this.editorService.getVisibleEditors();
if (visibleEditors[position]) {
return this.editorService.closeEditor(position, visibleEditors[position].input);
}
return TPromise.as(false);
}
}
export class RevertAndCloseEditorAction extends Action {
public static readonly ID = 'workbench.action.revertAndCloseActiveEditor';
......
......@@ -81,7 +81,7 @@ export class NoTabsTitleControl extends TitleControl {
// Close editor on middle mouse click
if (e instanceof MouseEvent && e.button === 1 /* Middle Button */) {
this.closeEditorAction.run({ groupId: group.id, editorIndex: group.indexOf(group.activeEditor) }).done(null, errors.onUnexpectedError);
this.closeOneEditorAction.run({ groupId: group.id, editorIndex: group.indexOf(group.activeEditor) }).done(null, errors.onUnexpectedError);
}
// Focus editor group unless:
......
......@@ -20,7 +20,7 @@ import { EventType as TouchEventType, GestureEvent, Gesture } from 'vs/base/brow
import { KeyCode } from 'vs/base/common/keyCodes';
import { ResourceLabel } from 'vs/workbench/browser/labels';
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IWorkbenchEditorService, DelegatingWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
......@@ -34,6 +34,7 @@ import { IDisposable, dispose, combinedDisposable } from 'vs/base/common/lifecyc
import { ScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
import { ScrollbarVisibility } from 'vs/base/common/scrollable';
import { getOrSet } from 'vs/base/common/map';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService';
import { TAB_INACTIVE_BACKGROUND, TAB_ACTIVE_BACKGROUND, TAB_ACTIVE_FOREGROUND, TAB_INACTIVE_FOREGROUND, TAB_BORDER, EDITOR_DRAG_AND_DROP_BACKGROUND, TAB_UNFOCUSED_ACTIVE_FOREGROUND, TAB_UNFOCUSED_INACTIVE_FOREGROUND, 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, EDITOR_GROUP_BACKGROUND, WORKBENCH_BACKGROUND } from 'vs/workbench/common/theme';
import { activeContrastBorder, contrastBorder, editorBackground } from 'vs/platform/theme/common/colorRegistry';
......@@ -83,6 +84,34 @@ export class TabsTitleControl extends TitleControl {
this.editorLabels = [];
}
protected initActions(services: IInstantiationService): void {
super.initActions(this.createScopedInstantiationService());
}
private createScopedInstantiationService(): IInstantiationService {
const stacks = this.editorGroupService.getStacksModel();
const delegatingEditorService = this.instantiationService.createInstance(DelegatingWorkbenchEditorService);
// We create a scoped instantiation service to override the behaviour when closing an inactive editor
// Specifically we want to move focus back to the editor when an inactive editor is closed from anywhere
// in the tabs title control (e.g. mouse middle click, context menu on tab). This is only needed for
// the inactive editors because closing the active one will always cause a tab switch that sets focus.
// We also want to block the tabs container to reveal the currently active tab because that makes it very
// hard to close multiple inactive tabs next to each other.
delegatingEditorService.setEditorCloseHandler((position, editor) => {
const group = stacks.groupAt(position);
if (group && stacks.isActive(group) && !group.isActive(editor)) {
this.editorGroupService.focusGroup(group);
}
this.blockRevealActiveTab = true;
return TPromise.as(void 0);
});
return this.instantiationService.createChild(new ServiceCollection([IWorkbenchEditorService, delegatingEditorService]));
}
public create(parent: HTMLElement): void {
super.create(parent);
......@@ -517,7 +546,7 @@ export class TabsTitleControl extends TitleControl {
this.tabDisposeables.push(actionRunner);
const bar = new ActionBar(tabCloseContainer, { ariaLabel: nls.localize('araLabelTabActions', "Tab actions"), actionRunner });
bar.push(this.closeEditorAction, { icon: true, label: false, keybinding: this.getKeybindingLabel(this.closeEditorAction) });
bar.push(this.closeOneEditorAction, { icon: true, label: false, keybinding: this.getKeybindingLabel(this.closeOneEditorAction) });
// Eventing
const disposable = this.hookTabListeners(tabContainer, index);
......@@ -636,7 +665,7 @@ export class TabsTitleControl extends TitleControl {
tab.blur();
if (e.button === 1 /* Middle Button*/ && !this.isTabActionBar((e.target || e.srcElement) as HTMLElement)) {
this.closeEditorAction.run({ groupId: this.context.id, editorIndex: index }).done(null, errors.onUnexpectedError);
this.closeOneEditorAction.run({ groupId: this.context.id, editorIndex: index }).done(null, errors.onUnexpectedError);
}
}));
......
......@@ -28,7 +28,7 @@ import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { ResolvedKeybinding } from 'vs/base/common/keyCodes';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { SplitEditorAction, CloseEditorAction } from 'vs/workbench/browser/parts/editor/editorActions';
import { SplitEditorAction, CloseOneEditorAction } from 'vs/workbench/browser/parts/editor/editorActions';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { createActionItem, fillInActions } from 'vs/platform/actions/browser/menuItemActionItem';
import { IMenuService, MenuId, IMenu, ExecuteCommandAction } from 'vs/platform/actions/common/actions';
......@@ -65,7 +65,7 @@ export abstract class TitleControl extends Themable implements ITitleAreaControl
protected dragged: boolean;
protected closeEditorAction: CloseEditorAction;
protected closeOneEditorAction: CloseOneEditorAction;
protected splitEditorAction: SplitEditorAction;
private parent: HTMLElement;
......@@ -75,7 +75,8 @@ export abstract class TitleControl extends Themable implements ITitleAreaControl
protected editorActionsToolbar: ToolBar;
private mapActionsToEditors: { [editorId: string]: IToolbarActions; };
private scheduler: RunOnceScheduler;
private titleAreaUpdateScheduler: RunOnceScheduler;
private titleAreaToolbarUpdateScheduler: RunOnceScheduler;
private refreshScheduled: boolean;
private resourceContext: ResourceContextKey;
......@@ -101,8 +102,11 @@ export abstract class TitleControl extends Themable implements ITitleAreaControl
this.stacks = editorGroupService.getStacksModel();
this.mapActionsToEditors = Object.create(null);
this.scheduler = new RunOnceScheduler(() => this.onSchedule(), 0);
this.toUnbind.push(this.scheduler);
this.titleAreaUpdateScheduler = new RunOnceScheduler(() => this.onSchedule(), 0);
this.toUnbind.push(this.titleAreaUpdateScheduler);
this.titleAreaToolbarUpdateScheduler = new RunOnceScheduler(() => this.updateEditorActionsToolbar(), 0);
this.toUnbind.push(this.titleAreaToolbarUpdateScheduler);
this.resourceContext = instantiationService.createInstance(ResourceContextKey);
......@@ -166,22 +170,26 @@ export abstract class TitleControl extends Themable implements ITitleAreaControl
public update(instant?: boolean): void {
if (instant) {
this.scheduler.cancel();
this.titleAreaUpdateScheduler.cancel();
this.onSchedule();
} else {
this.scheduler.schedule();
this.titleAreaUpdateScheduler.schedule();
}
this.titleAreaToolbarUpdateScheduler.cancel(); // a title area update will always refresh the toolbar too
}
public refresh(instant?: boolean) {
this.refreshScheduled = true;
if (instant) {
this.scheduler.cancel();
this.titleAreaUpdateScheduler.cancel();
this.onSchedule();
} else {
this.scheduler.schedule();
this.titleAreaUpdateScheduler.schedule();
}
this.titleAreaToolbarUpdateScheduler.cancel(); // a title area update will always refresh the toolbar too
}
public create(parent: HTMLElement): void {
......@@ -207,7 +215,7 @@ export abstract class TitleControl extends Themable implements ITitleAreaControl
}
protected initActions(services: IInstantiationService): void {
this.closeEditorAction = services.createInstance(CloseEditorAction, CloseEditorAction.ID, nls.localize('close', "Close"));
this.closeOneEditorAction = services.createInstance(CloseOneEditorAction, CloseOneEditorAction.ID, CloseOneEditorAction.LABEL);
this.splitEditorAction = services.createInstance(SplitEditorAction, SplitEditorAction.ID, SplitEditorAction.LABEL);
}
......@@ -296,7 +304,14 @@ export abstract class TitleControl extends Themable implements ITitleAreaControl
const codeEditor = isCodeEditor(widget) && widget || isDiffEditor(widget) && widget.getModifiedEditor();
const scopedContextKeyService = codeEditor && codeEditor.invokeWithinContext(accessor => accessor.get(IContextKeyService)) || this.contextKeyService;
const titleBarMenu = this.menuService.createMenu(MenuId.EditorTitle, scopedContextKeyService);
this.disposeOnEditorActions.push(titleBarMenu, titleBarMenu.onDidChange(_ => this.update()));
this.disposeOnEditorActions.push(titleBarMenu, titleBarMenu.onDidChange(_ => {
// schedule the update for the title area toolbar only if no other
// update to the title area is scheduled which will always also
// update the toolbar
if (!this.titleAreaUpdateScheduler.isScheduled()) {
this.titleAreaToolbarUpdateScheduler.schedule();
}
}));
fillInActions(titleBarMenu, { arg: this.resourceContext.get(), shouldForwardArgs: true }, { primary, secondary }, this.contextMenuService);
}
......@@ -334,11 +349,10 @@ export abstract class TitleControl extends Themable implements ITitleAreaControl
const primaryEditorActionIds = primaryEditorActions.map(a => a.id);
if (!tabOptions.showTabs) {
primaryEditorActionIds.push(this.closeEditorAction.id); // always show "Close" when tabs are disabled
primaryEditorActionIds.push(this.closeOneEditorAction.id); // always show "Close" when tabs are disabled
}
const secondaryEditorActionIds = secondaryEditorActions.map(a => a.id);
if (
!arrays.equals(primaryEditorActionIds, this.currentPrimaryEditorActionIds) ||
!arrays.equals(secondaryEditorActionIds, this.currentSecondaryEditorActionIds) ||
......@@ -348,7 +362,7 @@ export abstract class TitleControl extends Themable implements ITitleAreaControl
this.editorActionsToolbar.setActions(primaryEditorActions, secondaryEditorActions)();
if (!tabOptions.showTabs) {
this.editorActionsToolbar.addPrimaryAction(this.closeEditorAction)();
this.editorActionsToolbar.addPrimaryAction(this.closeOneEditorAction)();
}
this.currentPrimaryEditorActionIds = primaryEditorActionIds;
......@@ -416,7 +430,7 @@ export abstract class TitleControl extends Themable implements ITitleAreaControl
// Actions
[
this.splitEditorAction,
this.closeEditorAction
this.closeOneEditorAction
].forEach((action) => {
action.dispose();
});
......
......@@ -268,6 +268,10 @@ export class WorkbenchEditorService implements IWorkbenchEditorService {
}
public closeEditor(position: Position, input: IEditorInput): TPromise<void> {
return this.doCloseEditor(position, input);
}
protected doCloseEditor(position: Position, input: IEditorInput): TPromise<void> {
return this.editorPart.closeEditor(position, input);
}
......@@ -396,6 +400,7 @@ export interface IEditorCloseHandler {
*/
export class DelegatingWorkbenchEditorService extends WorkbenchEditorService {
private editorOpenHandler: IEditorOpenHandler;
private editorCloseHandler: IEditorCloseHandler;
constructor(
@IUntitledEditorService untitledEditorService: IUntitledEditorService,
......@@ -419,6 +424,10 @@ export class DelegatingWorkbenchEditorService extends WorkbenchEditorService {
this.editorOpenHandler = handler;
}
public setEditorCloseHandler(handler: IEditorCloseHandler): void {
this.editorCloseHandler = handler;
}
protected doOpenEditor(input: IEditorInput, options?: EditorOptions, sideBySide?: boolean): TPromise<IEditor>;
protected doOpenEditor(input: IEditorInput, options?: EditorOptions, position?: Position): TPromise<IEditor>;
protected doOpenEditor(input: IEditorInput, options?: EditorOptions, arg3?: any): TPromise<IEditor> {
......@@ -432,4 +441,12 @@ export class DelegatingWorkbenchEditorService extends WorkbenchEditorService {
return super.doOpenEditor(input, options, arg3);
});
}
protected doCloseEditor(position: Position, input: IEditorInput): TPromise<void> {
const handleClose = this.editorCloseHandler ? this.editorCloseHandler(position, input) : TPromise.as(void 0);
return handleClose.then(() => {
return super.doCloseEditor(position, input);
});
}
}
......@@ -266,11 +266,18 @@ suite('WorkbenchEditorService', () => {
delegate.setEditorOpenHandler((input: IEditorInput, options?: EditorOptions) => {
assert.strictEqual(input, inp);
return TPromise.as(ed);
});
delegate.setEditorCloseHandler((position, input) => {
assert.strictEqual(input, inp);
done();
return TPromise.as(ed);
return TPromise.as(void 0);
});
delegate.openEditor(inp);
delegate.closeEditor(0, inp);
});
});
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册