chore(vscode): update to 1.53.2

These conflicts will be resolved in the following commits. We do it this way so
that PR review is possible.
This commit is contained in:
Joe Previte
2021-02-25 11:27:27 -07:00
1900 changed files with 83066 additions and 64589 deletions

View File

@@ -8,25 +8,21 @@ import { Registry } from 'vs/platform/registry/common/platform';
import { Action } from 'vs/base/common/actions';
import { SyncActionDescriptor, MenuId, MenuRegistry, registerAction2, Action2 } from 'vs/platform/actions/common/actions';
import { IWorkbenchActionRegistry, Extensions as WorkbenchExtensions, CATEGORIES } from 'vs/workbench/common/actions';
import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IWorkbenchLayoutService, Parts, Position } from 'vs/workbench/services/layout/browser/layoutService';
import { IEditorGroupsService, GroupOrientation } from 'vs/workbench/services/editor/common/editorGroupsService';
import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { KeyMod, KeyCode, KeyChord } from 'vs/base/common/keyCodes';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { getMenuBarVisibility } from 'vs/platform/windows/common/windows';
import { isWindows, isLinux, isWeb } from 'vs/base/common/platform';
import { IsMacNativeContext } from 'vs/platform/contextkey/common/contextkeys';
import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { InEditorZenModeContext, IsCenteredLayoutContext, EditorAreaVisibleContext } from 'vs/workbench/common/editor';
import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { SideBarVisibleContext } from 'vs/workbench/common/viewlet';
import { IViewDescriptorService, IViewsService, FocusedViewContext, ViewContainerLocation, IViewDescriptor } from 'vs/workbench/common/views';
import { IViewDescriptorService, IViewsService, FocusedViewContext, ViewContainerLocation, IViewDescriptor, ViewContainerLocationToString } from 'vs/workbench/common/views';
import { IQuickInputService, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { IActivityBarService } from 'vs/workbench/services/activityBar/browser/activityBarService';
import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
import { Codicon } from 'vs/base/common/codicons';
const registry = Registry.as<IWorkbenchActionRegistry>(WorkbenchExtensions.WorkbenchActions);
@@ -125,54 +121,6 @@ MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, {
order: 3
});
// --- Toggle Editor Layout
export class ToggleEditorLayoutAction extends Action {
static readonly ID = 'workbench.action.toggleEditorGroupLayout';
static readonly LABEL = nls.localize('flipLayout', "Toggle Vertical/Horizontal Editor Layout");
private readonly toDispose = this._register(new DisposableStore());
constructor(
id: string,
label: string,
@IEditorGroupsService private readonly editorGroupService: IEditorGroupsService
) {
super(id, label);
this.class = Codicon.editorLayout.classNames;
this.updateEnablement();
this.registerListeners();
}
private registerListeners(): void {
this.toDispose.add(this.editorGroupService.onDidAddGroup(() => this.updateEnablement()));
this.toDispose.add(this.editorGroupService.onDidRemoveGroup(() => this.updateEnablement()));
}
private updateEnablement(): void {
this.enabled = this.editorGroupService.count > 1;
}
async run(): Promise<void> {
const newOrientation = (this.editorGroupService.orientation === GroupOrientation.VERTICAL) ? GroupOrientation.HORIZONTAL : GroupOrientation.VERTICAL;
this.editorGroupService.setGroupOrientation(newOrientation);
}
}
registry.registerWorkbenchAction(SyncActionDescriptor.from(ToggleEditorLayoutAction, { primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KEY_0, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_0 } }), 'View: Toggle Vertical/Horizontal Editor Layout', CATEGORIES.View.value);
MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, {
group: 'z_flip',
command: {
id: ToggleEditorLayoutAction.ID,
title: nls.localize({ key: 'miToggleEditorLayout', comment: ['&& denotes a mnemonic'] }, "Flip &&Layout")
},
order: 1
});
// --- Toggle Sidebar Position
export class ToggleSidebarPositionAction extends Action {
@@ -203,7 +151,64 @@ export class ToggleSidebarPositionAction extends Action {
}
}
registry.registerWorkbenchAction(SyncActionDescriptor.from(ToggleSidebarPositionAction), 'View: Toggle Side Bar Position', CATEGORIES.View.value);
registerAction2(class extends Action2 {
constructor() {
super({
id: ToggleSidebarPositionAction.ID,
title: { value: nls.localize('toggleSidebarPosition', "Toggle Side Bar Position"), original: 'Toggle Side Bar Position' },
category: CATEGORIES.View,
f1: true
});
}
run(accessor: ServicesAccessor) {
accessor.get(IInstantiationService).createInstance(ToggleSidebarPositionAction, ToggleSidebarPositionAction.ID, ToggleSidebarPositionAction.LABEL).run();
}
});
MenuRegistry.appendMenuItems([{
id: MenuId.ViewContainerTitleContext,
item: {
group: '3_workbench_layout_move',
command: {
id: ToggleSidebarPositionAction.ID,
title: nls.localize('move sidebar right', "Move Side Bar Right")
},
when: ContextKeyExpr.and(ContextKeyExpr.notEquals('config.workbench.sideBar.location', 'right'), ContextKeyExpr.equals('viewContainerLocation', ViewContainerLocationToString(ViewContainerLocation.Sidebar))),
order: 1
}
}, {
id: MenuId.ViewTitleContext,
item: {
group: '3_workbench_layout_move',
command: {
id: ToggleSidebarPositionAction.ID,
title: nls.localize('move sidebar right', "Move Side Bar Right")
},
when: ContextKeyExpr.and(ContextKeyExpr.notEquals('config.workbench.sideBar.location', 'right'), ContextKeyExpr.equals('viewLocation', ViewContainerLocationToString(ViewContainerLocation.Sidebar))),
order: 1
}
}, {
id: MenuId.ViewContainerTitleContext,
item: {
group: '3_workbench_layout_move',
command: {
id: ToggleSidebarPositionAction.ID,
title: nls.localize('move sidebar left', "Move Side Bar Left")
},
when: ContextKeyExpr.and(ContextKeyExpr.equals('config.workbench.sideBar.location', 'right'), ContextKeyExpr.equals('viewContainerLocation', ViewContainerLocationToString(ViewContainerLocation.Sidebar))),
order: 1
}
}, {
id: MenuId.ViewTitleContext,
item: {
group: '3_workbench_layout_move',
command: {
id: ToggleSidebarPositionAction.ID,
title: nls.localize('move sidebar left', "Move Side Bar Left")
},
when: ContextKeyExpr.and(ContextKeyExpr.equals('config.workbench.sideBar.location', 'right'), ContextKeyExpr.equals('viewLocation', ViewContainerLocationToString(ViewContainerLocation.Sidebar))),
order: 1
}
}]);
MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, {
group: '3_workbench_layout_move',
@@ -256,27 +261,6 @@ MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, {
order: 5
});
export class ToggleSidebarVisibilityAction extends Action {
static readonly ID = 'workbench.action.toggleSidebarVisibility';
static readonly LABEL = nls.localize('toggleSidebar', "Toggle Side Bar Visibility");
constructor(
id: string,
label: string,
@IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService
) {
super(id, label);
}
async run(): Promise<void> {
const hideSidebar = this.layoutService.isVisible(Parts.SIDEBAR_PART);
this.layoutService.setSideBarHidden(hideSidebar);
}
}
registry.registerWorkbenchAction(SyncActionDescriptor.from(ToggleSidebarVisibilityAction, { primary: KeyMod.CtrlCmd | KeyCode.KEY_B }), 'View: Toggle Side Bar Visibility', CATEGORIES.View.value);
MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, {
group: '2_appearance',
title: nls.localize({ key: 'miAppearance', comment: ['&& denotes a mnemonic'] }, "&&Appearance"),
@@ -284,10 +268,53 @@ MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, {
order: 1
});
export const TOGGLE_SIDEBAR_VISIBILITY_ACTION_ID = 'workbench.action.toggleSidebarVisibility';
registerAction2(class extends Action2 {
constructor() {
super({
id: TOGGLE_SIDEBAR_VISIBILITY_ACTION_ID,
title: { value: nls.localize('toggleSidebar', "Toggle Side Bar Visibility"), original: 'Toggle Side Bar Visibility' },
category: CATEGORIES.View,
f1: true,
keybinding: {
weight: KeybindingWeight.WorkbenchContrib,
primary: KeyMod.CtrlCmd | KeyCode.KEY_B
}
});
}
run(accessor: ServicesAccessor) {
const layoutService = accessor.get(IWorkbenchLayoutService);
layoutService.setSideBarHidden(layoutService.isVisible(Parts.SIDEBAR_PART));
}
});
MenuRegistry.appendMenuItems([{
id: MenuId.ViewContainerTitleContext,
item: {
group: '3_workbench_layout_move',
command: {
id: TOGGLE_SIDEBAR_VISIBILITY_ACTION_ID,
title: nls.localize('compositePart.hideSideBarLabel', "Hide Side Bar"),
},
when: ContextKeyExpr.and(SideBarVisibleContext, ContextKeyExpr.equals('viewContainerLocation', ViewContainerLocationToString(ViewContainerLocation.Sidebar))),
order: 2
}
}, {
id: MenuId.ViewTitleContext,
item: {
group: '3_workbench_layout_move',
command: {
id: TOGGLE_SIDEBAR_VISIBILITY_ACTION_ID,
title: nls.localize('compositePart.hideSideBarLabel', "Hide Side Bar"),
},
when: ContextKeyExpr.and(SideBarVisibleContext, ContextKeyExpr.equals('viewLocation', ViewContainerLocationToString(ViewContainerLocation.Sidebar))),
order: 2
}
}]);
MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, {
group: '2_workbench_layout',
command: {
id: ToggleSidebarVisibilityAction.ID,
id: TOGGLE_SIDEBAR_VISIBILITY_ACTION_ID,
title: nls.localize({ key: 'miShowSidebar', comment: ['&& denotes a mnemonic'] }, "Show &&Side Bar"),
toggled: SideBarVisibleContext
},
@@ -413,32 +440,16 @@ export class ToggleMenuBarAction extends Action {
static readonly ID = 'workbench.action.toggleMenuBar';
static readonly LABEL = nls.localize('toggleMenuBar', "Toggle Menu Bar");
private static readonly menuBarVisibilityKey = 'window.menuBarVisibility';
constructor(
id: string,
label: string,
@IConfigurationService private readonly configurationService: IConfigurationService
@IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService
) {
super(id, label);
}
run(): Promise<void> {
let currentVisibilityValue = getMenuBarVisibility(this.configurationService);
if (typeof currentVisibilityValue !== 'string') {
currentVisibilityValue = 'default';
}
let newVisibilityValue: string;
if (currentVisibilityValue === 'visible' || currentVisibilityValue === 'default') {
newVisibilityValue = 'toggle';
} else if (currentVisibilityValue === 'compact') {
newVisibilityValue = 'hidden';
} else {
newVisibilityValue = (isWeb && currentVisibilityValue === 'hidden') ? 'compact' : 'default';
}
return this.configurationService.updateValue(ToggleMenuBarAction.menuBarVisibilityKey, newVisibilityValue, ConfigurationTarget.USER);
async run(): Promise<void> {
this.layoutService.toggleMenuBar();
}
}
@@ -479,19 +490,18 @@ export class ResetViewLocationsAction extends Action {
registry.registerWorkbenchAction(SyncActionDescriptor.from(ResetViewLocationsAction), 'View: Reset View Locations', CATEGORIES.View.value);
// --- Toggle View with Command
export abstract class ToggleViewAction extends Action {
export class ToggleViewAction extends Action {
constructor(
id: string,
label: string,
private readonly viewId: string,
protected viewsService: IViewsService,
protected viewDescriptorService: IViewDescriptorService,
protected contextKeyService: IContextKeyService,
private layoutService: IWorkbenchLayoutService,
cssClass?: string
@IViewsService protected viewsService: IViewsService,
@IViewDescriptorService protected viewDescriptorService: IViewDescriptorService,
@IContextKeyService protected contextKeyService: IContextKeyService,
@IWorkbenchLayoutService private layoutService: IWorkbenchLayoutService,
) {
super(id, label, cssClass);
super(id, label);
}
async run(): Promise<void> {

View File

@@ -17,7 +17,6 @@ import { IWorkbenchActionRegistry, Extensions, CATEGORIES } from 'vs/workbench/c
import { Direction } from 'vs/base/browser/ui/grid/grid';
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { isAncestor } from 'vs/base/browser/dom';
abstract class BaseNavigationAction extends Action {
@@ -215,7 +214,8 @@ function findVisibleNeighbour(layoutService: IWorkbenchLayoutService, part: Part
}
function focusNextOrPreviousPart(layoutService: IWorkbenchLayoutService, editorService: IEditorService, next: boolean): void {
const currentlyFocusedPart = isActiveElementInNotebookEditor(editorService) ? Parts.EDITOR_PART : layoutService.hasFocus(Parts.EDITOR_PART) ? Parts.EDITOR_PART : layoutService.hasFocus(Parts.ACTIVITYBAR_PART) ? Parts.ACTIVITYBAR_PART :
const editorFocused = editorService.activeEditorPane?.hasFocus();
const currentlyFocusedPart = editorFocused ? Parts.EDITOR_PART : layoutService.hasFocus(Parts.ACTIVITYBAR_PART) ? Parts.ACTIVITYBAR_PART :
layoutService.hasFocus(Parts.STATUSBAR_PART) ? Parts.STATUSBAR_PART : layoutService.hasFocus(Parts.SIDEBAR_PART) ? Parts.SIDEBAR_PART : layoutService.hasFocus(Parts.PANEL_PART) ? Parts.PANEL_PART : undefined;
let partToFocus = Parts.EDITOR_PART;
if (currentlyFocusedPart) {
@@ -225,17 +225,6 @@ function focusNextOrPreviousPart(layoutService: IWorkbenchLayoutService, editorS
layoutService.focusPart(partToFocus);
}
function isActiveElementInNotebookEditor(editorService: IEditorService): boolean {
const activeEditorPane = editorService.activeEditorPane as unknown as { isNotebookEditor?: boolean } | undefined;
if (activeEditorPane?.isNotebookEditor) {
const control = editorService.activeEditorPane?.getControl() as { getDomNode(): HTMLElement; getOverflowContainerDomNode(): HTMLElement; };
const activeElement = document.activeElement;
return isAncestor(activeElement, control.getDomNode()) || isAncestor(activeElement, control.getOverflowContainerDomNode());
}
return false;
}
export class FocusNextPart extends Action {
static readonly ID = 'workbench.action.focusNextPart';
static readonly LABEL = nls.localize('focusNextPart', "Focus Next Part");

View File

@@ -76,23 +76,26 @@ export class TextInputActionsProvider extends Disposable implements IWorkbenchCo
// Context menu support in input/textarea
this.layoutService.container.addEventListener('contextmenu', e => this.onContextMenu(e));
}
private onContextMenu(e: MouseEvent): void {
if (e.target instanceof HTMLElement) {
const target = <HTMLElement>e.target;
if (target.nodeName && (target.nodeName.toLowerCase() === 'input' || target.nodeName.toLowerCase() === 'textarea')) {
EventHelper.stop(e, true);
this.contextMenuService.showContextMenu({
getAnchor: () => e,
getActions: () => this.textInputActions,
getActionsContext: () => target,
onHide: () => target.focus() // fixes https://github.com/microsoft/vscode/issues/52948
});
}
if (e.defaultPrevented) {
return; // make sure to not show these actions by accident if component indicated to prevent
}
const target = e.target;
if (!(target instanceof HTMLElement) || (target.nodeName.toLowerCase() !== 'input' && target.nodeName.toLowerCase() !== 'textarea')) {
return; // only for inputs or textareas
}
EventHelper.stop(e, true);
this.contextMenuService.showContextMenu({
getAnchor: () => e,
getActions: () => this.textInputActions,
getActionsContext: () => target,
onHide: () => target.focus() // fixes https://github.com/microsoft/vscode/issues/52948
});
}
}

View File

@@ -33,7 +33,7 @@ import { ResourceMap } from 'vs/base/common/map';
import { Codicon } from 'vs/base/common/codicons';
import { isHTMLElement } from 'vs/base/browser/dom';
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
export const inRecentFilesPickerContextKey = 'inRecentFilesPicker';
@@ -49,12 +49,17 @@ abstract class BaseOpenRecentAction extends Action {
tooltip: nls.localize('remove', "Remove from Recently Opened")
};
private readonly dirtyRecentlyOpened: IQuickInputButton = {
private readonly dirtyRecentlyOpenedFolder: IQuickInputButton = {
iconClass: 'dirty-workspace ' + Codicon.closeDirty.classNames,
tooltip: nls.localize('dirtyRecentlyOpened', "Workspace With Dirty Files"),
tooltip: nls.localize('dirtyRecentlyOpenedFolder', "Folder With Unsaved Files"),
alwaysVisible: true
};
private readonly dirtyRecentlyOpenedWorkspace: IQuickInputButton = {
...this.dirtyRecentlyOpenedFolder,
tooltip: nls.localize('dirtyRecentlyOpenedWorkspace', "Workspace With Unsaved Files"),
};
constructor(
id: string,
label: string,
@@ -77,7 +82,9 @@ abstract class BaseOpenRecentAction extends Action {
const recentlyOpened = await this.workspacesService.getRecentlyOpened();
const dirtyWorkspacesAndFolders = await this.workspacesService.getDirtyWorkspaces();
// Identify all folders and workspaces with dirty files
let hasWorkspaces = false;
// Identify all folders and workspaces with unsaved files
const dirtyFolders = new ResourceMap<boolean>();
const dirtyWorkspaces = new ResourceMap<IWorkspaceIdentifier>();
for (const dirtyWorkspace of dirtyWorkspacesAndFolders) {
@@ -85,6 +92,7 @@ abstract class BaseOpenRecentAction extends Action {
dirtyFolders.set(dirtyWorkspace, true);
} else {
dirtyWorkspaces.set(dirtyWorkspace.configPath, dirtyWorkspace);
hasWorkspaces = true;
}
}
@@ -96,6 +104,7 @@ abstract class BaseOpenRecentAction extends Action {
recentFolders.set(recent.folderUri, true);
} else {
recentWorkspaces.set(recent.workspace.configPath, recent.workspace);
hasWorkspaces = true;
}
}
@@ -124,7 +133,7 @@ abstract class BaseOpenRecentAction extends Action {
let keyMods: IKeyMods | undefined;
const workspaceSeparator: IQuickPickSeparator = { type: 'separator', label: nls.localize('workspaces', "workspaces") };
const workspaceSeparator: IQuickPickSeparator = { type: 'separator', label: hasWorkspaces ? nls.localize('workspacesAndFolders', "folders & workspaces") : nls.localize('folders', "folders") };
const fileSeparator: IQuickPickSeparator = { type: 'separator', label: nls.localize('files', "files") };
const picks = [workspaceSeparator, ...workspacePicks, fileSeparator, ...filePicks];
@@ -143,13 +152,14 @@ abstract class BaseOpenRecentAction extends Action {
context.removeItem();
}
// Dirty Workspace
else if (context.button === this.dirtyRecentlyOpened) {
// Dirty Folder/Workspace
else if (context.button === this.dirtyRecentlyOpenedFolder || context.button === this.dirtyRecentlyOpenedWorkspace) {
const isDirtyWorkspace = context.button === this.dirtyRecentlyOpenedWorkspace;
const result = await this.dialogService.confirm({
type: 'question',
title: nls.localize('dirtyWorkspace', "Workspace with Dirty Files"),
message: nls.localize('dirtyWorkspaceConfirm', "Do you want to open the workspace to review the dirty files?"),
detail: nls.localize('dirtyWorkspaceConfirmDetail', "Workspaces with dirty files cannot be removed until all dirty files have been saved or reverted.")
title: isDirtyWorkspace ? nls.localize('dirtyWorkspace', "Workspace with Unsaved Files") : nls.localize('dirtyFolder', "Folder with Unsaved Files"),
message: isDirtyWorkspace ? nls.localize('dirtyWorkspaceConfirm', "Do you want to open the workspace to review the unsaved files?") : nls.localize('dirtyFolderConfirm', "Do you want to open the folder to review the unsaved files?"),
detail: isDirtyWorkspace ? nls.localize('dirtyWorkspaceConfirmDetail', "Workspaces with unsaved files cannot be removed until all unsaved files have been saved or reverted.") : nls.localize('dirtyFolderConfirmDetail', "Folders with unsaved files cannot be removed until all unsaved files have been saved or reverted.")
});
if (result.confirmed) {
@@ -170,6 +180,7 @@ abstract class BaseOpenRecentAction extends Action {
let iconClasses: string[];
let fullLabel: string | undefined;
let resource: URI | undefined;
let isWorkspace = false;
// Folder
if (isRecentFolder(recent)) {
@@ -185,6 +196,7 @@ abstract class BaseOpenRecentAction extends Action {
iconClasses = getIconClasses(this.modelService, this.modeService, resource, FileKind.ROOT_FOLDER);
openable = { workspaceUri: resource };
fullLabel = recent.label || this.labelService.getWorkspaceLabel(recent.workspace, { verbose: true });
isWorkspace = true;
}
// File
@@ -200,9 +212,9 @@ abstract class BaseOpenRecentAction extends Action {
return {
iconClasses,
label: name,
ariaLabel: isDirty ? nls.localize('recentDirtyAriaLabel', "{0}, dirty workspace", name) : name,
ariaLabel: isDirty ? isWorkspace ? nls.localize('recentDirtyWorkspaceAriaLabel', "{0}, workspace with unsaved changes", name) : nls.localize('recentDirtyFolderAriaLabel', "{0}, folder with unsaved changes", name) : name,
description: parentPath,
buttons: isDirty ? [this.dirtyRecentlyOpened] : [this.removeFromRecentlyOpened],
buttons: isDirty ? [isWorkspace ? this.dirtyRecentlyOpenedWorkspace : this.dirtyRecentlyOpenedFolder] : [this.removeFromRecentlyOpened],
openable,
resource
};
@@ -405,7 +417,7 @@ CommandsRegistry.registerCommand('workbench.action.toggleConfirmBeforeClose', ac
const configurationService = accessor.get(IConfigurationService);
const setting = configurationService.inspect<'always' | 'keyboardOnly' | 'never'>('window.confirmBeforeClose').userValue;
return configurationService.updateValue('window.confirmBeforeClose', setting === 'never' ? 'keyboardOnly' : 'never', ConfigurationTarget.USER);
return configurationService.updateValue('window.confirmBeforeClose', setting === 'never' ? 'keyboardOnly' : 'never');
});
// --- Menu Registration

View File

@@ -114,7 +114,7 @@ export class CloseWorkspaceAction extends Action {
async run(): Promise<void> {
if (this.contextService.getWorkbenchState() === WorkbenchState.EMPTY) {
this.notificationService.info(nls.localize('noWorkspaceOpened', "There is currently no workspace opened in this instance to close."));
this.notificationService.info(nls.localize('noWorkspaceOrFolderOpened', "There is currently no workspace or folder opened in this instance to close."));
return;
}
@@ -225,7 +225,7 @@ export class SaveWorkspaceAsAction extends Action {
export class DuplicateWorkspaceInNewWindowAction extends Action {
static readonly ID = 'workbench.action.duplicateWorkspaceInNewWindow';
static readonly LABEL = nls.localize('duplicateWorkspaceInNewWindow', "Duplicate Workspace in New Window");
static readonly LABEL = nls.localize('duplicateWorkspaceInNewWindow', "Duplicate As Workspace in New Window");
constructor(
id: string,
@@ -259,7 +259,7 @@ registry.registerWorkbenchAction(SyncActionDescriptor.from(AddRootFolderAction),
registry.registerWorkbenchAction(SyncActionDescriptor.from(GlobalRemoveRootFolderAction), 'Workspaces: Remove Folder from Workspace...', workspacesCategory);
registry.registerWorkbenchAction(SyncActionDescriptor.from(CloseWorkspaceAction, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_F) }), 'Workspaces: Close Workspace', workspacesCategory, EmptyWorkspaceSupportContext);
registry.registerWorkbenchAction(SyncActionDescriptor.from(SaveWorkspaceAsAction), 'Workspaces: Save Workspace As...', workspacesCategory, EmptyWorkspaceSupportContext);
registry.registerWorkbenchAction(SyncActionDescriptor.from(DuplicateWorkspaceInNewWindowAction), 'Workspaces: Duplicate Workspace in New Window', workspacesCategory);
registry.registerWorkbenchAction(SyncActionDescriptor.from(DuplicateWorkspaceInNewWindowAction), 'Workspaces: Duplicate As Workspace in New Window', workspacesCategory);
// --- Menu Registration

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { Widget } from 'vs/base/browser/ui/widget';
import { IOverlayWidget, ICodeEditor, IOverlayWidgetPosition, OverlayWidgetPositionPreference } from 'vs/editor/browser/editorBrowser';
import { IOverlayWidget, ICodeEditor, IOverlayWidgetPosition, OverlayWidgetPositionPreference, isCodeEditor, isCompositeEditor } from 'vs/editor/browser/editorBrowser';
import { Emitter } from 'vs/base/common/event';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IThemeService } from 'vs/platform/theme/common/themeService';
@@ -15,11 +15,121 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
import { IHostService } from 'vs/workbench/services/host/browser/host';
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { hasWorkspaceFileExtension } from 'vs/platform/workspaces/common/workspaces';
import { Disposable, dispose } from 'vs/base/common/lifecycle';
import { Disposable, DisposableStore, dispose } from 'vs/base/common/lifecycle';
import { localize } from 'vs/nls';
import { IEditorContribution } from 'vs/editor/common/editorCommon';
import { isEqual } from 'vs/base/common/resources';
import { IFileService } from 'vs/platform/files/common/files';
import { URI } from 'vs/base/common/uri';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IRange } from 'vs/editor/common/core/range';
import { CursorChangeReason, ICursorPositionChangedEvent } from 'vs/editor/common/controller/cursorEvents';
import { ModelDecorationOptions } from 'vs/editor/common/model/textModel';
import { TrackedRangeStickiness, IModelDecorationsChangeAccessor } from 'vs/editor/common/model';
export interface IRangeHighlightDecoration {
resource: URI;
range: IRange;
isWholeLine?: boolean;
}
export class RangeHighlightDecorations extends Disposable {
private readonly _onHighlightRemoved = this._register(new Emitter<void>());
readonly onHighlightRemoved = this._onHighlightRemoved.event;
private rangeHighlightDecorationId: string | null = null;
private editor: ICodeEditor | null = null;
private readonly editorDisposables = this._register(new DisposableStore());
constructor(@IEditorService private readonly editorService: IEditorService) {
super();
}
removeHighlightRange() {
if (this.editor && this.editor.getModel() && this.rangeHighlightDecorationId) {
this.editor.deltaDecorations([this.rangeHighlightDecorationId], []);
this._onHighlightRemoved.fire();
}
this.rangeHighlightDecorationId = null;
}
highlightRange(range: IRangeHighlightDecoration, editor?: any) {
editor = editor ?? this.getEditor(range);
if (isCodeEditor(editor)) {
this.doHighlightRange(editor, range);
} else if (isCompositeEditor(editor) && isCodeEditor(editor.activeCodeEditor)) {
this.doHighlightRange(editor.activeCodeEditor, range);
}
}
private doHighlightRange(editor: ICodeEditor, selectionRange: IRangeHighlightDecoration) {
this.removeHighlightRange();
editor.changeDecorations((changeAccessor: IModelDecorationsChangeAccessor) => {
this.rangeHighlightDecorationId = changeAccessor.addDecoration(selectionRange.range, this.createRangeHighlightDecoration(selectionRange.isWholeLine));
});
this.setEditor(editor);
}
private getEditor(resourceRange: IRangeHighlightDecoration): ICodeEditor | undefined {
const activeEditor = this.editorService.activeEditor;
const resource = activeEditor && activeEditor.resource;
if (resource && isEqual(resource, resourceRange.resource)) {
return this.editorService.activeTextEditorControl as ICodeEditor;
}
return undefined;
}
private setEditor(editor: ICodeEditor) {
if (this.editor !== editor) {
this.editorDisposables.clear();
this.editor = editor;
this.editorDisposables.add(this.editor.onDidChangeCursorPosition((e: ICursorPositionChangedEvent) => {
if (
e.reason === CursorChangeReason.NotSet
|| e.reason === CursorChangeReason.Explicit
|| e.reason === CursorChangeReason.Undo
|| e.reason === CursorChangeReason.Redo
) {
this.removeHighlightRange();
}
}));
this.editorDisposables.add(this.editor.onDidChangeModel(() => { this.removeHighlightRange(); }));
this.editorDisposables.add(this.editor.onDidDispose(() => {
this.removeHighlightRange();
this.editor = null;
}));
}
}
private static readonly _WHOLE_LINE_RANGE_HIGHLIGHT = ModelDecorationOptions.register({
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
className: 'rangeHighlight',
isWholeLine: true
});
private static readonly _RANGE_HIGHLIGHT = ModelDecorationOptions.register({
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
className: 'rangeHighlight'
});
private createRangeHighlightDecoration(isWholeLine: boolean = true): ModelDecorationOptions {
return (isWholeLine ? RangeHighlightDecorations._WHOLE_LINE_RANGE_HIGHLIGHT : RangeHighlightDecorations._RANGE_HIGHLIGHT);
}
dispose() {
super.dispose();
if (this.editor && this.editor.getModel()) {
this.removeHighlightRange();
this.editor = null;
}
}
}
export class FloatingClickWidget extends Widget implements IOverlayWidget {

View File

@@ -56,15 +56,26 @@ export abstract class Composite extends Component implements IComposite {
return this._onDidBlur.event;
}
private _hasFocus = false;
hasFocus(): boolean {
return this._hasFocus;
}
private registerFocusTrackEvents(): { onDidFocus: Emitter<void>, onDidBlur: Emitter<void> } {
const container = assertIsDefined(this.getContainer());
const focusTracker = this._register(trackFocus(container));
const onDidFocus = this._onDidFocus = this._register(new Emitter<void>());
this._register(focusTracker.onDidFocus(() => onDidFocus.fire()));
this._register(focusTracker.onDidFocus(() => {
this._hasFocus = true;
onDidFocus.fire();
}));
const onDidBlur = this._onDidBlur = this._register(new Emitter<void>());
this._register(focusTracker.onDidBlur(() => onDidBlur.fire()));
this._register(focusTracker.onDidBlur(() => {
this._hasFocus = false;
onDidBlur.fire();
}));
return { onDidFocus, onDidBlur };
}
@@ -234,7 +245,6 @@ export abstract class CompositeDescriptor<T extends Composite> {
readonly cssClass?: string,
readonly order?: number,
readonly requestedIndex?: number,
readonly keybindingId?: string,
) { }
instantiate(instantiationService: IInstantiationService): T {

View File

@@ -17,7 +17,7 @@ import { WorkbenchState, IWorkspaceContextService } from 'vs/platform/workspace/
import { SideBarVisibleContext } from 'vs/workbench/common/viewlet';
import { IWorkbenchLayoutService, Parts, positionToString } from 'vs/workbench/services/layout/browser/layoutService';
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
import { PanelPositionContext } from 'vs/workbench/common/panel';
import { PanelMaximizedContext, PanelPositionContext, PanelVisibleContext } from 'vs/workbench/common/panel';
import { getRemoteName } from 'vs/platform/remote/common/remoteHosts';
import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService';
import { isNative } from 'vs/base/common/platform';
@@ -64,6 +64,8 @@ export class WorkbenchContextKeysHandler extends Disposable {
private sideBarVisibleContext: IContextKey<boolean>;
private editorAreaVisibleContext: IContextKey<boolean>;
private panelPositionContext: IContextKey<string>;
private panelVisibleContext: IContextKey<boolean>;
private panelMaximizedContext: IContextKey<boolean>;
constructor(
@IContextKeyService private readonly contextKeyService: IContextKeyService,
@@ -146,9 +148,13 @@ export class WorkbenchContextKeysHandler extends Disposable {
// Sidebar
this.sideBarVisibleContext = SideBarVisibleContext.bindTo(this.contextKeyService);
// Panel Position
// Panel
this.panelPositionContext = PanelPositionContext.bindTo(this.contextKeyService);
this.panelPositionContext.set(positionToString(this.layoutService.getPanelPosition()));
this.panelVisibleContext = PanelVisibleContext.bindTo(this.contextKeyService);
this.panelVisibleContext.set(this.layoutService.isVisible(Parts.PANEL_PART));
this.panelMaximizedContext = PanelMaximizedContext.bindTo(this.contextKeyService);
this.panelMaximizedContext.set(this.layoutService.isPanelMaximized());
this.registerListeners();
}
@@ -182,7 +188,11 @@ export class WorkbenchContextKeysHandler extends Disposable {
this._register(this.viewletService.onDidViewletClose(() => this.updateSideBarContextKeys()));
this._register(this.viewletService.onDidViewletOpen(() => this.updateSideBarContextKeys()));
this._register(this.layoutService.onPartVisibilityChange(() => this.editorAreaVisibleContext.set(this.layoutService.isVisible(Parts.EDITOR_PART))));
this._register(this.layoutService.onPartVisibilityChange(() => {
this.editorAreaVisibleContext.set(this.layoutService.isVisible(Parts.EDITOR_PART));
this.panelVisibleContext.set(this.layoutService.isVisible(Parts.PANEL_PART));
this.panelMaximizedContext.set(this.layoutService.isPanelMaximized());
}));
this._register(this.workingCopyService.onDidChangeDirty(workingCopy => this.dirtyWorkingCopiesContext.set(workingCopy.isDirty() || this.workingCopyService.hasDirty)));
}

View File

@@ -737,7 +737,7 @@ export class CompositeDragAndDropObserver extends Disposable {
if (callbacks.onDragEnd) {
this._onDragEnd.event(e => {
callbacks.onDragEnd!(e);
});
}, this, disposableStore);
}
return this._register(disposableStore);
}

View File

@@ -12,7 +12,6 @@ import { insert } from 'vs/base/common/arrays';
import { IDisposable, toDisposable } from 'vs/base/common/lifecycle';
export interface IEditorDescriptor {
getId(): string;
getName(): string;

View File

@@ -100,6 +100,10 @@ export const DEFAULT_LABELS_CONTAINER: IResourceLabelsContainer = {
};
export class ResourceLabels extends Disposable {
private _onDidChangeDecorations = this._register(new Emitter<void>());
readonly onDidChangeDecorations = this._onDidChangeDecorations.event;
private widgets: ResourceLabelWidget[] = [];
private labels: IResourceLabel[] = [];
@@ -148,7 +152,18 @@ export class ResourceLabels extends Disposable {
}));
// notify when file decoration changes
this._register(this.decorationsService.onDidChangeDecorations(e => this.widgets.forEach(widget => widget.notifyFileDecorationsChanges(e))));
this._register(this.decorationsService.onDidChangeDecorations(e => {
let notifyDidChangeDecorations = false;
this.widgets.forEach(widget => {
if (widget.notifyFileDecorationsChanges(e)) {
notifyDidChangeDecorations = true;
}
});
if (notifyDidChangeDecorations) {
this._onDidChangeDecorations.fire();
}
}));
// notify when theme changes
this._register(this.themeService.onDidColorThemeChange(() => this.widgets.forEach(widget => widget.notifyThemeChange())));
@@ -311,19 +326,21 @@ class ResourceLabelWidget extends IconLabel {
}
}
notifyFileDecorationsChanges(e: IResourceDecorationChangeEvent): void {
notifyFileDecorationsChanges(e: IResourceDecorationChangeEvent): boolean {
if (!this.options) {
return;
return false;
}
const resource = toResource(this.label);
if (!resource) {
return;
return false;
}
if (this.options.fileDecorations && e.affectsResource(resource)) {
this.render(false);
return this.render(false);
}
return false;
}
notifyExtensionsRegistered(): void {
@@ -465,7 +482,7 @@ class ResourceLabelWidget extends IconLabel {
this.setLabel('');
}
private render(clearIconCache: boolean): void {
private render(clearIconCache: boolean): boolean {
if (this.isHidden) {
if (!this.needsRedraw) {
this.needsRedraw = clearIconCache ? Redraw.Full : Redraw.Basic;
@@ -475,7 +492,7 @@ class ResourceLabelWidget extends IconLabel {
this.needsRedraw = Redraw.Full;
}
return;
return false;
}
if (this.label) {
@@ -492,7 +509,7 @@ class ResourceLabelWidget extends IconLabel {
}
if (!this.label) {
return;
return false;
}
this.renderDisposables.clear();
@@ -558,6 +575,8 @@ class ResourceLabelWidget extends IconLabel {
this.setLabel(label || '', this.label.description, iconLabelOptions);
this._onDidRender.fire();
return true;
}
dispose(): void {

View File

@@ -78,7 +78,9 @@ enum Storage {
GRID_LAYOUT = 'workbench.grid.layout',
GRID_WIDTH = 'workbench.grid.width',
GRID_HEIGHT = 'workbench.grid.height'
GRID_HEIGHT = 'workbench.grid.height',
MENU_VISIBILITY = 'window.menuBarVisibility'
}
enum Classes {
@@ -321,9 +323,12 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
if (this.state.fullscreen && (this.state.menuBar.visibility === 'toggle' || this.state.menuBar.visibility === 'default')) {
// Propagate to grid
this.workbenchGrid.setViewVisible(this.titleBarPartView, this.isVisible(Parts.TITLEBAR_PART));
this.layout();
}
// Move layout call to any time the menubar
// is toggled to update consumers of offset
// see issue #115267
this.layout();
}
}
@@ -622,7 +627,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
return this._openedDefaultEditors;
}
private getInitialFilesToOpen(): { filesToOpenOrCreate?: IPath[], filesToDiff?: IPath[] } | undefined {
private getInitialFilesToOpen(): { filesToOpenOrCreate?: IPath[], filesToDiff?: IPath[]; } | undefined {
const defaultLayout = this.environmentService.options?.defaultLayout;
if (defaultLayout?.editors?.length && this.storageService.isNew(StorageScope.WORKSPACE)) {
this._openedDefaultEditors = true;
@@ -652,7 +657,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
// Restore editors
restorePromises.push((async () => {
mark('willRestoreEditors');
mark('code/willRestoreEditors');
// first ensure the editor part is restored
await this.editorGroupService.whenRestored;
@@ -669,17 +674,17 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
await this.editorService.openEditors(editors);
}
mark('didRestoreEditors');
mark('code/didRestoreEditors');
})());
// Restore default views
const restoreDefaultViewsPromise = (async () => {
if (this.state.views.defaults?.length) {
mark('willOpenDefaultViews');
mark('code/willOpenDefaultViews');
let locationsRestored: { id: string; order: number }[] = [];
let locationsRestored: { id: string; order: number; }[] = [];
const tryOpenView = (view: { id: string; order: number }): boolean => {
const tryOpenView = (view: { id: string; order: number; }): boolean => {
const location = this.viewDescriptorService.getViewLocationById(view.id);
if (location !== null) {
const container = this.viewDescriptorService.getViewContainerByViewId(view.id);
@@ -733,7 +738,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
this.state.panel.panelToRestore = locationsRestored[ViewContainerLocation.Panel].id;
}
mark('didOpenDefaultViews');
mark('code/didOpenDefaultViews');
}
})();
restorePromises.push(restoreDefaultViewsPromise);
@@ -748,14 +753,14 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
return;
}
mark('willRestoreViewlet');
mark('code/willRestoreViewlet');
const viewlet = await this.viewletService.openViewlet(this.state.sideBar.viewletToRestore);
if (!viewlet) {
await this.viewletService.openViewlet(this.viewDescriptorService.getDefaultViewContainer(ViewContainerLocation.Sidebar)?.id); // fallback to default viewlet as needed
}
mark('didRestoreViewlet');
mark('code/didRestoreViewlet');
})());
// Restore Panel
@@ -768,14 +773,14 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
return;
}
mark('willRestorePanel');
mark('code/willRestorePanel');
const panel = await this.panelService.openPanel(this.state.panel.panelToRestore!);
if (!panel) {
await this.panelService.openPanel(Registry.as<PanelRegistry>(PanelExtensions.Panels).getDefaultPanelId()); // fallback to default panel as needed
}
mark('didRestorePanel');
mark('code/didRestorePanel');
})());
// Restore Zen Mode
@@ -1128,7 +1133,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
[Parts.STATUSBAR_PART]: this.statusBarPartView
};
const fromJSON = ({ type }: { type: Parts }) => viewMap[type];
const fromJSON = ({ type }: { type: Parts; }) => viewMap[type];
const workbenchGrid = SerializableGrid.deserialize(
this.createGridDescriptor(),
{ fromJSON },
@@ -1410,7 +1415,29 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
// If panel part becomes visible, show last active panel or default panel
else if (!hidden && !this.panelService.getActivePanel()) {
const panelToOpen = this.panelService.getLastActivePanelId();
let panelToOpen: string | undefined = this.panelService.getLastActivePanelId();
const hasViews = (id: string): boolean => {
const viewContainer = this.viewDescriptorService.getViewContainerById(id);
if (!viewContainer) {
return false;
}
const viewContainerModel = this.viewDescriptorService.getViewContainerModel(viewContainer);
if (!viewContainerModel) {
return false;
}
return viewContainerModel.activeViewDescriptors.length >= 1;
};
// verify that the panel we try to open has views before we default to it
// otherwise fall back to any view that has views still refs #111463
if (!panelToOpen || !hasViews(panelToOpen)) {
panelToOpen = this.viewDescriptorService
.getViewContainersByLocation(ViewContainerLocation.Panel)
.find(viewContainer => hasViews(viewContainer.id))?.id;
}
if (panelToOpen) {
const focus = !skipLayout;
this.panelService.openPanel(panelToOpen, focus);
@@ -1529,6 +1556,24 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
return this.state.menuBar.visibility;
}
toggleMenuBar(): void {
let currentVisibilityValue = getMenuBarVisibility(this.configurationService);
if (typeof currentVisibilityValue !== 'string') {
currentVisibilityValue = 'default';
}
let newVisibilityValue: string;
if (currentVisibilityValue === 'visible' || currentVisibilityValue === 'default') {
newVisibilityValue = 'toggle';
} else if (currentVisibilityValue === 'compact') {
newVisibilityValue = 'hidden';
} else {
newVisibilityValue = (isWeb && currentVisibilityValue === 'hidden') ? 'compact' : 'default';
}
this.configurationService.updateValue(Storage.MENU_VISIBILITY, newVisibilityValue);
}
getPanelPosition(): Position {
return this.state.panel.position;
}

View File

@@ -0,0 +1,99 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IAction } from 'vs/base/common/actions';
import { Disposable, DisposableStore, IDisposable, MutableDisposable } from 'vs/base/common/lifecycle';
import { Emitter, Event } from 'vs/base/common/event';
import { MenuId, IMenuService, IMenu, SubmenuItemAction, IMenuActionOptions } from 'vs/platform/actions/common/actions';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem';
class MenuActions extends Disposable {
private readonly menu: IMenu;
private _primaryActions: IAction[] = [];
get primaryActions() { return this._primaryActions; }
private _secondaryActions: IAction[] = [];
get secondaryActions() { return this._secondaryActions; }
private readonly _onDidChange = this._register(new Emitter<void>());
readonly onDidChange = this._onDidChange.event;
private disposables = this._register(new DisposableStore());
constructor(
menuId: MenuId,
private readonly options: IMenuActionOptions | undefined,
private readonly menuService: IMenuService,
private readonly contextKeyService: IContextKeyService
) {
super();
this.menu = this._register(menuService.createMenu(menuId, contextKeyService));
this._register(this.menu.onDidChange(() => this.updateActions()));
this.updateActions();
}
private updateActions(): void {
this.disposables.clear();
this._primaryActions = [];
this._secondaryActions = [];
this.disposables.add(createAndFillInActionBarActions(this.menu, this.options, { primary: this._primaryActions, secondary: this._secondaryActions }));
this.disposables.add(this.updateSubmenus([...this._primaryActions, ...this._secondaryActions], {}));
this._onDidChange.fire();
}
private updateSubmenus(actions: readonly IAction[], submenus: { [id: number]: IMenu }): IDisposable {
const disposables = new DisposableStore();
for (const action of actions) {
if (action instanceof SubmenuItemAction && !submenus[action.item.submenu.id]) {
const menu = submenus[action.item.submenu.id] = disposables.add(this.menuService.createMenu(action.item.submenu, this.contextKeyService));
disposables.add(menu.onDidChange(() => this.updateActions()));
disposables.add(this.updateSubmenus(action.actions, submenus));
}
}
return disposables;
}
}
export class CompositeMenuActions extends Disposable {
private readonly menuActions: MenuActions;
private readonly contextMenuActionsDisposable = this._register(new MutableDisposable());
private _onDidChange = this._register(new Emitter<void>());
readonly onDidChange: Event<void> = this._onDidChange.event;
constructor(
menuId: MenuId,
private readonly contextMenuId: MenuId | undefined,
private readonly options: IMenuActionOptions | undefined,
@IContextKeyService private readonly contextKeyService: IContextKeyService,
@IMenuService private readonly menuService: IMenuService,
) {
super();
this.menuActions = this._register(new MenuActions(menuId, this.options, menuService, contextKeyService));
this._register(this.menuActions.onDidChange(() => this._onDidChange.fire()));
}
getPrimaryActions(): IAction[] {
return this.menuActions.primaryActions;
}
getSecondaryActions(): IAction[] {
return this.menuActions.secondaryActions;
}
getContextMenuActions(): IAction[] {
const actions: IAction[] = [];
if (this.contextMenuId) {
const menu = this.menuService.createMenu(this.contextMenuId, this.contextKeyService);
this.contextMenuActionsDisposable.value = createAndFillInActionBarActions(menu, this.options, { primary: [], secondary: actions });
menu.dispose();
}
return actions;
}
}

View File

@@ -16,13 +16,9 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace
import { ViewPaneContainer } from './parts/views/viewPaneContainer';
import { IPaneComposite } from 'vs/workbench/common/panecomposite';
import { IAction, IActionViewItem, Separator } from 'vs/base/common/actions';
import { ViewContainerMenuActions } from 'vs/workbench/browser/parts/views/viewMenuActions';
import { MenuId } from 'vs/platform/actions/common/actions';
export class PaneComposite extends Composite implements IPaneComposite {
private menuActions: ViewContainerMenuActions;
constructor(
id: string,
protected readonly viewPaneContainer: ViewPaneContainer,
@@ -35,8 +31,6 @@ export class PaneComposite extends Composite implements IPaneComposite {
@IWorkspaceContextService protected contextService: IWorkspaceContextService
) {
super(id, telemetryService, themeService, storageService);
this.menuActions = this._register(this.instantiationService.createInstance(ViewContainerMenuActions, this.getId(), MenuId.ViewContainerTitleContext));
this._register(this.viewPaneContainer.onTitleAreaUpdate(() => this.updateTitleArea()));
}
@@ -71,22 +65,36 @@ export class PaneComposite extends Composite implements IPaneComposite {
getContextMenuActions(): ReadonlyArray<IAction> {
const result = [];
result.push(...this.menuActions.getContextMenuActions());
result.push(...this.viewPaneContainer.getContextMenuActions2());
if (result.length) {
const otherActions = this.viewPaneContainer.getContextMenuActions();
if (otherActions.length) {
result.push(new Separator());
result.push(...otherActions);
}
result.push(...this.viewPaneContainer.getContextMenuActions());
return result;
}
getActions(): ReadonlyArray<IAction> {
return this.viewPaneContainer.getActions();
const result = [];
result.push(...this.viewPaneContainer.getActions2());
result.push(...this.viewPaneContainer.getActions());
return result;
}
getSecondaryActions(): ReadonlyArray<IAction> {
return this.viewPaneContainer.getSecondaryActions();
const menuActions = this.viewPaneContainer.getSecondaryActions2();
const viewPaneContainerActions = this.viewPaneContainer.getSecondaryActions();
if (menuActions.length && viewPaneContainerActions.length) {
return [
...menuActions,
new Separator(),
...viewPaneContainerActions
];
}
return menuActions.length ? menuActions : viewPaneContainerActions;
}
getActionViewItem(action: IAction): IActionViewItem | undefined {

View File

@@ -6,23 +6,74 @@
import { Registry } from 'vs/platform/registry/common/platform';
import { IPanel } from 'vs/workbench/common/panel';
import { CompositeDescriptor, CompositeRegistry } from 'vs/workbench/browser/composite';
import { IConstructorSignature0, BrandedService } from 'vs/platform/instantiation/common/instantiation';
import { IConstructorSignature0, BrandedService, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { assertIsDefined } from 'vs/base/common/types';
import { PaneComposite } from 'vs/workbench/browser/panecomposite';
import { IAction, Separator } from 'vs/base/common/actions';
import { CompositeMenuActions } from 'vs/workbench/browser/menuActions';
import { MenuId } from 'vs/platform/actions/common/actions';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
export abstract class Panel extends PaneComposite implements IPanel { }
export abstract class Panel extends PaneComposite implements IPanel {
private readonly panelActions: CompositeMenuActions;
constructor(id: string,
viewPaneContainer: ViewPaneContainer,
@ITelemetryService telemetryService: ITelemetryService,
@IStorageService storageService: IStorageService,
@IInstantiationService instantiationService: IInstantiationService,
@IThemeService themeService: IThemeService,
@IContextMenuService contextMenuService: IContextMenuService,
@IExtensionService extensionService: IExtensionService,
@IWorkspaceContextService contextService: IWorkspaceContextService,
) {
super(id, viewPaneContainer, telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService);
this.panelActions = this._register(this.instantiationService.createInstance(CompositeMenuActions, MenuId.PanelTitle, undefined, undefined));
this._register(this.panelActions.onDidChange(() => this.updateTitleArea()));
}
getActions(): ReadonlyArray<IAction> {
return [...super.getActions(), ...this.panelActions.getPrimaryActions()];
}
getSecondaryActions(): ReadonlyArray<IAction> {
return this.mergeSecondaryActions(super.getSecondaryActions(), this.panelActions.getSecondaryActions());
}
getContextMenuActions(): ReadonlyArray<IAction> {
return this.mergeSecondaryActions(super.getContextMenuActions(), this.panelActions.getContextMenuActions());
}
private mergeSecondaryActions(actions: ReadonlyArray<IAction>, panelActions: IAction[]): ReadonlyArray<IAction> {
if (panelActions.length && actions.length) {
return [
...actions,
new Separator(),
...panelActions,
];
}
return panelActions.length ? panelActions : actions;
}
}
/**
* A panel descriptor is a leightweight descriptor of a panel in the workbench.
*/
export class PanelDescriptor extends CompositeDescriptor<Panel> {
static create<Services extends BrandedService[]>(ctor: { new(...services: Services): Panel }, id: string, name: string, cssClass?: string, order?: number, requestedIndex?: number, _commandId?: string): PanelDescriptor {
return new PanelDescriptor(ctor as IConstructorSignature0<Panel>, id, name, cssClass, order, requestedIndex, _commandId);
static create<Services extends BrandedService[]>(ctor: { new(...services: Services): Panel }, id: string, name: string, cssClass?: string, order?: number, requestedIndex?: number): PanelDescriptor {
return new PanelDescriptor(ctor as IConstructorSignature0<Panel>, id, name, cssClass, order, requestedIndex);
}
private constructor(ctor: IConstructorSignature0<Panel>, id: string, name: string, cssClass?: string, order?: number, requestedIndex?: number, _commandId?: string) {
super(ctor, id, name, cssClass, order, requestedIndex, _commandId);
private constructor(ctor: IConstructorSignature0<Panel>, id: string, name: string, cssClass?: string, order?: number, requestedIndex?: number) {
super(ctor, id, name, cssClass, order, requestedIndex);
}
}

View File

@@ -162,7 +162,7 @@ class PartLayout {
if (this.options && this.options.hasTitle) {
titleSize = new Dimension(width, Math.min(height, PartLayout.TITLE_HEIGHT));
} else {
titleSize = new Dimension(0, 0);
titleSize = Dimension.None;
}
let contentWidth = width;

View File

@@ -4,18 +4,18 @@
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/activityaction';
import * as nls from 'vs/nls';
import * as DOM from 'vs/base/browser/dom';
import { localize } from 'vs/nls';
import { EventType, addDisposableListener, EventHelper } from 'vs/base/browser/dom';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { EventType as TouchEventType, GestureEvent } from 'vs/base/browser/touch';
import { Action, IAction, Separator, SubmenuAction } from 'vs/base/common/actions';
import { Action, IAction, Separator, SubmenuAction, toAction } from 'vs/base/common/actions';
import { KeyCode } from 'vs/base/common/keyCodes';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { IMenuService, MenuId, IMenu, registerAction2, Action2, IAction2Options } from 'vs/platform/actions/common/actions';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { activeContrastBorder, focusBorder } from 'vs/platform/theme/common/colorRegistry';
import { ICssStyleCollector, IColorTheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
import { IColorTheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
import { ActivityAction, ActivityActionViewItem, ICompositeBar, ICompositeBarColors, ToggleCompositePinnedAction } from 'vs/workbench/browser/parts/compositeBarActions';
import { CATEGORIES } from 'vs/workbench/common/actions';
import { IActivity } from 'vs/workbench/common/activity';
@@ -29,12 +29,12 @@ import { isMacintosh, isWeb } from 'vs/base/common/platform';
import { getCurrentAuthenticationSessionInfo, IAuthenticationService } from 'vs/workbench/services/authentication/browser/authenticationService';
import { AuthenticationSession } from 'vs/editor/common/modes';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IProductService } from 'vs/platform/product/common/productService';
import { AnchorAlignment, AnchorAxisAlignment } from 'vs/base/browser/ui/contextview/contextview';
import { getTitleBarStyle } from 'vs/platform/windows/common/windows';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
export class ViewContainerActivityAction extends ActivityAction {
@@ -97,10 +97,10 @@ export class ViewContainerActivityAction extends ActivityAction {
private logAction(action: string) {
type ActivityBarActionClassification = {
viewletId: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
action: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
viewletId: { classification: 'SystemMetaData', purpose: 'FeatureInsight'; };
action: { classification: 'SystemMetaData', purpose: 'FeatureInsight'; };
};
this.telemetryService.publicLog2<{ viewletId: String, action: String }, ActivityBarActionClassification>('activityBarAction', { viewletId: this.activity.id, action });
this.telemetryService.publicLog2<{ viewletId: String, action: String; }, ActivityBarActionClassification>('activityBarAction', { viewletId: this.activity.id, action });
}
}
@@ -109,6 +109,7 @@ class MenuActivityActionViewItem extends ActivityActionViewItem {
constructor(
private readonly menuId: MenuId,
action: ActivityAction,
private contextMenuActionsProvider: () => IAction[],
colors: (theme: IColorTheme) => ICompositeBarColors,
@IThemeService themeService: IThemeService,
@IMenuService protected readonly menuService: IMenuService,
@@ -126,30 +127,35 @@ class MenuActivityActionViewItem extends ActivityActionViewItem {
// Context menus are triggered on mouse down so that an item can be picked
// and executed with releasing the mouse over it
this._register(DOM.addDisposableListener(this.container, DOM.EventType.MOUSE_DOWN, (e: MouseEvent) => {
DOM.EventHelper.stop(e, true);
this._register(addDisposableListener(this.container, EventType.MOUSE_DOWN, (e: MouseEvent) => {
EventHelper.stop(e, true);
this.showContextMenu(e);
}));
this._register(DOM.addDisposableListener(this.container, DOM.EventType.KEY_UP, (e: KeyboardEvent) => {
this._register(addDisposableListener(this.container, EventType.KEY_UP, (e: KeyboardEvent) => {
let event = new StandardKeyboardEvent(e);
if (event.equals(KeyCode.Enter) || event.equals(KeyCode.Space)) {
DOM.EventHelper.stop(e, true);
EventHelper.stop(e, true);
this.showContextMenu();
}
}));
this._register(DOM.addDisposableListener(this.container, TouchEventType.Tap, (e: GestureEvent) => {
DOM.EventHelper.stop(e, true);
this._register(addDisposableListener(this.container, TouchEventType.Tap, (e: GestureEvent) => {
EventHelper.stop(e, true);
this.showContextMenu();
}));
}
protected async showContextMenu(e?: MouseEvent): Promise<void> {
private async showContextMenu(e?: MouseEvent): Promise<void> {
const disposables = new DisposableStore();
const menu = disposables.add(this.menuService.createMenu(this.menuId, this.contextKeyService));
const actions = await this.resolveActions(menu, disposables);
let actions: IAction[];
if (e?.button !== 2) {
const menu = disposables.add(this.menuService.createMenu(this.menuId, this.contextKeyService));
actions = await this.resolveMainMenuActions(menu, disposables);
} else {
actions = await this.resolveContextMenuActions(disposables);
}
const isUsingCustomMenu = isWeb || (getTitleBarStyle(this.configurationService) !== 'native' && !isMacintosh); // see #40262
const position = this.configurationService.getValue('workbench.sideBar.location');
@@ -163,13 +169,17 @@ class MenuActivityActionViewItem extends ActivityActionViewItem {
});
}
protected async resolveActions(menu: IMenu, disposables: DisposableStore): Promise<IAction[]> {
protected async resolveMainMenuActions(menu: IMenu, disposables: DisposableStore): Promise<IAction[]> {
const actions: IAction[] = [];
disposables.add(createAndFillInActionBarActions(menu, undefined, { primary: [], secondary: actions }));
return actions;
}
protected async resolveContextMenuActions(disposables: DisposableStore): Promise<IAction[]> {
return this.contextMenuActionsProvider();
}
}
export class HomeActivityActionViewItem extends MenuActivityActionViewItem {
@@ -179,6 +189,7 @@ export class HomeActivityActionViewItem extends MenuActivityActionViewItem {
constructor(
private readonly goHomeHref: string,
action: ActivityAction,
contextMenuActionsProvider: () => IAction[],
colors: (theme: IColorTheme) => ICompositeBarColors,
@IThemeService themeService: IThemeService,
@IMenuService menuService: IMenuService,
@@ -188,27 +199,32 @@ export class HomeActivityActionViewItem extends MenuActivityActionViewItem {
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService,
@IStorageService private readonly storageService: IStorageService
) {
super(MenuId.MenubarHomeMenu, action, colors, themeService, menuService, contextMenuService, contextKeyService, configurationService, environmentService);
super(MenuId.MenubarHomeMenu, action, contextMenuActionsProvider, colors, themeService, menuService, contextMenuService, contextKeyService, configurationService, environmentService);
}
protected async resolveActions(homeMenu: IMenu, disposables: DisposableStore): Promise<IAction[]> {
protected async resolveMainMenuActions(homeMenu: IMenu, disposables: DisposableStore): Promise<IAction[]> {
const actions = [];
// Go Home
actions.push(disposables.add(new Action('goHome', nls.localize('goHome', "Go Home"), undefined, true, async () => window.location.href = this.goHomeHref)));
actions.push(disposables.add(new Separator()));
actions.push(toAction({ id: 'goHome', label: localize('goHome', "Go Home"), run: () => window.location.href = this.goHomeHref }));
// Contributed
const contributedActions = await super.resolveActions(homeMenu, disposables);
actions.push(...contributedActions);
// Hide
if (contributedActions.length > 0) {
const contributedActions = await super.resolveMainMenuActions(homeMenu, disposables);
if (contributedActions.length) {
actions.push(disposables.add(new Separator()));
actions.push(...contributedActions);
}
actions.push(disposables.add(new Action('hide', nls.localize('hide', "Hide"), undefined, true, async () => {
this.storageService.store(HomeActivityActionViewItem.HOME_BAR_VISIBILITY_PREFERENCE, false, StorageScope.GLOBAL, StorageTarget.USER);
})));
return actions;
}
protected async resolveContextMenuActions(disposables: DisposableStore): Promise<IAction[]> {
const actions = await super.resolveContextMenuActions(disposables);
actions.unshift(...[
toAction({ id: 'hideHomeButton', label: localize('hideHomeButton', "Hide Home Button"), run: () => this.storageService.store(HomeActivityActionViewItem.HOME_BAR_VISIBILITY_PREFERENCE, false, StorageScope.GLOBAL, StorageTarget.USER) }),
new Separator()
]);
return actions;
}
@@ -220,6 +236,7 @@ export class AccountsActivityActionViewItem extends MenuActivityActionViewItem {
constructor(
action: ActivityAction,
contextMenuActionsProvider: () => IAction[],
colors: (theme: IColorTheme) => ICompositeBarColors,
@IThemeService themeService: IThemeService,
@IContextMenuService contextMenuService: IContextMenuService,
@@ -227,15 +244,15 @@ export class AccountsActivityActionViewItem extends MenuActivityActionViewItem {
@IContextKeyService contextKeyService: IContextKeyService,
@IAuthenticationService private readonly authenticationService: IAuthenticationService,
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService,
@IStorageService private readonly storageService: IStorageService,
@IProductService private readonly productService: IProductService,
@IConfigurationService configurationService: IConfigurationService,
@IStorageService private readonly storageService: IStorageService
) {
super(MenuId.AccountsContext, action, colors, themeService, menuService, contextMenuService, contextKeyService, configurationService, environmentService);
super(MenuId.AccountsContext, action, contextMenuActionsProvider, colors, themeService, menuService, contextMenuService, contextKeyService, configurationService, environmentService);
}
protected async resolveActions(accountsMenu: IMenu, disposables: DisposableStore): Promise<IAction[]> {
await super.resolveActions(accountsMenu, disposables);
protected async resolveMainMenuActions(accountsMenu: IMenu, disposables: DisposableStore): Promise<IAction[]> {
await super.resolveMainMenuActions(accountsMenu, disposables);
const otherCommands = accountsMenu.getActions();
const providers = this.authenticationService.getProviderIds();
@@ -243,7 +260,7 @@ export class AccountsActivityActionViewItem extends MenuActivityActionViewItem {
try {
const sessions = await this.authenticationService.getSessions(providerId);
const groupedSessions: { [label: string]: AuthenticationSession[] } = {};
const groupedSessions: { [label: string]: AuthenticationSession[]; } = {};
sessions.forEach(session => {
if (groupedSessions[session.account.label]) {
groupedSessions[session.account.label].push(session);
@@ -266,11 +283,11 @@ export class AccountsActivityActionViewItem extends MenuActivityActionViewItem {
if (sessionInfo.sessions) {
Object.keys(sessionInfo.sessions).forEach(accountName => {
const manageExtensionsAction = disposables.add(new Action(`configureSessions${accountName}`, nls.localize('manageTrustedExtensions', "Manage Trusted Extensions"), '', true, () => {
const manageExtensionsAction = disposables.add(new Action(`configureSessions${accountName}`, localize('manageTrustedExtensions', "Manage Trusted Extensions"), '', true, () => {
return this.authenticationService.manageTrustedExtensionsForAccount(sessionInfo.providerId, accountName);
}));
const signOutAction = disposables.add(new Action('signOut', nls.localize('signOut', "Sign Out"), '', true, () => {
const signOutAction = disposables.add(new Action('signOut', localize('signOut', "Sign Out"), '', true, () => {
return this.authenticationService.signOutOfAccount(sessionInfo.providerId, accountName);
}));
@@ -285,7 +302,7 @@ export class AccountsActivityActionViewItem extends MenuActivityActionViewItem {
menus.push(providerSubMenu);
});
} else {
const providerUnavailableAction = disposables.add(new Action('providerUnavailable', nls.localize('authProviderUnavailable', '{0} is currently unavailable', providerDisplayName)));
const providerUnavailableAction = disposables.add(new Action('providerUnavailable', localize('authProviderUnavailable', '{0} is currently unavailable', providerDisplayName)));
menus.push(providerUnavailableAction);
}
});
@@ -302,22 +319,26 @@ export class AccountsActivityActionViewItem extends MenuActivityActionViewItem {
}
});
if (menus.length) {
menus.push(disposables.add(new Separator()));
}
menus.push(disposables.add(new Action('hide', nls.localize('hide', "Hide"), undefined, true, async () => {
this.storageService.store(AccountsActivityActionViewItem.ACCOUNTS_VISIBILITY_PREFERENCE_KEY, false, StorageScope.GLOBAL, StorageTarget.USER);
})));
return menus;
}
protected async resolveContextMenuActions(disposables: DisposableStore): Promise<IAction[]> {
const actions = await super.resolveContextMenuActions(disposables);
actions.unshift(...[
toAction({ id: 'hideAccounts', label: localize('hideAccounts', "Hide Accounts"), run: () => this.storageService.store(AccountsActivityActionViewItem.ACCOUNTS_VISIBILITY_PREFERENCE_KEY, false, StorageScope.GLOBAL, StorageTarget.USER) }),
new Separator()
]);
return actions;
}
}
export class GlobalActivityActionViewItem extends MenuActivityActionViewItem {
constructor(
action: ActivityAction,
contextMenuActionsProvider: () => IAction[],
colors: (theme: IColorTheme) => ICompositeBarColors,
@IThemeService themeService: IThemeService,
@IMenuService menuService: IMenuService,
@@ -326,7 +347,7 @@ export class GlobalActivityActionViewItem extends MenuActivityActionViewItem {
@IConfigurationService configurationService: IConfigurationService,
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService
) {
super(MenuId.GlobalActivity, action, colors, themeService, menuService, contextMenuService, contextKeyService, configurationService, environmentService);
super(MenuId.GlobalActivity, action, contextMenuActionsProvider, colors, themeService, menuService, contextMenuService, contextKeyService, configurationService, environmentService);
}
}
@@ -379,7 +400,7 @@ registerAction2(
constructor() {
super({
id: 'workbench.action.previousSideBarView',
title: { value: nls.localize('previousSideBarView', "Previous Side Bar View"), original: 'Previous Side Bar View' },
title: { value: localize('previousSideBarView', "Previous Side Bar View"), original: 'Previous Side Bar View' },
category: CATEGORIES.View,
f1: true
}, -1);
@@ -392,7 +413,7 @@ registerAction2(
constructor() {
super({
id: 'workbench.action.nextSideBarView',
title: { value: nls.localize('nextSideBarView', "Next Side Bar View"), original: 'Next Side Bar View' },
title: { value: localize('nextSideBarView', "Next Side Bar View"), original: 'Next Side Bar View' },
category: CATEGORIES.View,
f1: true
}, 1);
@@ -400,7 +421,7 @@ registerAction2(
}
);
registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => {
registerThemingParticipant((theme, collector) => {
const activityBarBackgroundColor = theme.getColor(ACTIVITY_BAR_BACKGROUND);
if (activityBarBackgroundColor) {
collector.addRule(`

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/activitybarpart';
import * as nls from 'vs/nls';
import { localize } from 'vs/nls';
import { ActionsOrientation, ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
import { GLOBAL_ACTIVITY_ID, IActivity, ACCOUNTS_ACTIVITY_ID } from 'vs/workbench/common/activity';
import { Part } from 'vs/workbench/browser/part';
@@ -13,7 +13,7 @@ import { IBadge, NumberBadge } from 'vs/workbench/services/activity/common/activ
import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService';
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { IDisposable, toDisposable, DisposableStore, Disposable } from 'vs/base/common/lifecycle';
import { ToggleActivityBarVisibilityAction, ToggleMenuBarAction, ToggleSidebarPositionAction } from 'vs/workbench/browser/actions/layoutActions';
import { ToggleActivityBarVisibilityAction, ToggleSidebarPositionAction } from 'vs/workbench/browser/actions/layoutActions';
import { IThemeService, IColorTheme, ThemeIcon } from 'vs/platform/theme/common/themeService';
import { ACTIVITY_BAR_BACKGROUND, ACTIVITY_BAR_BORDER, ACTIVITY_BAR_FOREGROUND, ACTIVITY_BAR_ACTIVE_BORDER, ACTIVITY_BAR_BADGE_BACKGROUND, ACTIVITY_BAR_BADGE_FOREGROUND, ACTIVITY_BAR_INACTIVE_FOREGROUND, ACTIVITY_BAR_ACTIVE_BACKGROUND, ACTIVITY_BAR_DRAG_AND_DROP_BORDER } from 'vs/workbench/common/theme';
import { contrastBorder } from 'vs/platform/theme/common/colorRegistry';
@@ -23,33 +23,34 @@ import { IStorageService, StorageScope, IStorageValueChangeEvent, StorageTarget
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { URI, UriComponents } from 'vs/base/common/uri';
import { ToggleCompositePinnedAction, ICompositeBarColors, ActivityAction, ICompositeActivity } from 'vs/workbench/browser/parts/compositeBarActions';
import { IViewDescriptorService, ViewContainer, TEST_VIEW_CONTAINER_ID, IViewContainerModel, ViewContainerLocation, IViewsService } from 'vs/workbench/common/views';
import { IViewDescriptorService, ViewContainer, IViewContainerModel, ViewContainerLocation, IViewsService } from 'vs/workbench/common/views';
import { IContextKeyService, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { assertIsDefined } from 'vs/base/common/types';
import { assertIsDefined, isString } from 'vs/base/common/types';
import { IActivityBarService } from 'vs/workbench/services/activityBar/browser/activityBarService';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { Schemas } from 'vs/base/common/network';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { CustomMenubarControl } from 'vs/workbench/browser/parts/titlebar/menubarControl';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { getMenuBarVisibility } from 'vs/platform/windows/common/windows';
import { isWeb } from 'vs/base/common/platform';
import { isNative, isWeb } from 'vs/base/common/platform';
import { Before2D } from 'vs/workbench/browser/dnd';
import { Codicon, iconRegistry } from 'vs/base/common/codicons';
import { Action, Separator } from 'vs/base/common/actions';
import { IAction, Separator, toAction } from 'vs/base/common/actions';
import { Event } from 'vs/base/common/event';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { KeyCode } from 'vs/base/common/keyCodes';
import { Action2, registerAction2 } from 'vs/platform/actions/common/actions';
import { CATEGORIES } from 'vs/workbench/common/actions';
import { registerIcon } from 'vs/platform/theme/common/iconRegistry';
import { StringSHA1 } from 'vs/base/common/hash';
interface IPlaceholderViewContainer {
readonly id: string;
readonly name?: string;
readonly iconUrl?: UriComponents;
readonly themeIcon?: ThemeIcon;
readonly views?: { when?: string }[];
readonly isBuiltin?: boolean;
readonly views?: { when?: string; }[];
}
interface IPinnedViewContainer {
@@ -66,12 +67,10 @@ interface ICachedViewContainer {
readonly pinned: boolean;
readonly order?: number;
visible: boolean;
views?: { when?: string }[];
isBuiltin?: boolean;
views?: { when?: string; }[];
}
const settingsViewBarIcon = registerIcon('settings-view-bar-icon', Codicon.settingsGear, nls.localize('settingsViewBarIcon', 'Settings icon in the view bar.'));
const accountsViewBarIcon = registerIcon('accounts-view-bar-icon', Codicon.account, nls.localize('accountsViewBarIcon', 'Accounts icon in the view bar.'));
export class ActivitybarPart extends Part implements IActivityBarService {
declare readonly _serviceBrand: undefined;
@@ -81,6 +80,9 @@ export class ActivitybarPart extends Part implements IActivityBarService {
private static readonly ACTION_HEIGHT = 48;
private static readonly ACCOUNTS_ACTION_INDEX = 0;
private static readonly GEAR_ICON = registerIcon('settings-view-bar-icon', Codicon.settingsGear, localize('settingsViewBarIcon', "Settings icon in the view bar."));
private static readonly ACCOUNTS_ICON = registerIcon('accounts-view-bar-icon', Codicon.account, localize('accountsViewBarIcon', "Accounts icon in the view bar."));
//#region IView
readonly minimumWidth: number = 48;
@@ -110,12 +112,13 @@ export class ActivitybarPart extends Part implements IActivityBarService {
private readonly accountsActivity: ICompositeActivity[] = [];
private readonly compositeActions = new Map<string, { activityAction: ViewContainerActivityAction, pinnedAction: ToggleCompositePinnedAction }>();
private readonly compositeActions = new Map<string, { activityAction: ViewContainerActivityAction, pinnedAction: ToggleCompositePinnedAction; }>();
private readonly viewContainerDisposables = new Map<string, IDisposable>();
private readonly keyboardNavigationDisposables = this._register(new DisposableStore());
private readonly location = ViewContainerLocation.Sidebar;
private hasExtensionsRegistered: boolean = false;
constructor(
@IInstantiationService private readonly instantiationService: IInstantiationService,
@@ -132,14 +135,8 @@ export class ActivitybarPart extends Part implements IActivityBarService {
super(Parts.ACTIVITYBAR_PART, { hasTitle: false }, themeService, storageService, layoutService);
for (const cachedViewContainer of this.cachedViewContainers) {
if (
environmentService.remoteAuthority || // In remote window, hide activity bar entries until registered
this.shouldBeHidden(cachedViewContainer.id, cachedViewContainer)
) {
cachedViewContainer.visible = false;
}
cachedViewContainer.visible = !this.shouldBeHidden(cachedViewContainer.id, cachedViewContainer);
}
this.compositeBar = this.createCompositeBar();
this.onDidRegisterViewContainers(this.getViewContainers());
@@ -161,53 +158,66 @@ export class ActivitybarPart extends Part implements IActivityBarService {
icon: true,
orientation: ActionsOrientation.VERTICAL,
preventLoopNavigation: true,
openComposite: (compositeId: string) => this.viewsService.openViewContainer(compositeId, true),
getActivityAction: (compositeId: string) => this.getCompositeActions(compositeId).activityAction,
getCompositePinnedAction: (compositeId: string) => this.getCompositeActions(compositeId).pinnedAction,
getOnCompositeClickAction: (compositeId: string) => new Action(compositeId, '', '', true, () => this.viewsService.isViewContainerVisible(compositeId) ? Promise.resolve(this.viewsService.closeViewContainer(compositeId)) : this.viewsService.openViewContainer(compositeId)),
getContextMenuActions: () => {
const actions = [];
openComposite: compositeId => this.viewsService.openViewContainer(compositeId, true),
getActivityAction: compositeId => this.getCompositeActions(compositeId).activityAction,
getCompositePinnedAction: compositeId => this.getCompositeActions(compositeId).pinnedAction,
getOnCompositeClickAction: compositeId => toAction({ id: compositeId, label: '', run: async () => this.viewsService.isViewContainerVisible(compositeId) ? this.viewsService.closeViewContainer(compositeId) : this.viewsService.openViewContainer(compositeId) }),
fillExtraContextMenuActions: actions => {
// Home
const topActions: IAction[] = [];
if (this.homeBarContainer) {
actions.push(new Action(
'toggleHomeBarAction',
this.homeBarVisibilityPreference ? nls.localize('hideHomeBar', "Hide Home Button") : nls.localize('showHomeBar', "Show Home Button"),
undefined,
true,
async () => { this.homeBarVisibilityPreference = !this.homeBarVisibilityPreference; }
));
topActions.push({
id: 'toggleHomeBarAction',
label: localize('homeButton', "Home Button"),
class: undefined,
tooltip: localize('homeButton', "Home Button"),
checked: this.homeBarVisibilityPreference,
enabled: true,
run: async () => this.homeBarVisibilityPreference = !this.homeBarVisibilityPreference,
dispose: () => { }
});
}
// Menu
const menuBarVisibility = getMenuBarVisibility(this.configurationService);
if (menuBarVisibility === 'compact' || (menuBarVisibility === 'hidden' && isWeb)) {
actions.push(this.instantiationService.createInstance(ToggleMenuBarAction, ToggleMenuBarAction.ID, menuBarVisibility === 'compact' ? nls.localize('hideMenu', "Hide Menu") : nls.localize('showMenu', "Show Menu")));
topActions.push({
id: 'toggleMenuVisibility',
label: localize('menu', "Menu"),
class: undefined,
tooltip: localize('menu', "Menu"),
checked: menuBarVisibility === 'compact',
enabled: true,
run: async () => this.layoutService.toggleMenuBar(),
dispose: () => { }
});
}
if (topActions.length) {
actions.unshift(...topActions, new Separator());
}
// Accounts
actions.push(new Action(
'toggleAccountsVisibility',
this.accountsVisibilityPreference ? nls.localize('hideAccounts', "Hide Accounts") : nls.localize('showAccounts', "Show Accounts"),
undefined,
true,
async () => { this.accountsVisibilityPreference = !this.accountsVisibilityPreference; }
));
actions.push(new Separator());
actions.push({
id: 'toggleAccountsVisibility',
label: localize('accounts', "Accounts"),
class: undefined,
tooltip: localize('accounts', "Accounts"),
checked: this.accountsVisibilityPreference,
enabled: true,
run: async () => this.accountsVisibilityPreference = !this.accountsVisibilityPreference,
dispose: () => { }
});
actions.push(new Separator());
// Toggle Sidebar
actions.push(this.instantiationService.createInstance(ToggleSidebarPositionAction, ToggleSidebarPositionAction.ID, ToggleSidebarPositionAction.getLabel(this.layoutService)));
// Toggle Activity Bar
actions.push(new Action(
ToggleActivityBarVisibilityAction.ID,
nls.localize('hideActivitBar', "Hide Activity Bar"),
undefined,
true,
async () => { this.instantiationService.invokeFunction(accessor => new ToggleActivityBarVisibilityAction().run(accessor)); }
));
return actions;
actions.push(toAction({ id: ToggleActivityBarVisibilityAction.ID, label: localize('hideActivitBar', "Hide Activity Bar"), run: async () => this.instantiationService.invokeFunction(accessor => new ToggleActivityBarVisibilityAction().run(accessor)) }));
},
getContextMenuActionsForComposite: compositeId => this.getContextMenuActionsForComposite(compositeId),
getDefaultCompositeId: () => this.viewDescriptorService.getDefaultViewContainer(this.location)!.id,
@@ -223,24 +233,20 @@ export class ActivitybarPart extends Part implements IActivityBarService {
}));
}
private getContextMenuActionsForComposite(compositeId: string): Action[] {
const actions = [];
private getContextMenuActionsForComposite(compositeId: string): IAction[] {
const actions: IAction[] = [];
const viewContainer = this.viewDescriptorService.getViewContainerById(compositeId)!;
const defaultLocation = this.viewDescriptorService.getDefaultViewContainerLocation(viewContainer)!;
if (defaultLocation !== this.viewDescriptorService.getViewContainerLocation(viewContainer)) {
actions.push(new Action('resetLocationAction', nls.localize('resetLocation', "Reset Location"), undefined, true, async () => {
this.viewDescriptorService.moveViewContainerToLocation(viewContainer, defaultLocation);
}));
actions.push(toAction({ id: 'resetLocationAction', label: localize('resetLocation', "Reset Location"), run: () => this.viewDescriptorService.moveViewContainerToLocation(viewContainer, defaultLocation) }));
} else {
const viewContainerModel = this.viewDescriptorService.getViewContainerModel(viewContainer);
if (viewContainerModel.allViewDescriptors.length === 1) {
const viewToReset = viewContainerModel.allViewDescriptors[0];
const defaultContainer = this.viewDescriptorService.getDefaultContainerById(viewToReset.id)!;
if (defaultContainer !== viewContainer) {
actions.push(new Action('resetLocationAction', nls.localize('resetLocation', "Reset Location"), undefined, true, async () => {
this.viewDescriptorService.moveViewsToContainer([viewToReset], defaultContainer);
}));
actions.push(toAction({ id: 'resetLocationAction', label: localize('resetLocation', "Reset Location"), run: () => this.viewDescriptorService.moveViewsToContainer([viewToReset], defaultContainer) }));
}
}
}
@@ -278,7 +284,7 @@ export class ActivitybarPart extends Part implements IActivityBarService {
}));
}
private onDidChangeViewContainers(added: ReadonlyArray<{ container: ViewContainer, location: ViewContainerLocation }>, removed: ReadonlyArray<{ container: ViewContainer, location: ViewContainerLocation }>) {
private onDidChangeViewContainers(added: ReadonlyArray<{ container: ViewContainer, location: ViewContainerLocation; }>, removed: ReadonlyArray<{ container: ViewContainer, location: ViewContainerLocation; }>) {
removed.filter(({ location }) => location === ViewContainerLocation.Sidebar).forEach(({ container }) => this.onDidDeregisterViewContainer(container));
this.onDidRegisterViewContainers(added.filter(({ location }) => location === ViewContainerLocation.Sidebar).map(({ container }) => container));
}
@@ -310,7 +316,22 @@ export class ActivitybarPart extends Part implements IActivityBarService {
}
private onDidRegisterExtensions(): void {
this.removeNotExistingComposites();
this.hasExtensionsRegistered = true;
// show/hide/remove composites
for (const { id } of this.cachedViewContainers) {
const viewContainer = this.getViewContainer(id);
if (viewContainer) {
this.showOrHideViewContainer(viewContainer);
} else {
if (this.viewDescriptorService.isViewContainerRemovedPermanently(id)) {
this.removeComposite(id);
} else {
this.hideComposite(id);
}
}
}
this.saveCachedViewContainers();
}
@@ -322,7 +343,7 @@ export class ActivitybarPart extends Part implements IActivityBarService {
this.compositeBar.addComposite(viewContainer);
this.compositeBar.activateComposite(viewContainer.id);
if (viewContainer.hideIfEmpty) {
if (this.shouldBeHidden(viewContainer)) {
const viewContainerModel = this.viewDescriptorService.getViewContainerModel(viewContainer);
if (viewContainerModel.activeViewDescriptors.length === 0) {
// Update the composite bar by hiding
@@ -460,7 +481,8 @@ export class ActivitybarPart extends Part implements IActivityBarService {
// Home action bar
const homeIndicator = this.environmentService.options?.homeIndicator;
if (homeIndicator) {
// TODO @sbatten remove the fake setting and associated code
if (homeIndicator && this.configurationService.getValue<boolean>('window.showHomeIndicator')) {
let codicon = iconRegistry.get(homeIndicator.icon);
if (!codicon) {
codicon = Codicon.code;
@@ -556,14 +578,14 @@ export class ActivitybarPart extends Part implements IActivityBarService {
private createHomeBar(href: string, icon: Codicon): void {
this.homeBarContainer = document.createElement('div');
this.homeBarContainer.setAttribute('aria-label', nls.localize('homeIndicator', "Home"));
this.homeBarContainer.setAttribute('aria-label', localize('homeIndicator', "Home"));
this.homeBarContainer.setAttribute('role', 'toolbar');
this.homeBarContainer.classList.add('home-bar');
this.homeBar = this._register(new ActionBar(this.homeBarContainer, {
actionViewItemProvider: action => this.instantiationService.createInstance(HomeActivityActionViewItem, href, action as ActivityAction, (theme: IColorTheme) => this.getActivitybarItemColors(theme)),
actionViewItemProvider: action => this.instantiationService.createInstance(HomeActivityActionViewItem, href, action as ActivityAction, () => this.compositeBar.getContextMenuActions(), (theme: IColorTheme) => this.getActivitybarItemColors(theme)),
orientation: ActionsOrientation.VERTICAL,
ariaLabel: nls.localize('home', "Home"),
ariaLabel: localize('home', "Home"),
animated: false,
preventLoopNavigation: true,
ignoreOrientationForPreviousAndNextKey: true
@@ -575,7 +597,7 @@ export class ActivitybarPart extends Part implements IActivityBarService {
this.homeBar.push(this._register(new ActivityAction({
id: 'workbench.actions.home',
name: nls.localize('home', "Home"),
name: localize('home', "Home"),
cssClass: icon.classNames
})));
@@ -587,17 +609,17 @@ export class ActivitybarPart extends Part implements IActivityBarService {
this.globalActivityActionBar = this._register(new ActionBar(container, {
actionViewItemProvider: action => {
if (action.id === 'workbench.actions.manage') {
return this.instantiationService.createInstance(GlobalActivityActionViewItem, action as ActivityAction, (theme: IColorTheme) => this.getActivitybarItemColors(theme));
return this.instantiationService.createInstance(GlobalActivityActionViewItem, action as ActivityAction, () => this.compositeBar.getContextMenuActions(), (theme: IColorTheme) => this.getActivitybarItemColors(theme));
}
if (action.id === 'workbench.actions.accounts') {
return this.instantiationService.createInstance(AccountsActivityActionViewItem, action as ActivityAction, (theme: IColorTheme) => this.getActivitybarItemColors(theme));
return this.instantiationService.createInstance(AccountsActivityActionViewItem, action as ActivityAction, () => this.compositeBar.getContextMenuActions(), (theme: IColorTheme) => this.getActivitybarItemColors(theme));
}
throw new Error(`No view item for action '${action.id}'`);
},
orientation: ActionsOrientation.VERTICAL,
ariaLabel: nls.localize('manage', "Manage"),
ariaLabel: localize('manage', "Manage"),
animated: false,
preventLoopNavigation: true,
ignoreOrientationForPreviousAndNextKey: true
@@ -605,15 +627,15 @@ export class ActivitybarPart extends Part implements IActivityBarService {
this.globalActivityAction = this._register(new ActivityAction({
id: 'workbench.actions.manage',
name: nls.localize('manage', "Manage"),
cssClass: ThemeIcon.asClassName(settingsViewBarIcon)
name: localize('manage', "Manage"),
cssClass: ThemeIcon.asClassName(ActivitybarPart.GEAR_ICON)
}));
if (this.accountsVisibilityPreference) {
this.accountsActivityAction = this._register(new ActivityAction({
id: 'workbench.actions.accounts',
name: nls.localize('accounts', "Accounts"),
cssClass: ThemeIcon.asClassName(accountsViewBarIcon)
name: localize('accounts', "Accounts"),
cssClass: ThemeIcon.asClassName(ActivitybarPart.ACCOUNTS_ICON)
}));
this.globalActivityActionBar.push(this.accountsActivityAction, { index: ActivitybarPart.ACCOUNTS_ACTION_INDEX });
@@ -630,7 +652,7 @@ export class ActivitybarPart extends Part implements IActivityBarService {
} else {
this.accountsActivityAction = this._register(new ActivityAction({
id: 'workbench.actions.accounts',
name: nls.localize('accounts', "Accounts"),
name: localize('accounts', "Accounts"),
cssClass: Codicon.account.classNames
}));
this.globalActivityActionBar.push(this.accountsActivityAction, { index: ActivitybarPart.ACCOUNTS_ACTION_INDEX });
@@ -640,7 +662,7 @@ export class ActivitybarPart extends Part implements IActivityBarService {
this.updateGlobalActivity(ACCOUNTS_ACTIVITY_ID);
}
private getCompositeActions(compositeId: string): { activityAction: ViewContainerActivityAction, pinnedAction: ToggleCompositePinnedAction } {
private getCompositeActions(compositeId: string): { activityAction: ViewContainerActivityAction, pinnedAction: ToggleCompositePinnedAction; } {
let compositeActions = this.compositeActions.get(compositeId);
if (!compositeActions) {
const viewContainer = this.getViewContainer(compositeId);
@@ -666,32 +688,27 @@ export class ActivitybarPart extends Part implements IActivityBarService {
private onDidRegisterViewContainers(viewContainers: ReadonlyArray<ViewContainer>): void {
for (const viewContainer of viewContainers) {
this.compositeBar.addComposite(viewContainer);
// Pin it by default if it is new
const cachedViewContainer = this.cachedViewContainers.filter(({ id }) => id === viewContainer.id)[0];
const visibleViewContainer = this.viewsService.getVisibleViewContainer(this.location);
const isActive = visibleViewContainer?.id === viewContainer.id;
if (isActive || !this.shouldBeHidden(viewContainer.id, cachedViewContainer)) {
this.compositeBar.addComposite(viewContainer);
// Pin it by default if it is new
if (!cachedViewContainer) {
this.compositeBar.pin(viewContainer.id);
}
if (isActive) {
this.compositeBar.activateComposite(viewContainer.id);
}
if (!cachedViewContainer) {
this.compositeBar.pin(viewContainer.id);
}
// Active
const visibleViewContainer = this.viewsService.getVisibleViewContainer(this.location);
if (visibleViewContainer?.id === viewContainer.id) {
this.compositeBar.activateComposite(viewContainer.id);
}
}
for (const viewContainer of viewContainers) {
const viewContainerModel = this.viewDescriptorService.getViewContainerModel(viewContainer);
this.updateActivity(viewContainer, viewContainerModel);
this.onDidChangeActiveViews(viewContainer, viewContainerModel);
this.showOrHideViewContainer(viewContainer);
const disposables = new DisposableStore();
disposables.add(viewContainerModel.onDidChangeContainerInfo(() => this.updateActivity(viewContainer, viewContainerModel)));
disposables.add(viewContainerModel.onDidChangeActiveViewDescriptors(() => this.onDidChangeActiveViews(viewContainer, viewContainerModel)));
disposables.add(viewContainerModel.onDidChangeActiveViewDescriptors(() => this.showOrHideViewContainer(viewContainer)));
this.viewContainerDisposables.set(viewContainer.id, disposables);
}
@@ -728,12 +745,15 @@ export class ActivitybarPart extends Part implements IActivityBarService {
let iconUrl: URI | undefined = undefined;
if (URI.isUri(icon)) {
iconUrl = icon;
cssClass = `activity-${id.replace(/\./g, '-')}`;
const cssUrl = asCSSUrl(icon);
const hash = new StringSHA1();
hash.update(cssUrl);
cssClass = `activity-${id.replace(/\./g, '-')}-${hash.digest()}`;
const iconClass = `.monaco-workbench .activitybar .monaco-action-bar .action-label.${cssClass}`;
createCSSRule(iconClass, `
mask: ${asCSSUrl(icon)} no-repeat 50% 50%;
mask: ${cssUrl} no-repeat 50% 50%;
mask-size: 24px;
-webkit-mask: ${asCSSUrl(icon)} no-repeat 50% 50%;
-webkit-mask: ${cssUrl} no-repeat 50% 50%;
-webkit-mask-size: 24px;
`);
} else if (ThemeIcon.isThemeIcon(icon)) {
@@ -743,36 +763,43 @@ export class ActivitybarPart extends Part implements IActivityBarService {
return { id, name, cssClass, iconUrl, keybindingId };
}
private onDidChangeActiveViews(viewContainer: ViewContainer, viewContainerModel: IViewContainerModel): void {
if (viewContainerModel.activeViewDescriptors.length) {
this.compositeBar.addComposite(viewContainer);
} else if (viewContainer.hideIfEmpty) {
private showOrHideViewContainer(viewContainer: ViewContainer): void {
if (this.shouldBeHidden(viewContainer)) {
this.hideComposite(viewContainer.id);
} else {
this.compositeBar.addComposite(viewContainer);
}
}
private shouldBeHidden(viewContainerId: string, cachedViewContainer?: ICachedViewContainer): boolean {
const viewContainer = this.getViewContainer(viewContainerId);
if (!viewContainer || !viewContainer.hideIfEmpty) {
return false;
}
private shouldBeHidden(viewContainerOrId: string | ViewContainer, cachedViewContainer?: ICachedViewContainer): boolean {
const viewContainer = isString(viewContainerOrId) ? this.getViewContainer(viewContainerOrId) : viewContainerOrId;
const viewContainerId = isString(viewContainerOrId) ? viewContainerOrId : viewContainerOrId.id;
return cachedViewContainer?.views && cachedViewContainer.views.length
? cachedViewContainer.views.every(({ when }) => !!when && !this.contextKeyService.contextMatchesRules(ContextKeyExpr.deserialize(when)))
: viewContainerId === TEST_VIEW_CONTAINER_ID /* Hide Test view container for the first time or it had no views registered before */;
}
private removeNotExistingComposites(): void {
const viewContainers = this.getViewContainers();
for (const { id } of this.cachedViewContainers) {
if (viewContainers.every(viewContainer => viewContainer.id !== id)) {
if (this.viewDescriptorService.isViewContainerRemovedPermanently(id)) {
this.removeComposite(id);
} else {
this.hideComposite(id);
if (viewContainer) {
if (viewContainer.hideIfEmpty) {
if (this.viewDescriptorService.getViewContainerModel(viewContainer).activeViewDescriptors.length > 0) {
return false;
}
} else {
return false;
}
}
// Check cache only if extensions are not yet registered and current window is not native (desktop) remote connection window
if (!this.hasExtensionsRegistered && !(this.environmentService.remoteAuthority && isNative)) {
cachedViewContainer = cachedViewContainer || this.cachedViewContainers.find(({ id }) => id === viewContainerId);
// Show builtin ViewContainer if not registered yet
if (!viewContainer && cachedViewContainer?.isBuiltin) {
return false;
}
if (cachedViewContainer?.views?.length) {
return cachedViewContainer.views.every(({ when }) => !!when && !this.contextKeyService.contextMatchesRules(ContextKeyExpr.deserialize(when)));
}
}
return true;
}
private hideComposite(compositeId: string): void {
@@ -864,7 +891,6 @@ export class ActivitybarPart extends Part implements IActivityBarService {
private getViewContainer(id: string): ViewContainer | undefined {
const viewContainer = this.viewDescriptorService.getViewContainerById(id);
return viewContainer && this.viewDescriptorService.getViewContainerLocation(viewContainer) === this.location ? viewContainer : undefined;
}
@@ -918,22 +944,22 @@ export class ActivitybarPart extends Part implements IActivityBarService {
const viewContainer = this.getViewContainer(compositeItem.id);
if (viewContainer) {
const viewContainerModel = this.viewDescriptorService.getViewContainerModel(viewContainer);
const views: { when: string | undefined }[] = [];
const views: { when: string | undefined; }[] = [];
for (const { when } of viewContainerModel.allViewDescriptors) {
views.push({ when: when ? when.serialize() : undefined });
}
const cacheIcon = URI.isUri(viewContainerModel.icon) ? viewContainerModel.icon.scheme === Schemas.file : true;
state.push({
id: compositeItem.id,
name: viewContainerModel.title,
icon: cacheIcon ? viewContainerModel.icon : undefined,
icon: URI.isUri(viewContainerModel.icon) && this.environmentService.remoteAuthority && isNative ? undefined : viewContainerModel.icon, /* Donot cache uri icons in desktop with remote connection */
views,
pinned: compositeItem.pinned,
order: compositeItem.order,
visible: compositeItem.visible
visible: compositeItem.visible,
isBuiltin: !viewContainer.extensionId
});
} else {
state.push({ id: compositeItem.id, pinned: compositeItem.pinned, order: compositeItem.order, visible: false });
state.push({ id: compositeItem.id, pinned: compositeItem.pinned, order: compositeItem.order, visible: false, isBuiltin: false });
}
}
@@ -948,9 +974,10 @@ export class ActivitybarPart extends Part implements IActivityBarService {
const cachedViewContainer = this._cachedViewContainers.filter(cached => cached.id === placeholderViewContainer.id)[0];
if (cachedViewContainer) {
cachedViewContainer.name = placeholderViewContainer.name;
cachedViewContainer.icon = placeholderViewContainer.themeIcon ? ThemeIcon.revive(placeholderViewContainer.themeIcon) :
cachedViewContainer.icon = placeholderViewContainer.themeIcon ? placeholderViewContainer.themeIcon :
placeholderViewContainer.iconUrl ? URI.revive(placeholderViewContainer.iconUrl) : undefined;
cachedViewContainer.views = placeholderViewContainer.views;
cachedViewContainer.isBuiltin = placeholderViewContainer.isBuiltin;
}
}
}
@@ -966,11 +993,12 @@ export class ActivitybarPart extends Part implements IActivityBarService {
order
})));
this.setPlaceholderViewContainers(cachedViewContainers.map(({ id, icon, name, views }) => (<IPlaceholderViewContainer>{
this.setPlaceholderViewContainers(cachedViewContainers.map(({ id, icon, name, views, isBuiltin }) => (<IPlaceholderViewContainer>{
id,
iconUrl: URI.isUri(icon) ? icon : undefined,
themeIcon: ThemeIcon.isThemeIcon(icon) ? icon : undefined,
name,
isBuiltin,
views
})));
}
@@ -1067,7 +1095,7 @@ class FocusActivityBarAction extends Action2 {
constructor() {
super({
id: 'workbench.action.focusActivityBar',
title: { value: nls.localize('focusActivityBar', "Focus Activity Bar"), original: 'Focus Activity Bar' },
title: { value: localize('focusActivityBar', "Focus Activity Bar"), original: 'Focus Activity Bar' },
category: CATEGORIES.View,
f1: true
});

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
import { Action, IAction, Separator } from 'vs/base/common/actions';
import { IAction, toAction } from 'vs/base/common/actions';
import { illegalArgument } from 'vs/base/common/errors';
import * as arrays from 'vs/base/common/arrays';
import { IDisposable, toDisposable } from 'vs/base/common/lifecycle';
@@ -149,10 +149,10 @@ export interface ICompositeBarOptions {
readonly preventLoopNavigation?: boolean;
getActivityAction: (compositeId: string) => ActivityAction;
getCompositePinnedAction: (compositeId: string) => Action;
getOnCompositeClickAction: (compositeId: string) => Action;
getContextMenuActions: () => Action[];
getContextMenuActionsForComposite: (compositeId: string) => Action[];
getCompositePinnedAction: (compositeId: string) => IAction;
getOnCompositeClickAction: (compositeId: string) => IAction;
fillExtraContextMenuActions: (actions: IAction[]) => void;
getContextMenuActionsForComposite: (compositeId: string) => IAction[];
openComposite: (compositeId: string) => Promise<IComposite | null>;
getDefaultCompositeId: () => string;
hidePart: () => void;
@@ -208,15 +208,15 @@ export class CompositeBar extends Widget implements ICompositeBar {
create(parent: HTMLElement): HTMLElement {
const actionBarDiv = parent.appendChild($('.composite-bar'));
this.compositeSwitcherBar = this._register(new ActionBar(actionBarDiv, {
actionViewItemProvider: (action: IAction) => {
actionViewItemProvider: action => {
if (action instanceof CompositeOverflowActivityAction) {
return this.compositeOverflowActionViewItem;
}
const item = this.model.findItem(action.id);
return item && this.instantiationService.createInstance(
CompositeActionViewItem, action as ActivityAction, item.pinnedAction,
(compositeId: string) => this.options.getContextMenuActionsForComposite(compositeId),
() => this.getContextMenuActions() as Action[],
compositeId => this.options.getContextMenuActionsForComposite(compositeId),
() => this.getContextMenuActions(),
this.options.colors,
this.options.icon,
this.options.dndHandler,
@@ -319,7 +319,7 @@ export class CompositeBar extends Widget implements ICompositeBar {
this.updateCompositeSwitcher();
}
addComposite({ id, name, order, requestedIndex }: { id: string; name: string, order?: number, requestedIndex?: number }): void {
addComposite({ id, name, order, requestedIndex }: { id: string; name: string, order?: number, requestedIndex?: number; }): void {
// Add to the model
if (this.model.add(id, name, order, requestedIndex)) {
this.computeSizes([this.model.findItem(id)]);
@@ -596,7 +596,7 @@ export class CompositeBar extends Widget implements ICompositeBar {
this.compositeOverflowAction,
() => this.getOverflowingComposites(),
() => this.model.activeItem ? this.model.activeItem.id : undefined,
(compositeId: string) => {
compositeId => {
const item = this.model.findItem(compositeId);
return item?.activity[0]?.badge;
},
@@ -610,7 +610,7 @@ export class CompositeBar extends Widget implements ICompositeBar {
this._onDidChange.fire();
}
private getOverflowingComposites(): { id: string, name?: string }[] {
private getOverflowingComposites(): { id: string, name?: string; }[] {
let overflowingIds = this.model.visibleItems.filter(item => item.pinned).map(item => item.id);
// Show the active composite even if it is not pinned
@@ -631,9 +631,9 @@ export class CompositeBar extends Widget implements ICompositeBar {
});
}
private getContextMenuActions(): IAction[] {
getContextMenuActions(): IAction[] {
const actions: IAction[] = this.model.visibleItems
.map(({ id, name, activityAction }) => (<IAction>{
.map(({ id, name, activityAction }) => (toAction({
id,
label: this.getAction(id).label || name || id,
checked: this.isPinned(id),
@@ -645,19 +645,17 @@ export class CompositeBar extends Widget implements ICompositeBar {
this.pin(id, true);
}
}
}));
const otherActions = this.options.getContextMenuActions();
if (otherActions.length) {
actions.push(new Separator());
actions.push(...otherActions);
}
})));
this.options.fillExtraContextMenuActions(actions);
return actions;
}
}
interface ICompositeBarModelItem extends ICompositeBarItem {
activityAction: ActivityAction;
pinnedAction: Action;
pinnedAction: IAction;
activity: ICompositeActivity[];
}

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
import { Action, Separator } from 'vs/base/common/actions';
import { Action, IAction, Separator } from 'vs/base/common/actions';
import * as dom from 'vs/base/browser/dom';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { dispose, toDisposable, MutableDisposable } from 'vs/base/common/lifecycle';
@@ -374,14 +374,14 @@ export class CompositeOverflowActivityAction extends ActivityAction {
}
export class CompositeOverflowActivityActionViewItem extends ActivityActionViewItem {
private actions: Action[] = [];
private actions: IAction[] = [];
constructor(
action: ActivityAction,
private getOverflowingComposites: () => { id: string, name?: string }[],
private getActiveCompositeId: () => string | undefined,
private getBadge: (compositeId: string) => IBadge,
private getCompositeOpenAction: (compositeId: string) => Action,
private getCompositeOpenAction: (compositeId: string) => IAction,
colors: (theme: IColorTheme) => ICompositeBarColors,
@IContextMenuService private readonly contextMenuService: IContextMenuService,
@IThemeService themeService: IThemeService
@@ -404,7 +404,7 @@ export class CompositeOverflowActivityActionViewItem extends ActivityActionViewI
});
}
private getActions(): Action[] {
private getActions(): IAction[] {
return this.getOverflowingComposites().map(composite => {
const action = this.getCompositeOpenAction(composite.id);
action.checked = this.getActiveCompositeId() === action.id;
@@ -457,9 +457,9 @@ export class CompositeActionViewItem extends ActivityActionViewItem {
constructor(
private compositeActivityAction: ActivityAction,
private toggleCompositePinnedAction: Action,
private compositeContextMenuActionsProvider: (compositeId: string) => ReadonlyArray<Action>,
private contextMenuActionsProvider: () => ReadonlyArray<Action>,
private toggleCompositePinnedAction: IAction,
private compositeContextMenuActionsProvider: (compositeId: string) => IAction[],
private contextMenuActionsProvider: () => IAction[],
colors: (theme: IColorTheme) => ICompositeBarColors,
icon: boolean,
private dndHandler: ICompositeDragAndDrop,
@@ -500,9 +500,14 @@ export class CompositeActionViewItem extends ActivityActionViewItem {
return this.compositeActivity;
}
private getActivtyName(): string {
private getActivtyName(skipKeybinding = false): string {
let name = this.compositeActivityAction.activity.name;
if (skipKeybinding) {
return name;
}
const keybinding = this.compositeActivityAction.activity.keybindingId ? this.keybindingService.lookupKeybinding(this.compositeActivityAction.activity.keybindingId) : null;
return keybinding ? nls.localize('titleKeybinding', "{0} ({1})", this.compositeActivityAction.activity.name, keybinding.getLabel()) : this.compositeActivityAction.activity.name;
return keybinding ? nls.localize('titleKeybinding', "{0} ({1})", name, keybinding.getLabel()) : name;
}
render(container: HTMLElement): void {
@@ -601,7 +606,7 @@ export class CompositeActionViewItem extends ActivityActionViewItem {
}
private showContextMenu(container: HTMLElement): void {
const actions: Action[] = [this.toggleCompositePinnedAction];
const actions: IAction[] = [this.toggleCompositePinnedAction];
const compositeContextMenuActions = this.compositeContextMenuActionsProvider(this.activity.id);
if (compositeContextMenuActions.length) {
@@ -615,10 +620,10 @@ export class CompositeActionViewItem extends ActivityActionViewItem {
const isPinned = this.compositeBar.isPinned(this.activity.id);
if (isPinned) {
this.toggleCompositePinnedAction.label = nls.localize('hide', "Hide");
this.toggleCompositePinnedAction.label = nls.localize('hide', "Hide '{0}'", this.getActivtyName(true));
this.toggleCompositePinnedAction.checked = false;
} else {
this.toggleCompositePinnedAction.label = nls.localize('keep', "Keep");
this.toggleCompositePinnedAction.label = nls.localize('keep', "Keep '{0}'", this.getActivtyName(true));
}
const otherActions = this.contextMenuActionsProvider();

View File

@@ -327,10 +327,6 @@ export abstract class CompositePart<T extends Composite> extends Part {
const primaryActions: IAction[] = composite?.getActions().slice(0) || [];
const secondaryActions: IAction[] = composite?.getSecondaryActions().slice(0) || [];
// From Part
primaryActions.push(...this.getActions());
secondaryActions.push(...this.getSecondaryActions());
// Update context
const toolBar = assertIsDefined(this.toolBar);
toolBar.context = this.actionsContextProvider();
@@ -471,14 +467,6 @@ export abstract class CompositePart<T extends Composite> extends Part {
return compositeItem ? compositeItem.progress : undefined;
}
protected getActions(): ReadonlyArray<IAction> {
return [];
}
protected getSecondaryActions(): ReadonlyArray<IAction> {
return [];
}
protected getTitleAreaDropDownAnchorAlignment(): AnchorAlignment {
return AnchorAlignment.RIGHT;
}

View File

@@ -19,9 +19,9 @@ import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle
import { Disposable } from 'vs/base/common/lifecycle';
export class DialogHandlerContribution extends Disposable implements IWorkbenchContribution {
private impl: IDialogHandler;
private readonly model: IDialogsModel;
private readonly impl: IDialogHandler;
private model: IDialogsModel;
private currentDialog: IDialogViewItem | undefined;
constructor(

View File

@@ -6,19 +6,13 @@
import * as dom from 'vs/base/browser/dom';
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
import { BreadcrumbsItem, BreadcrumbsWidget, IBreadcrumbsItemEvent } from 'vs/base/browser/ui/breadcrumbs/breadcrumbsWidget';
import { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel';
import { tail } from 'vs/base/common/arrays';
import { timeout } from 'vs/base/common/async';
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import { combinedDisposable, DisposableStore } from 'vs/base/common/lifecycle';
import { combinedDisposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle';
import { extUri } from 'vs/base/common/resources';
import { URI } from 'vs/base/common/uri';
import 'vs/css!./media/breadcrumbscontrol';
import { ICodeEditor, isCodeEditor, isDiffEditor } from 'vs/editor/browser/editorBrowser';
import { Range } from 'vs/editor/common/core/range';
import { ICodeEditorViewState, ScrollType } from 'vs/editor/common/editorCommon';
import { SymbolKinds } from 'vs/editor/common/modes';
import { OutlineElement, OutlineGroup, OutlineModel, TreeElement } from 'vs/editor/contrib/documentSymbols/outlineModel';
import { localize } from 'vs/nls';
import { MenuId, MenuRegistry } from 'vs/platform/actions/common/actions';
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
@@ -28,35 +22,92 @@ import { IContextViewService } from 'vs/platform/contextview/browser/contextView
import { FileKind, IFileService, IFileStat } from 'vs/platform/files/common/files';
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { IListService, WorkbenchListFocusContextKey } from 'vs/platform/list/browser/listService';
import { IListService, WorkbenchDataTree, WorkbenchListFocusContextKey } from 'vs/platform/list/browser/listService';
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
import { ColorIdentifier, ColorFunction } from 'vs/platform/theme/common/colorRegistry';
import { attachBreadcrumbsStyler } from 'vs/platform/theme/common/styler';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { ResourceLabel } from 'vs/workbench/browser/labels';
import { BreadcrumbsConfig, IBreadcrumbsService } from 'vs/workbench/browser/parts/editor/breadcrumbs';
import { BreadcrumbElement, EditorBreadcrumbsModel, FileElement } from 'vs/workbench/browser/parts/editor/breadcrumbsModel';
import { BreadcrumbsPicker, createBreadcrumbsPicker } from 'vs/workbench/browser/parts/editor/breadcrumbsPicker';
import { BreadcrumbsModel, FileElement, OutlineElement2 } from 'vs/workbench/browser/parts/editor/breadcrumbsModel';
import { BreadcrumbsFilePicker, BreadcrumbsOutlinePicker, BreadcrumbsPicker } from 'vs/workbench/browser/parts/editor/breadcrumbsPicker';
import { IEditorPartOptions, EditorResourceAccessor, SideBySideEditor } from 'vs/workbench/common/editor';
import { ACTIVE_GROUP, ACTIVE_GROUP_TYPE, IEditorService, SIDE_GROUP, SIDE_GROUP_TYPE } from 'vs/workbench/services/editor/common/editorService';
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
import { IEditorGroupView } from 'vs/workbench/browser/parts/editor/editor';
import { onDidChangeZoomLevel } from 'vs/base/browser/browser';
import { withNullAsUndefined, withUndefinedAsNull } from 'vs/base/common/types';
import { ILabelService } from 'vs/platform/label/common/label';
import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService';
import { TextEditorSelectionRevealType } from 'vs/platform/editor/common/editor';
import { CATEGORIES } from 'vs/workbench/common/actions';
import { ITreeNode } from 'vs/base/browser/ui/tree/tree';
import { IOutline } from 'vs/workbench/services/outline/browser/outline';
class Item extends BreadcrumbsItem {
class OutlineItem extends BreadcrumbsItem {
private readonly _disposables = new DisposableStore();
constructor(
readonly element: BreadcrumbElement,
readonly model: BreadcrumbsModel,
readonly element: OutlineElement2,
readonly options: IBreadcrumbsControlOptions
) {
super();
}
dispose(): void {
this._disposables.dispose();
}
equals(other: BreadcrumbsItem): boolean {
if (!(other instanceof OutlineItem)) {
return false;
}
return this.element === other.element &&
this.options.showFileIcons === other.options.showFileIcons &&
this.options.showSymbolIcons === other.options.showSymbolIcons;
}
render(container: HTMLElement): void {
const { element, outline } = this.element;
if (element === outline) {
const element = dom.$('span', undefined, '…');
container.appendChild(element);
return;
}
const templateId = outline.config.delegate.getTemplateId(element);
const renderer = outline.config.renderers.find(renderer => renderer.templateId === templateId);
if (!renderer) {
container.innerText = '<<NO RENDERER>>';
return;
}
const template = renderer.renderTemplate(container);
renderer.renderElement(<ITreeNode<any, any>>{
element,
children: [],
depth: 0,
visibleChildrenCount: 0,
visibleChildIndex: 0,
collapsible: false,
collapsed: false,
visible: true,
filterData: undefined
}, 0, template, undefined);
this._disposables.add(toDisposable(() => { renderer.disposeTemplate(template); }));
}
}
class FileItem extends BreadcrumbsItem {
private readonly _disposables = new DisposableStore();
constructor(
readonly model: BreadcrumbsModel,
readonly element: FileElement,
readonly options: IBreadcrumbsControlOptions,
@IInstantiationService private readonly _instantiationService: IInstantiationService
) {
@@ -68,59 +119,26 @@ class Item extends BreadcrumbsItem {
}
equals(other: BreadcrumbsItem): boolean {
if (!(other instanceof Item)) {
if (!(other instanceof FileItem)) {
return false;
}
if (this.element instanceof FileElement && other.element instanceof FileElement) {
return (extUri.isEqual(this.element.uri, other.element.uri) &&
this.options.showFileIcons === other.options.showFileIcons &&
this.options.showSymbolIcons === other.options.showSymbolIcons);
}
if (this.element instanceof TreeElement && other.element instanceof TreeElement) {
return this.element.id === other.element.id;
}
return false;
return (extUri.isEqual(this.element.uri, other.element.uri) &&
this.options.showFileIcons === other.options.showFileIcons &&
this.options.showSymbolIcons === other.options.showSymbolIcons);
}
render(container: HTMLElement): void {
if (this.element instanceof FileElement) {
// file/folder
let label = this._instantiationService.createInstance(ResourceLabel, container, {});
label.element.setFile(this.element.uri, {
hidePath: true,
hideIcon: this.element.kind === FileKind.FOLDER || !this.options.showFileIcons,
fileKind: this.element.kind,
fileDecorations: { colors: this.options.showDecorationColors, badges: false },
});
container.classList.add(FileKind[this.element.kind].toLowerCase());
this._disposables.add(label);
} else if (this.element instanceof OutlineModel) {
// has outline element but not in one
let label = document.createElement('div');
label.innerText = '\u2026';
label.className = 'hint-more';
container.appendChild(label);
} else if (this.element instanceof OutlineGroup) {
// provider
let label = new IconLabel(container);
label.setLabel(this.element.label);
this._disposables.add(label);
} else if (this.element instanceof OutlineElement) {
// symbol
if (this.options.showSymbolIcons) {
let icon = document.createElement('div');
icon.className = SymbolKinds.toCssClassName(this.element.symbol.kind);
container.appendChild(icon);
container.classList.add('shows-symbol-icon');
}
let label = new IconLabel(container);
let title = this.element.symbol.name.replace(/\r|\n|\r\n/g, '\u23CE');
label.setLabel(title);
this._disposables.add(label);
}
// file/folder
let label = this._instantiationService.createInstance(ResourceLabel, container, {});
label.element.setFile(this.element.uri, {
hidePath: true,
hideIcon: this.element.kind === FileKind.FOLDER || !this.options.showFileIcons,
fileKind: this.element.kind,
fileDecorations: { colors: this.options.showDecorationColors, badges: false },
});
container.classList.add(FileKind[this.element.kind].toLowerCase());
this._disposables.add(label);
}
}
@@ -170,26 +188,23 @@ export class BreadcrumbsControl {
private readonly _editorGroup: IEditorGroupView,
@IContextKeyService private readonly _contextKeyService: IContextKeyService,
@IContextViewService private readonly _contextViewService: IContextViewService,
@IEditorService private readonly _editorService: IEditorService,
@ICodeEditorService private readonly _codeEditorService: ICodeEditorService,
@IWorkspaceContextService private readonly _workspaceService: IWorkspaceContextService,
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@IThemeService private readonly _themeService: IThemeService,
@IQuickInputService private readonly _quickInputService: IQuickInputService,
@IConfigurationService private readonly _configurationService: IConfigurationService,
@ITextResourceConfigurationService private readonly _textResourceConfigurationService: ITextResourceConfigurationService,
@IFileService private readonly _fileService: IFileService,
@ITelemetryService private readonly _telemetryService: ITelemetryService,
@IEditorService private readonly _editorService: IEditorService,
@ILabelService private readonly _labelService: ILabelService,
@IConfigurationService configurationService: IConfigurationService,
@IBreadcrumbsService breadcrumbsService: IBreadcrumbsService,
) {
this.domNode = document.createElement('div');
this.domNode.classList.add('breadcrumbs-control');
dom.append(container, this.domNode);
this._cfUseQuickPick = BreadcrumbsConfig.UseQuickPick.bindTo(_configurationService);
this._cfShowIcons = BreadcrumbsConfig.Icons.bindTo(_configurationService);
this._cfTitleScrollbarSizing = BreadcrumbsConfig.TitleScrollbarSizing.bindTo(_configurationService);
this._cfUseQuickPick = BreadcrumbsConfig.UseQuickPick.bindTo(configurationService);
this._cfShowIcons = BreadcrumbsConfig.Icons.bindTo(configurationService);
this._cfTitleScrollbarSizing = BreadcrumbsConfig.TitleScrollbarSizing.bindTo(configurationService);
const sizing = this._cfTitleScrollbarSizing.getValue() ?? 'default';
this._widget = new BreadcrumbsWidget(this.domNode, BreadcrumbsControl.SCROLLBAR_SIZES[sizing]);
@@ -256,14 +271,11 @@ export class BreadcrumbsControl {
this._ckBreadcrumbsVisible.set(true);
this._ckBreadcrumbsPossible.set(true);
const editor = this._getActiveCodeEditor();
const model = new EditorBreadcrumbsModel(
const model = this._instantiationService.createInstance(BreadcrumbsModel,
fileInfoUri ?? uri,
uri, editor,
this._configurationService,
this._textResourceConfigurationService,
this._workspaceService
this._editorGroup.activeEditorPane
);
this.domNode.classList.toggle('relative-path', model.isRelative());
this.domNode.classList.toggle('backslash-path', this._labelService.getSeparator(uri.scheme, uri.authority) === '\\');
@@ -274,7 +286,7 @@ export class BreadcrumbsControl {
showFileIcons: this._options.showFileIcons && showIcons,
showSymbolIcons: this._options.showSymbolIcons && showIcons
};
const items = model.getElements().map(element => new Item(element, options, this._instantiationService));
const items = model.getElements().map(element => element instanceof FileElement ? new FileItem(model, element, options, this._instantiationService) : new OutlineItem(model, element, options));
this._widget.setItems(items);
this._widget.reveal(items[items.length - 1]);
};
@@ -298,7 +310,7 @@ export class BreadcrumbsControl {
this._breadcrumbsDisposables.add({
dispose: () => {
if (this._breadcrumbsPickerShowing) {
this._contextViewService.hideContextView(this);
this._contextViewService.hideContextView({ source: this });
}
}
});
@@ -306,20 +318,6 @@ export class BreadcrumbsControl {
return true;
}
private _getActiveCodeEditor(): ICodeEditor | undefined {
if (!this._editorGroup.activeEditorPane) {
return undefined;
}
let control = this._editorGroup.activeEditorPane.getControl();
let editor: ICodeEditor | undefined;
if (isCodeEditor(control)) {
editor = control as ICodeEditor;
} else if (isDiffEditor(control)) {
editor = control.getModifiedEditor();
}
return editor;
}
private _onFocusEvent(event: IBreadcrumbsItemEvent): void {
if (event.item && this._breadcrumbsPickerShowing) {
this._breadcrumbsPickerIgnoreOnceItem = undefined;
@@ -339,13 +337,12 @@ export class BreadcrumbsControl {
return;
}
const { element } = event.item as Item;
const { element } = event.item as FileItem | OutlineItem;
this._editorGroup.focus();
type BreadcrumbSelectClassification = {
type: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
};
this._telemetryService.publicLog2<{ type: string }, BreadcrumbSelectClassification>('breadcrumbs/select', { type: element instanceof TreeElement ? 'symbol' : 'file' });
type BreadcrumbSelect = { type: string };
type BreadcrumbSelectClassification = { type: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; };
this._telemetryService.publicLog2<BreadcrumbSelect, BreadcrumbSelectClassification>('breadcrumbs/select', { type: event.item instanceof OutlineItem ? 'symbol' : 'file' });
const group = this._getEditorGroup(event.payload);
if (group !== undefined) {
@@ -360,64 +357,31 @@ export class BreadcrumbsControl {
// using quick pick
this._widget.setFocused(undefined);
this._widget.setSelection(undefined);
this._quickInputService.quickAccess.show(element instanceof TreeElement ? '@' : '');
this._quickInputService.quickAccess.show(element instanceof OutlineElement2 ? '@' : '');
return;
}
// show picker
let picker: BreadcrumbsPicker;
let pickerAnchor: { x: number; y: number };
let editor = this._getActiveCodeEditor();
let editorDecorations: string[] = [];
let editorViewState: ICodeEditorViewState | undefined;
interface IHideData { didPick?: boolean, source?: BreadcrumbsControl }
this._contextViewService.showContextView({
render: (parent: HTMLElement) => {
picker = createBreadcrumbsPicker(this._instantiationService, parent, element);
let selectListener = picker.onDidPickElement(data => {
if (data.target) {
editorViewState = undefined;
}
this._contextViewService.hideContextView(this);
if (event.item instanceof FileItem) {
picker = this._instantiationService.createInstance(BreadcrumbsFilePicker, parent, event.item.model.resource);
} else if (event.item instanceof OutlineItem) {
picker = this._instantiationService.createInstance(BreadcrumbsOutlinePicker, parent, event.item.model.resource);
}
const group = (picker.useAltAsMultipleSelectionModifier && (data.browserEvent as MouseEvent).metaKey) || (!picker.useAltAsMultipleSelectionModifier && (data.browserEvent as MouseEvent).altKey)
? SIDE_GROUP
: ACTIVE_GROUP;
this._revealInEditor(event, data.target, group, (data.browserEvent as MouseEvent).button === 1);
/* __GDPR__
"breadcrumbs/open" : {
"type": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
}
*/
this._telemetryService.publicLog('breadcrumbs/open', { type: !data ? 'nothing' : data.target instanceof TreeElement ? 'symbol' : 'file' });
});
let focusListener = picker.onDidFocusElement(data => {
if (!editor || !(data.target instanceof OutlineElement)) {
return;
}
if (!editorViewState) {
editorViewState = withNullAsUndefined(editor.saveViewState());
}
const { symbol } = data.target;
editor.revealRangeInCenterIfOutsideViewport(symbol.range, ScrollType.Smooth);
editorDecorations = editor.deltaDecorations(editorDecorations, [{
range: symbol.range,
options: {
className: 'rangeHighlight',
isWholeLine: true
}
}]);
});
let zoomListener = onDidChangeZoomLevel(() => {
this._contextViewService.hideContextView(this);
});
let selectListener = picker.onWillPickElement(() => this._contextViewService.hideContextView({ source: this, didPick: true }));
let zoomListener = onDidChangeZoomLevel(() => this._contextViewService.hideContextView({ source: this }));
let focusTracker = dom.trackFocus(parent);
let blurListener = focusTracker.onDidBlur(() => {
this._breadcrumbsPickerIgnoreOnceItem = this._widget.isDOMFocused() ? event.item : undefined;
this._contextViewService.hideContextView(this);
this._contextViewService.hideContextView({ source: this });
});
this._breadcrumbsPickerShowing = true;
@@ -426,7 +390,6 @@ export class BreadcrumbsControl {
return combinedDisposable(
picker,
selectListener,
focusListener,
zoomListener,
focusTracker,
blurListener
@@ -465,19 +428,17 @@ export class BreadcrumbsControl {
}
return pickerAnchor;
},
onHide: (data) => {
if (editor) {
editor.deltaDecorations(editorDecorations, []);
if (editorViewState) {
editor.restoreViewState(editorViewState);
}
onHide: (data?: IHideData) => {
if (!data?.didPick) {
picker.restoreViewState();
}
this._breadcrumbsPickerShowing = false;
this._updateCkBreadcrumbsActive();
if (data === this) {
if (data?.source === this) {
this._widget.setFocused(undefined);
this._widget.setSelection(undefined);
}
picker.dispose();
}
});
}
@@ -487,11 +448,11 @@ export class BreadcrumbsControl {
this._ckBreadcrumbsActive.set(value);
}
private _revealInEditor(event: IBreadcrumbsItemEvent, element: BreadcrumbElement, group: SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE | undefined, pinned: boolean = false): void {
private async _revealInEditor(event: IBreadcrumbsItemEvent, element: FileElement | OutlineElement2, group: SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE | undefined, pinned: boolean = false): Promise<void> {
if (element instanceof FileElement) {
if (element.kind === FileKind.FILE) {
// open file in any editor
this._editorService.openEditor({ resource: element.uri, options: { pinned } }, group);
await this._editorService.openEditor({ resource: element.uri, options: { pinned } }, group);
} else {
// show next picker
let items = this._widget.getItems();
@@ -499,20 +460,8 @@ export class BreadcrumbsControl {
this._widget.setFocused(items[idx + 1]);
this._widget.setSelection(items[idx + 1], BreadcrumbsControl.Payload_Pick);
}
} else if (element instanceof OutlineElement) {
// open symbol in code editor
const model = OutlineModel.get(element);
if (model) {
this._codeEditorService.openCodeEditor({
resource: model.uri,
options: {
selection: Range.collapseToStart(element.symbol.selectionRange),
selectionRevealType: TextEditorSelectionRevealType.CenterIfOutsideViewport,
pinned
}
}, withUndefinedAsNull(this._getActiveCodeEditor()), group === SIDE_GROUP);
}
} else {
element.outline.reveal(element, { pinned }, group === SIDE_GROUP);
}
}
@@ -744,29 +693,29 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
handler(accessor) {
const editors = accessor.get(IEditorService);
const lists = accessor.get(IListService);
const element = lists.lastFocusedList ? <OutlineElement | IFileStat>lists.lastFocusedList.getFocus()[0] : undefined;
if (element instanceof OutlineElement) {
const outlineElement = OutlineModel.get(element);
if (!outlineElement) {
return undefined;
}
// open symbol in editor
return editors.openEditor({
resource: outlineElement.uri,
options: { selection: Range.collapseToStart(element.symbol.selectionRange), pinned: true }
}, SIDE_GROUP);
const tree = lists.lastFocusedList;
if (!(tree instanceof WorkbenchDataTree)) {
return;
}
} else if (element && URI.isUri(element.resource)) {
// open file in editor
const element = <IFileStat | unknown>tree.getFocus()[0];
if (URI.isUri((<IFileStat>element)?.resource)) {
// IFileStat: open file in editor
return editors.openEditor({
resource: element.resource,
resource: (<IFileStat>element).resource,
options: { pinned: true }
}, SIDE_GROUP);
}
} else {
// ignore
return undefined;
// IOutline: check if this the outline and iff so reveal element
const input = tree.getInput();
if (input && typeof (<IOutline<any>>input).outlineKind === 'string') {
return (<IOutline<any>>input).reveal(element, {
pinned: true,
preserveFocus: false
}, true);
}
}
});

View File

@@ -3,27 +3,20 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { equals } from 'vs/base/common/arrays';
import { TimeoutTimer } from 'vs/base/common/async';
import { CancellationTokenSource } from 'vs/base/common/cancellation';
import { onUnexpectedError } from 'vs/base/common/errors';
import { Emitter, Event } from 'vs/base/common/event';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { DisposableStore, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { isEqual, dirname } from 'vs/base/common/resources';
import { URI } from 'vs/base/common/uri';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { IPosition } from 'vs/editor/common/core/position';
import { DocumentSymbolProviderRegistry } from 'vs/editor/common/modes';
import { OutlineElement, OutlineGroup, OutlineModel, TreeElement } from 'vs/editor/contrib/documentSymbols/outlineModel';
import { IWorkspaceContextService, IWorkspaceFolder, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { Schemas } from 'vs/base/common/network';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { BreadcrumbsConfig } from 'vs/workbench/browser/parts/editor/breadcrumbs';
import { FileKind } from 'vs/platform/files/common/files';
import { withNullAsUndefined } from 'vs/base/common/types';
import { OutlineFilter } from 'vs/editor/contrib/documentSymbols/outlineTree';
import { ITextModel } from 'vs/editor/common/model';
import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService';
import { IOutline, IOutlineService, OutlineTarget } from 'vs/workbench/services/outline/browser/outline';
import { IEditorPane } from 'vs/workbench/common/editor';
export class FileElement {
constructor(
@@ -32,11 +25,16 @@ export class FileElement {
) { }
}
export type BreadcrumbElement = FileElement | OutlineModel | OutlineGroup | OutlineElement;
type FileInfo = { path: FileElement[], folder?: IWorkspaceFolder };
export class EditorBreadcrumbsModel {
export class OutlineElement2 {
constructor(
readonly element: IOutline<any> | any,
readonly outline: IOutline<any>
) { }
}
export class BreadcrumbsModel {
private readonly _disposables = new DisposableStore();
private readonly _fileInfo: FileInfo;
@@ -45,28 +43,31 @@ export class EditorBreadcrumbsModel {
private readonly _cfgFilePath: BreadcrumbsConfig<'on' | 'off' | 'last'>;
private readonly _cfgSymbolPath: BreadcrumbsConfig<'on' | 'off' | 'last'>;
private _outlineElements: Array<OutlineModel | OutlineGroup | OutlineElement> = [];
private _outlineDisposables = new DisposableStore();
private readonly _currentOutline = new MutableDisposable<IOutline<any>>();
private readonly _outlineDisposables = new DisposableStore();
private readonly _onDidUpdate = new Emitter<this>();
readonly onDidUpdate: Event<this> = this._onDidUpdate.event;
constructor(
fileInfoUri: URI,
private readonly _uri: URI,
private readonly _editor: ICodeEditor | undefined,
@IConfigurationService private readonly _configurationService: IConfigurationService,
@ITextResourceConfigurationService private readonly _textResourceConfigurationService: ITextResourceConfigurationService,
@IWorkspaceContextService workspaceService: IWorkspaceContextService,
readonly resource: URI,
editor: IEditorPane | undefined,
@IConfigurationService configurationService: IConfigurationService,
@IWorkspaceContextService private readonly _workspaceService: IWorkspaceContextService,
@IOutlineService private readonly _outlineService: IOutlineService,
) {
this._cfgEnabled = BreadcrumbsConfig.IsEnabled.bindTo(_configurationService);
this._cfgFilePath = BreadcrumbsConfig.FilePath.bindTo(_configurationService);
this._cfgSymbolPath = BreadcrumbsConfig.SymbolPath.bindTo(_configurationService);
this._cfgEnabled = BreadcrumbsConfig.IsEnabled.bindTo(configurationService);
this._cfgFilePath = BreadcrumbsConfig.FilePath.bindTo(configurationService);
this._cfgSymbolPath = BreadcrumbsConfig.SymbolPath.bindTo(configurationService);
this._disposables.add(this._cfgFilePath.onDidChange(_ => this._onDidUpdate.fire(this)));
this._disposables.add(this._cfgSymbolPath.onDidChange(_ => this._onDidUpdate.fire(this)));
this._fileInfo = EditorBreadcrumbsModel._initFilePathInfo(fileInfoUri, workspaceService);
this._bindToEditor();
this._fileInfo = this._initFilePathInfo(resource);
if (editor) {
this._bindToEditor(editor);
this._disposables.add(_outlineService.onDidChange(() => this._bindToEditor(editor)));
}
this._onDidUpdate.fire(this);
}
@@ -74,6 +75,7 @@ export class EditorBreadcrumbsModel {
this._cfgEnabled.dispose();
this._cfgFilePath.dispose();
this._cfgSymbolPath.dispose();
this._currentOutline.dispose();
this._outlineDisposables.dispose();
this._disposables.dispose();
this._onDidUpdate.dispose();
@@ -83,8 +85,8 @@ export class EditorBreadcrumbsModel {
return Boolean(this._fileInfo.folder);
}
getElements(): ReadonlyArray<BreadcrumbElement> {
let result: BreadcrumbElement[] = [];
getElements(): ReadonlyArray<FileElement | OutlineElement2> {
let result: (FileElement | OutlineElement2)[] = [];
// file path elements
if (this._cfgFilePath.getValue() === 'on') {
@@ -93,17 +95,27 @@ export class EditorBreadcrumbsModel {
result = result.concat(this._fileInfo.path.slice(-1));
}
// symbol path elements
if (this._cfgSymbolPath.getValue() === 'on') {
result = result.concat(this._outlineElements);
} else if (this._cfgSymbolPath.getValue() === 'last' && this._outlineElements.length > 0) {
result = result.concat(this._outlineElements.slice(-1));
if (this._cfgSymbolPath.getValue() === 'off') {
return result;
}
if (!this._currentOutline.value) {
return result;
}
const breadcrumbsElements = this._currentOutline.value.config.breadcrumbsDataSource.getBreadcrumbElements();
for (let i = this._cfgSymbolPath.getValue() === 'last' && breadcrumbsElements.length > 0 ? breadcrumbsElements.length - 1 : 0; i < breadcrumbsElements.length; i++) {
result.push(new OutlineElement2(breadcrumbsElements[i], this._currentOutline.value));
}
if (breadcrumbsElements.length === 0 && !this._currentOutline.value.isEmpty) {
result.push(new OutlineElement2(this._currentOutline.value, this._currentOutline.value));
}
return result;
}
private static _initFilePathInfo(uri: URI, workspaceService: IWorkspaceContextService): FileInfo {
private _initFilePathInfo(uri: URI): FileInfo {
if (uri.scheme === Schemas.untitled) {
return {
@@ -113,7 +125,7 @@ export class EditorBreadcrumbsModel {
}
let info: FileInfo = {
folder: withNullAsUndefined(workspaceService.getWorkspaceFolder(uri)),
folder: withNullAsUndefined(this._workspaceService.getWorkspaceFolder(uri)),
path: []
};
@@ -130,181 +142,33 @@ export class EditorBreadcrumbsModel {
}
}
if (info.folder && workspaceService.getWorkbenchState() === WorkbenchState.WORKSPACE) {
if (info.folder && this._workspaceService.getWorkbenchState() === WorkbenchState.WORKSPACE) {
info.path.unshift(new FileElement(info.folder.uri, FileKind.ROOT_FOLDER));
}
return info;
}
private _bindToEditor(): void {
if (!this._editor) {
return;
}
// update as language, model, providers changes
this._disposables.add(DocumentSymbolProviderRegistry.onDidChange(_ => this._updateOutline()));
this._disposables.add(this._editor.onDidChangeModel(_ => this._updateOutline()));
this._disposables.add(this._editor.onDidChangeModelLanguage(_ => this._updateOutline()));
// update when config changes (re-render)
this._disposables.add(this._configurationService.onDidChangeConfiguration(e => {
if (!this._cfgEnabled.getValue()) {
// breadcrumbs might be disabled (also via a setting/config) and that is
// something we must check before proceeding.
return;
}
if (e.affectsConfiguration('breadcrumbs')) {
this._updateOutline(true);
return;
}
if (this._editor && this._editor.getModel()) {
const editorModel = this._editor.getModel() as ITextModel;
const languageName = editorModel.getLanguageIdentifier().language;
// Checking for changes in the current language override config.
// We can't be more specific than this because the ConfigurationChangeEvent(e) only includes the first part of the root path
if (e.affectsConfiguration(`[${languageName}]`)) {
this._updateOutline(true);
}
}
}));
// update soon'ish as model content change
const updateSoon = new TimeoutTimer();
this._disposables.add(updateSoon);
this._disposables.add(this._editor.onDidChangeModelContent(_ => {
const timeout = OutlineModel.getRequestDelay(this._editor!.getModel());
updateSoon.cancelAndSet(() => this._updateOutline(true), timeout);
}));
this._updateOutline();
// stop when editor dies
this._disposables.add(this._editor.onDidDispose(() => this._outlineDisposables.clear()));
}
private _updateOutline(didChangeContent?: boolean): void {
private _bindToEditor(editor: IEditorPane): void {
const newCts = new CancellationTokenSource();
this._currentOutline.clear();
this._outlineDisposables.clear();
if (!didChangeContent) {
this._updateOutlineElements([]);
}
this._outlineDisposables.add(toDisposable(() => newCts.dispose(true)));
const editor = this._editor!;
const buffer = editor.getModel();
if (!buffer || !DocumentSymbolProviderRegistry.has(buffer) || !isEqual(buffer.uri, this._uri)) {
return;
}
const source = new CancellationTokenSource();
const versionIdThen = buffer.getVersionId();
const timeout = new TimeoutTimer();
this._outlineDisposables.add({
dispose: () => {
source.dispose(true);
timeout.dispose();
this._outlineService.createOutline(editor, OutlineTarget.Breadcrumbs, newCts.token).then(outline => {
if (newCts.token.isCancellationRequested) {
// cancelled: dispose new outline and reset
outline?.dispose();
outline = undefined;
}
});
OutlineModel.create(buffer, source.token).then(model => {
if (source.token.isCancellationRequested) {
// cancelled -> do nothing
return;
this._currentOutline.value = outline;
this._onDidUpdate.fire(this);
if (outline) {
this._outlineDisposables.add(outline.onDidChange(() => this._onDidUpdate.fire(this)));
}
if (TreeElement.empty(model)) {
// empty -> no outline elements
this._updateOutlineElements([]);
} else {
// copy the model
model = model.adopt();
this._updateOutlineElements(this._getOutlineElements(model, editor.getPosition()));
this._outlineDisposables.add(editor.onDidChangeCursorPosition(_ => {
timeout.cancelAndSet(() => {
if (!buffer.isDisposed() && versionIdThen === buffer.getVersionId() && editor.getModel()) {
this._updateOutlineElements(this._getOutlineElements(model, editor.getPosition()));
}
}, 150);
}));
}
}).catch(err => {
this._updateOutlineElements([]);
this._onDidUpdate.fire(this);
onUnexpectedError(err);
});
}
private _getOutlineElements(model: OutlineModel, position: IPosition | null): Array<OutlineModel | OutlineGroup | OutlineElement> {
if (!model || !position) {
return [];
}
let item: OutlineGroup | OutlineElement | undefined = model.getItemEnclosingPosition(position);
if (!item) {
return this._getOutlineElementsRoot(model);
}
let chain: Array<OutlineGroup | OutlineElement> = [];
while (item) {
chain.push(item);
let parent: any = item.parent;
if (parent instanceof OutlineModel) {
break;
}
if (parent instanceof OutlineGroup && parent.parent && parent.parent.children.size === 1) {
break;
}
item = parent;
}
let result: Array<OutlineGroup | OutlineElement> = [];
for (let i = chain.length - 1; i >= 0; i--) {
let element = chain[i];
if (this._isFiltered(element)) {
break;
}
result.push(element);
}
if (result.length === 0) {
return this._getOutlineElementsRoot(model);
}
return result;
}
private _getOutlineElementsRoot(model: OutlineModel): (OutlineModel | OutlineGroup | OutlineElement)[] {
for (const child of model.children.values()) {
if (!this._isFiltered(child)) {
return [model];
}
}
return [];
}
private _isFiltered(element: TreeElement): boolean {
if (element instanceof OutlineElement) {
const key = `breadcrumbs.${OutlineFilter.kindToConfigName[element.symbol.kind]}`;
let uri: URI | undefined;
if (this._editor && this._editor.getModel()) {
const model = this._editor.getModel() as ITextModel;
uri = model.uri;
}
return !this._textResourceConfigurationService.getValue<boolean>(uri, key);
}
return false;
}
private _updateOutlineElements(elements: Array<OutlineModel | OutlineGroup | OutlineElement>): void {
if (!equals(elements, this._outlineElements, EditorBreadcrumbsModel._outlineElementEquals)) {
this._outlineElements = elements;
this._onDidUpdate.fire(this);
}
}
private static _outlineElementEquals(a: OutlineModel | OutlineGroup | OutlineElement, b: OutlineModel | OutlineGroup | OutlineElement): boolean {
if (a === b) {
return true;
} else if (!a || !b) {
return false;
} else {
return a.id === b.id;
}
}
}

View File

@@ -8,34 +8,30 @@ import { onUnexpectedError } from 'vs/base/common/errors';
import { Emitter, Event } from 'vs/base/common/event';
import { createMatches, FuzzyScore } from 'vs/base/common/filters';
import * as glob from 'vs/base/common/glob';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { IDisposable, DisposableStore, MutableDisposable, Disposable } from 'vs/base/common/lifecycle';
import { posix } from 'vs/base/common/path';
import { basename, dirname, isEqual } from 'vs/base/common/resources';
import { URI } from 'vs/base/common/uri';
import 'vs/css!./media/breadcrumbscontrol';
import { OutlineElement, OutlineModel, TreeElement } from 'vs/editor/contrib/documentSymbols/outlineModel';
import { IConfigurationService, IConfigurationOverrides } from 'vs/platform/configuration/common/configuration';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { FileKind, IFileService, IFileStat } from 'vs/platform/files/common/files';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { WorkbenchDataTree, WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService';
import { breadcrumbsPickerBackground, widgetShadow } from 'vs/platform/theme/common/colorRegistry';
import { IWorkspace, IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { isWorkspace, isWorkspaceFolder, IWorkspace, IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { ResourceLabels, IResourceLabel, DEFAULT_LABELS_CONTAINER } from 'vs/workbench/browser/labels';
import { BreadcrumbsConfig } from 'vs/workbench/browser/parts/editor/breadcrumbs';
import { BreadcrumbElement, FileElement } from 'vs/workbench/browser/parts/editor/breadcrumbsModel';
import { OutlineElement2, FileElement } from 'vs/workbench/browser/parts/editor/breadcrumbsModel';
import { IAsyncDataSource, ITreeRenderer, ITreeNode, ITreeFilter, TreeVisibility, ITreeSorter } from 'vs/base/browser/ui/tree/tree';
import { OutlineVirtualDelegate, OutlineGroupRenderer, OutlineElementRenderer, OutlineItemComparator, OutlineIdentityProvider, OutlineNavigationLabelProvider, OutlineDataSource, OutlineSortOrder, OutlineFilter, OutlineAccessibilityProvider } from 'vs/editor/contrib/documentSymbols/outlineTree';
import { IIdentityProvider, IListVirtualDelegate, IKeyboardNavigationLabelProvider } from 'vs/base/browser/ui/list/list';
import { IFileIconTheme, IThemeService } from 'vs/platform/theme/common/themeService';
import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget';
import { IModeService } from 'vs/editor/common/services/modeService';
import { localize } from 'vs/nls';
export function createBreadcrumbsPicker(instantiationService: IInstantiationService, parent: HTMLElement, element: BreadcrumbElement): BreadcrumbsPicker {
return element instanceof FileElement
? instantiationService.createInstance(BreadcrumbsFilePicker, parent)
: instantiationService.createInstance(BreadcrumbsOutlinePicker, parent);
}
import { IOutline, IOutlineComparator } from 'vs/workbench/services/outline/browser/outline';
import { IEditorOptions } from 'vs/platform/editor/common/editor';
import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService';
interface ILayoutInfo {
maxHeight: number;
@@ -62,17 +58,18 @@ export abstract class BreadcrumbsPicker {
protected _fakeEvent = new UIEvent('fakeEvent');
protected _layoutInfo!: ILayoutInfo;
private readonly _onDidPickElement = new Emitter<SelectEvent>();
readonly onDidPickElement: Event<SelectEvent> = this._onDidPickElement.event;
protected readonly _onWillPickElement = new Emitter<void>();
readonly onWillPickElement: Event<void> = this._onWillPickElement.event;
private readonly _onDidFocusElement = new Emitter<SelectEvent>();
readonly onDidFocusElement: Event<SelectEvent> = this._onDidFocusElement.event;
private readonly _previewDispoables = new MutableDisposable();
constructor(
parent: HTMLElement,
protected resource: URI,
@IInstantiationService protected readonly _instantiationService: IInstantiationService,
@IThemeService protected readonly _themeService: IThemeService,
@IConfigurationService protected readonly _configurationService: IConfigurationService,
@ITelemetryService private readonly _telemetryService: ITelemetryService,
) {
this._domNode = document.createElement('div');
this._domNode.className = 'monaco-breadcrumbs-picker show-file-icons';
@@ -81,12 +78,13 @@ export abstract class BreadcrumbsPicker {
dispose(): void {
this._disposables.dispose();
this._onDidPickElement.dispose();
this._onDidFocusElement.dispose();
this._tree.dispose();
this._previewDispoables.dispose();
this._onWillPickElement.dispose();
this._domNode.remove();
setTimeout(() => this._tree.dispose(), 0); // tree cannot be disposed while being opened...
}
show(input: any, maxHeight: number, width: number, arrowSize: number, arrowOffset: number): void {
async show(input: any, maxHeight: number, width: number, arrowSize: number, arrowOffset: number): Promise<void> {
const theme = this._themeService.getColorTheme();
const color = theme.getColor(breadcrumbsPickerBackground);
@@ -103,33 +101,33 @@ export abstract class BreadcrumbsPicker {
this._domNode.appendChild(this._treeContainer);
this._layoutInfo = { maxHeight, width, arrowSize, arrowOffset, inputHeight: 0 };
this._tree = this._createTree(this._treeContainer);
this._tree = this._createTree(this._treeContainer, input);
this._disposables.add(this._tree.onDidChangeSelection(e => {
if (e.browserEvent !== this._fakeEvent) {
const target = this._getTargetFromEvent(e.elements[0]);
if (target) {
setTimeout(_ => {// need to debounce here because this disposes the tree and the tree doesn't like to be disposed on click
this._onDidPickElement.fire({ target, browserEvent: e.browserEvent || new UIEvent('fake') });
}, 0);
}
this._disposables.add(this._tree.onDidOpen(async e => {
const { element, editorOptions, sideBySide } = e;
const didReveal = await this._revealElement(element, { ...editorOptions, preserveFocus: false }, sideBySide);
if (!didReveal) {
return;
}
// send telemetry
interface OpenEvent { type: string }
interface OpenEventGDPR { type: { classification: 'SystemMetaData', purpose: 'FeatureInsight' } }
this._telemetryService.publicLog2<OpenEvent, OpenEventGDPR>('breadcrumbs/open', { type: element instanceof OutlineElement2 ? 'symbol' : 'file' });
}));
this._disposables.add(this._tree.onDidChangeFocus(e => {
const target = this._getTargetFromEvent(e.elements[0]);
if (target) {
this._onDidFocusElement.fire({ target, browserEvent: e.browserEvent || new UIEvent('fake') });
}
this._previewDispoables.value = this._previewElement(e.elements[0]);
}));
this._disposables.add(this._tree.onDidChangeContentHeight(() => {
this._layout();
}));
this._domNode.focus();
this._setInput(input).then(() => {
try {
await this._setInput(input);
this._layout();
}).catch(onUnexpectedError);
} catch (err) {
onUnexpectedError(err);
}
}
protected _layout(): void {
@@ -146,16 +144,15 @@ export abstract class BreadcrumbsPicker {
this._treeContainer.style.height = `${treeHeight}px`;
this._treeContainer.style.width = `${this._layoutInfo.width}px`;
this._tree.layout(treeHeight, this._layoutInfo.width);
}
get useAltAsMultipleSelectionModifier() {
return this._tree.useAltAsMultipleSelectionModifier;
}
restoreViewState(): void { }
protected abstract _setInput(element: FileElement | OutlineElement2): Promise<void>;
protected abstract _createTree(container: HTMLElement, input: any): Tree<any, any>;
protected abstract _previewElement(element: any): IDisposable;
protected abstract _revealElement(element: any, options: IEditorOptions, sideBySide: boolean): Promise<boolean>;
protected abstract _setInput(element: BreadcrumbElement): Promise<void>;
protected abstract _createTree(container: HTMLElement): Tree<any, any>;
protected abstract _getTargetFromEvent(element: any): any | undefined;
}
//#region - Files
@@ -173,9 +170,9 @@ class FileIdentityProvider implements IIdentityProvider<IWorkspace | IWorkspaceF
getId(element: IWorkspace | IWorkspaceFolder | IFileStat | URI): { toString(): string; } {
if (URI.isUri(element)) {
return element.toString();
} else if (IWorkspace.isIWorkspace(element)) {
} else if (isWorkspace(element)) {
return element.id;
} else if (IWorkspaceFolder.isIWorkspaceFolder(element)) {
} else if (isWorkspaceFolder(element)) {
return element.uri.toString();
} else {
return element.resource.toString();
@@ -186,43 +183,31 @@ class FileIdentityProvider implements IIdentityProvider<IWorkspace | IWorkspaceF
class FileDataSource implements IAsyncDataSource<IWorkspace | URI, IWorkspaceFolder | IFileStat> {
private readonly _parents = new WeakMap<object, IWorkspaceFolder | IFileStat>();
constructor(
@IFileService private readonly _fileService: IFileService,
) { }
hasChildren(element: IWorkspace | URI | IWorkspaceFolder | IFileStat): boolean {
return URI.isUri(element)
|| IWorkspace.isIWorkspace(element)
|| IWorkspaceFolder.isIWorkspaceFolder(element)
|| isWorkspace(element)
|| isWorkspaceFolder(element)
|| element.isDirectory;
}
getChildren(element: IWorkspace | URI | IWorkspaceFolder | IFileStat): Promise<(IWorkspaceFolder | IFileStat)[]> {
if (IWorkspace.isIWorkspace(element)) {
return Promise.resolve(element.folders).then(folders => {
for (let child of folders) {
this._parents.set(element, child);
}
return folders;
});
async getChildren(element: IWorkspace | URI | IWorkspaceFolder | IFileStat): Promise<(IWorkspaceFolder | IFileStat)[]> {
if (isWorkspace(element)) {
return element.folders;
}
let uri: URI;
if (IWorkspaceFolder.isIWorkspaceFolder(element)) {
if (isWorkspaceFolder(element)) {
uri = element.uri;
} else if (URI.isUri(element)) {
uri = element;
} else {
uri = element.resource;
}
return this._fileService.resolve(uri).then(stat => {
for (const child of stat.children || []) {
this._parents.set(stat, child);
}
return stat.children || [];
});
const stat = await this._fileService.resolve(uri);
return stat.children ?? [];
}
}
@@ -245,7 +230,7 @@ class FileRenderer implements ITreeRenderer<IFileStat | IWorkspaceFolder, FuzzyS
const { element } = node;
let resource: URI;
let fileKind: FileKind;
if (IWorkspaceFolder.isIWorkspaceFolder(element)) {
if (isWorkspaceFolder(element)) {
resource = element.uri;
fileKind = FileKind.ROOT_FOLDER;
} else {
@@ -327,7 +312,7 @@ class FileFilter implements ITreeFilter<IWorkspaceFolder | IFileStat> {
}
filter(element: IWorkspaceFolder | IFileStat, _parentVisibility: TreeVisibility): boolean {
if (IWorkspaceFolder.isIWorkspaceFolder(element)) {
if (isWorkspaceFolder(element)) {
// not a file
return true;
}
@@ -345,7 +330,7 @@ class FileFilter implements ITreeFilter<IWorkspaceFolder | IFileStat> {
export class FileSorter implements ITreeSorter<IFileStat | IWorkspaceFolder> {
compare(a: IFileStat | IWorkspaceFolder, b: IFileStat | IWorkspaceFolder): number {
if (IWorkspaceFolder.isIWorkspaceFolder(a) && IWorkspaceFolder.isIWorkspaceFolder(b)) {
if (isWorkspaceFolder(a) && isWorkspaceFolder(b)) {
return a.index - b.index;
}
if ((a as IFileStat).isDirectory === (b as IFileStat).isDirectory) {
@@ -363,12 +348,15 @@ export class BreadcrumbsFilePicker extends BreadcrumbsPicker {
constructor(
parent: HTMLElement,
protected resource: URI,
@IInstantiationService instantiationService: IInstantiationService,
@IThemeService themeService: IThemeService,
@IConfigurationService configService: IConfigurationService,
@IWorkspaceContextService private readonly _workspaceService: IWorkspaceContextService,
@IEditorService private readonly _editorService: IEditorService,
@ITelemetryService telemetryService: ITelemetryService,
) {
super(parent, instantiationService, themeService, configService);
super(parent, resource, instantiationService, themeService, configService, telemetryService);
}
_createTree(container: HTMLElement) {
@@ -406,7 +394,7 @@ export class BreadcrumbsFilePicker extends BreadcrumbsPicker {
});
}
_setInput(element: BreadcrumbElement): Promise<void> {
async _setInput(element: FileElement | OutlineElement2): Promise<void> {
const { uri, kind } = (element as FileElement);
let input: IWorkspace | URI;
if (kind === FileKind.ROOT_FOLDER) {
@@ -416,115 +404,120 @@ export class BreadcrumbsFilePicker extends BreadcrumbsPicker {
}
const tree = this._tree as WorkbenchAsyncDataTree<IWorkspace | URI, IWorkspaceFolder | IFileStat, FuzzyScore>;
return tree.setInput(input).then(() => {
let focusElement: IWorkspaceFolder | IFileStat | undefined;
for (const { element } of tree.getNode().children) {
if (IWorkspaceFolder.isIWorkspaceFolder(element) && isEqual(element.uri, uri)) {
focusElement = element;
break;
} else if (isEqual((element as IFileStat).resource, uri)) {
focusElement = element as IFileStat;
break;
}
await tree.setInput(input);
let focusElement: IWorkspaceFolder | IFileStat | undefined;
for (const { element } of tree.getNode().children) {
if (isWorkspaceFolder(element) && isEqual(element.uri, uri)) {
focusElement = element;
break;
} else if (isEqual((element as IFileStat).resource, uri)) {
focusElement = element as IFileStat;
break;
}
if (focusElement) {
tree.reveal(focusElement, 0.5);
tree.setFocus([focusElement], this._fakeEvent);
}
tree.domFocus();
});
}
if (focusElement) {
tree.reveal(focusElement, 0.5);
tree.setFocus([focusElement], this._fakeEvent);
}
tree.domFocus();
}
protected _getTargetFromEvent(element: any): any | undefined {
if (element && !IWorkspaceFolder.isIWorkspaceFolder(element) && !(element as IFileStat).isDirectory) {
return new FileElement((element as IFileStat).resource, FileKind.FILE);
protected _previewElement(_element: any): IDisposable {
return Disposable.None;
}
async _revealElement(element: IFileStat | IWorkspaceFolder, options: IEditorOptions, sideBySide: boolean): Promise<boolean> {
let resource: URI | undefined;
if (isWorkspaceFolder(element)) {
resource = element.uri;
} else if (!element.isDirectory) {
resource = element.resource;
}
if (resource) {
this._onWillPickElement.fire();
await this._editorService.openEditor({ resource, options }, sideBySide ? SIDE_GROUP : undefined);
return true;
}
return false;
}
}
//#endregion
//#region - Symbols
//#region - Outline
class OutlineTreeSorter<E> implements ITreeSorter<E> {
private _order: 'name' | 'type' | 'position';
constructor(
private comparator: IOutlineComparator<E>,
uri: URI | undefined,
@ITextResourceConfigurationService configService: ITextResourceConfigurationService,
) {
this._order = configService.getValue(uri, 'breadcrumbs.symbolSortOrder');
}
compare(a: E, b: E): number {
if (this._order === 'name') {
return this.comparator.compareByName(a, b);
} else if (this._order === 'type') {
return this.comparator.compareByType(a, b);
} else {
return this.comparator.compareByPosition(a, b);
}
}
}
export class BreadcrumbsOutlinePicker extends BreadcrumbsPicker {
protected readonly _symbolSortOrder: BreadcrumbsConfig<'position' | 'name' | 'type'>;
protected _outlineComparator: OutlineItemComparator;
protected _createTree(container: HTMLElement, input: OutlineElement2) {
constructor(
parent: HTMLElement,
@IInstantiationService instantiationService: IInstantiationService,
@IThemeService themeService: IThemeService,
@IConfigurationService configurationService: IConfigurationService,
@IModeService private readonly _modeService: IModeService,
) {
super(parent, instantiationService, themeService, configurationService);
this._symbolSortOrder = BreadcrumbsConfig.SymbolSortOrder.bindTo(this._configurationService);
this._outlineComparator = new OutlineItemComparator();
}
const { config } = input.outline;
protected _createTree(container: HTMLElement) {
return <WorkbenchDataTree<OutlineModel, any, FuzzyScore>>this._instantiationService.createInstance(
return <WorkbenchDataTree<IOutline<any>, any, FuzzyScore>>this._instantiationService.createInstance(
WorkbenchDataTree,
'BreadcrumbsOutlinePicker',
container,
new OutlineVirtualDelegate(),
[new OutlineGroupRenderer(), this._instantiationService.createInstance(OutlineElementRenderer)],
new OutlineDataSource(),
config.delegate,
config.renderers,
config.treeDataSource,
{
...config.options,
sorter: this._instantiationService.createInstance(OutlineTreeSorter, config.comparator, undefined),
collapseByDefault: true,
expandOnlyOnTwistieClick: true,
multipleSelectionSupport: false,
sorter: this._outlineComparator,
identityProvider: new OutlineIdentityProvider(),
keyboardNavigationLabelProvider: new OutlineNavigationLabelProvider(),
accessibilityProvider: new OutlineAccessibilityProvider(localize('breadcrumbs', "Breadcrumbs")),
filter: this._instantiationService.createInstance(OutlineFilter, 'breadcrumbs')
}
);
}
dispose(): void {
this._symbolSortOrder.dispose();
super.dispose();
}
protected _setInput(input: OutlineElement2): Promise<void> {
protected _setInput(input: BreadcrumbElement): Promise<void> {
const element = input as TreeElement;
const model = OutlineModel.get(element)!;
const tree = this._tree as WorkbenchDataTree<OutlineModel, any, FuzzyScore>;
const viewState = input.outline.captureViewState();
this.restoreViewState = () => { viewState.dispose(); };
const overrideConfiguration = {
resource: model.uri,
overrideIdentifier: this._modeService.getModeIdByFilepathOrFirstLine(model.uri)
};
this._outlineComparator.type = this._getOutlineItemCompareType(overrideConfiguration);
const tree = this._tree as WorkbenchDataTree<IOutline<any>, any, FuzzyScore>;
tree.setInput(model);
if (element !== model) {
tree.reveal(element, 0.5);
tree.setFocus([element], this._fakeEvent);
tree.setInput(input.outline);
if (input.element !== input.outline) {
tree.reveal(input.element, 0.5);
tree.setFocus([input.element], this._fakeEvent);
}
tree.domFocus();
return Promise.resolve();
}
protected _getTargetFromEvent(element: any): any | undefined {
if (element instanceof OutlineElement) {
return element;
}
protected _previewElement(element: any): IDisposable {
const outline: IOutline<any> = this._tree.getInput();
return outline.preview(element);
}
private _getOutlineItemCompareType(overrideConfiguration?: IConfigurationOverrides): OutlineSortOrder {
switch (this._symbolSortOrder.getValue(overrideConfiguration)) {
case 'name':
return OutlineSortOrder.ByName;
case 'type':
return OutlineSortOrder.ByKind;
case 'position':
default:
return OutlineSortOrder.ByPosition;
}
async _revealElement(element: any, options: IEditorOptions, sideBySide: boolean): Promise<boolean> {
this._onWillPickElement.fire();
const outline: IOutline<any> = this._tree.getInput();
await outline.reveal(element, options, sideBySide);
return true;
}
}

View File

@@ -33,7 +33,7 @@ import {
JoinAllGroupsAction, FocusLeftGroup, FocusAboveGroup, FocusRightGroup, FocusBelowGroup, EditorLayoutSingleAction, EditorLayoutTwoColumnsAction, EditorLayoutThreeColumnsAction, EditorLayoutTwoByTwoGridAction,
EditorLayoutTwoRowsAction, EditorLayoutThreeRowsAction, EditorLayoutTwoColumnsBottomAction, EditorLayoutTwoRowsRightAction, NewEditorGroupLeftAction, NewEditorGroupRightAction,
NewEditorGroupAboveAction, NewEditorGroupBelowAction, SplitEditorOrthogonalAction, CloseEditorInAllGroupsAction, NavigateToLastEditLocationAction, ToggleGroupSizesAction, ShowAllEditorsByMostRecentlyUsedAction,
QuickAccessPreviousRecentlyUsedEditorAction, OpenPreviousRecentlyUsedEditorInGroupAction, OpenNextRecentlyUsedEditorInGroupAction, QuickAccessLeastRecentlyUsedEditorAction, QuickAccessLeastRecentlyUsedEditorInGroupAction, ReopenResourcesAction, ToggleEditorTypeAction
QuickAccessPreviousRecentlyUsedEditorAction, OpenPreviousRecentlyUsedEditorInGroupAction, OpenNextRecentlyUsedEditorInGroupAction, QuickAccessLeastRecentlyUsedEditorAction, QuickAccessLeastRecentlyUsedEditorInGroupAction, ReopenResourcesAction, ToggleEditorTypeAction, DuplicateGroupDownAction, DuplicateGroupLeftAction, DuplicateGroupRightAction, DuplicateGroupUpAction
} from 'vs/workbench/browser/parts/editor/editorActions';
import * as editorCommands from 'vs/workbench/browser/parts/editor/editorCommands';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
@@ -42,7 +42,7 @@ import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/co
import { ContextKeyExpr, ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey';
import { isMacintosh } from 'vs/base/common/platform';
import { registerEditorContribution } from 'vs/editor/browser/editorExtensions';
import { OpenWorkspaceButtonContribution } from 'vs/workbench/browser/parts/editor/editorWidgets';
import { OpenWorkspaceButtonContribution } from 'vs/workbench/browser/codeeditor';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { toLocalResource } from 'vs/base/common/resources';
import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions';
@@ -351,6 +351,10 @@ registry.registerWorkbenchAction(SyncActionDescriptor.from(MoveGroupLeftAction,
registry.registerWorkbenchAction(SyncActionDescriptor.from(MoveGroupRightAction, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.RightArrow) }), 'View: Move Editor Group Right', CATEGORIES.View.value);
registry.registerWorkbenchAction(SyncActionDescriptor.from(MoveGroupUpAction, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.UpArrow) }), 'View: Move Editor Group Up', CATEGORIES.View.value);
registry.registerWorkbenchAction(SyncActionDescriptor.from(MoveGroupDownAction, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.DownArrow) }), 'View: Move Editor Group Down', CATEGORIES.View.value);
registry.registerWorkbenchAction(SyncActionDescriptor.from(DuplicateGroupLeftAction), 'View: Duplicate Editor Group Left', CATEGORIES.View.value);
registry.registerWorkbenchAction(SyncActionDescriptor.from(DuplicateGroupRightAction), 'View: Duplicate Editor Group Right', CATEGORIES.View.value);
registry.registerWorkbenchAction(SyncActionDescriptor.from(DuplicateGroupUpAction), 'View: Duplicate Editor Group Up', CATEGORIES.View.value);
registry.registerWorkbenchAction(SyncActionDescriptor.from(DuplicateGroupDownAction), 'View: Duplicate Editor Group Down', CATEGORIES.View.value);
registry.registerWorkbenchAction(SyncActionDescriptor.from(MoveEditorToPreviousGroupAction, { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.LeftArrow, mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.LeftArrow } }), 'View: Move Editor into Previous Group', CATEGORIES.View.value);
registry.registerWorkbenchAction(SyncActionDescriptor.from(MoveEditorToNextGroupAction, { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.RightArrow, mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.RightArrow } }), 'View: Move Editor into Next Group', CATEGORIES.View.value);
registry.registerWorkbenchAction(SyncActionDescriptor.from(MoveEditorToFirstGroupAction, { primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KEY_1, mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.KEY_1 } }), 'View: Move Editor into First Group', CATEGORIES.View.value);

View File

@@ -23,6 +23,7 @@ import { AllEditorsByMostRecentlyUsedQuickAccess, ActiveGroupEditorsByMostRecent
import { Codicon } from 'vs/base/common/codicons';
import { IFilesConfigurationService, AutoSaveMode } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService';
import { openEditorWith, getAllAvailableEditors } from 'vs/workbench/services/editor/common/editorOpenWith';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
export class ExecuteCommandAction extends Action {
@@ -746,12 +747,13 @@ export class CloseEditorInAllGroupsAction extends Action {
}
}
export class BaseMoveGroupAction extends Action {
class BaseMoveCopyGroupAction extends Action {
constructor(
id: string,
label: string,
private direction: GroupDirection,
private isMove: boolean,
private editorGroupService: IEditorGroupsService
) {
super(id, label);
@@ -766,9 +768,18 @@ export class BaseMoveGroupAction extends Action {
}
if (sourceGroup) {
const targetGroup = this.findTargetGroup(sourceGroup);
if (targetGroup) {
this.editorGroupService.moveGroup(sourceGroup, targetGroup, this.direction);
let resultGroup: IEditorGroup | undefined = undefined;
if (this.isMove) {
const targetGroup = this.findTargetGroup(sourceGroup);
if (targetGroup) {
resultGroup = this.editorGroupService.moveGroup(sourceGroup, targetGroup, this.direction);
}
} else {
resultGroup = this.editorGroupService.copyGroup(sourceGroup, sourceGroup, this.direction);
}
if (resultGroup) {
this.editorGroupService.activateGroup(resultGroup);
}
}
}
@@ -801,6 +812,18 @@ export class BaseMoveGroupAction extends Action {
}
}
class BaseMoveGroupAction extends BaseMoveCopyGroupAction {
constructor(
id: string,
label: string,
direction: GroupDirection,
editorGroupService: IEditorGroupsService
) {
super(id, label, direction, true, editorGroupService);
}
}
export class MoveGroupLeftAction extends BaseMoveGroupAction {
static readonly ID = 'workbench.action.moveActiveEditorGroupLeft';
@@ -857,6 +880,74 @@ export class MoveGroupDownAction extends BaseMoveGroupAction {
}
}
class BaseDuplicateGroupAction extends BaseMoveCopyGroupAction {
constructor(
id: string,
label: string,
direction: GroupDirection,
editorGroupService: IEditorGroupsService
) {
super(id, label, direction, false, editorGroupService);
}
}
export class DuplicateGroupLeftAction extends BaseDuplicateGroupAction {
static readonly ID = 'workbench.action.duplicateActiveEditorGroupLeft';
static readonly LABEL = nls.localize('duplicateActiveGroupLeft', "Duplicate Editor Group Left");
constructor(
id: string,
label: string,
@IEditorGroupsService editorGroupService: IEditorGroupsService
) {
super(id, label, GroupDirection.LEFT, editorGroupService);
}
}
export class DuplicateGroupRightAction extends BaseDuplicateGroupAction {
static readonly ID = 'workbench.action.duplicateActiveEditorGroupRight';
static readonly LABEL = nls.localize('duplicateActiveGroupRight', "Duplicate Editor Group Right");
constructor(
id: string,
label: string,
@IEditorGroupsService editorGroupService: IEditorGroupsService
) {
super(id, label, GroupDirection.RIGHT, editorGroupService);
}
}
export class DuplicateGroupUpAction extends BaseDuplicateGroupAction {
static readonly ID = 'workbench.action.duplicateActiveEditorGroupUp';
static readonly LABEL = nls.localize('duplicateActiveGroupUp', "Duplicate Editor Group Up");
constructor(
id: string,
label: string,
@IEditorGroupsService editorGroupService: IEditorGroupsService
) {
super(id, label, GroupDirection.UP, editorGroupService);
}
}
export class DuplicateGroupDownAction extends BaseDuplicateGroupAction {
static readonly ID = 'workbench.action.duplicateActiveEditorGroupDown';
static readonly LABEL = nls.localize('duplicateActiveGroupDown', "Duplicate Editor Group Down");
constructor(
id: string,
label: string,
@IEditorGroupsService editorGroupService: IEditorGroupsService
) {
super(id, label, GroupDirection.DOWN, editorGroupService);
}
}
export class MinimizeOtherGroupsAction extends Action {
static readonly ID = 'workbench.action.minimizeOtherEditors';
@@ -1803,9 +1894,8 @@ export class ReopenResourcesAction extends Action {
constructor(
id: string,
label: string,
@IQuickInputService private readonly quickInputService: IQuickInputService,
@IEditorService private readonly editorService: IEditorService,
@IConfigurationService private readonly configurationService: IConfigurationService
@IInstantiationService private readonly instantiationService: IInstantiationService,
) {
super(id, label);
}
@@ -1823,7 +1913,7 @@ export class ReopenResourcesAction extends Action {
const options = activeEditorPane.options;
const group = activeEditorPane.group;
await openEditorWith(activeInput, undefined, options, group, this.editorService, this.configurationService, this.quickInputService);
await this.instantiationService.invokeFunction(openEditorWith, activeInput, undefined, options, group);
}
}

View File

@@ -23,7 +23,6 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
import { CommandsRegistry, ICommandHandler } from 'vs/platform/commands/common/commands';
import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions';
import { ActiveGroupEditorsByMostRecentlyUsedQuickAccess } from 'vs/workbench/browser/parts/editor/editorQuickAccess';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { ITextEditorOptions } from 'vs/platform/editor/common/editor';
import { openEditorWith } from 'vs/workbench/services/editor/common/editorOpenWith';
@@ -484,7 +483,6 @@ function registerOpenEditorAPICommands(): void {
const editorService = accessor.get(IEditorService);
const editorGroupsService = accessor.get(IEditorGroupsService);
const configurationService = accessor.get(IConfigurationService);
const quickInputService = accessor.get(IQuickInputService);
const [columnArg, optionsArg] = columnAndOptions ?? [];
let group: IEditorGroup | undefined = undefined;
@@ -504,7 +502,7 @@ function registerOpenEditorAPICommands(): void {
const textOptions: ITextEditorOptions = optionsArg ? { ...optionsArg, override: false } : { override: false };
const input = editorService.createEditorInput({ resource: URI.revive(resource) });
return openEditorWith(input, id, textOptions, group, editorService, configurationService, quickInputService);
return openEditorWith(accessor, input, id, textOptions, group);
});
}
@@ -902,24 +900,10 @@ function registerOtherEditorCommands(): void {
id: TOGGLE_KEEP_EDITORS_COMMAND_ID,
handler: accessor => {
const configurationService = accessor.get(IConfigurationService);
const notificationService = accessor.get(INotificationService);
const openerService = accessor.get(IOpenerService);
// Update setting
const currentSetting = configurationService.getValue<boolean>('workbench.editor.enablePreview');
const newSetting = currentSetting === true ? false : true;
configurationService.updateValue('workbench.editor.enablePreview', newSetting);
// Inform user
notificationService.prompt(
Severity.Info,
newSetting ?
nls.localize('enablePreview', "Preview editors have been enabled in settings.") :
nls.localize('disablePreview', "Preview editors have been disabled in settings."),
[{
label: nls.localize('learnMode', "Learn More"), run: () => openerService.open('https://go.microsoft.com/fwlink/?linkid=2147473')
}]
);
}
});

View File

@@ -283,7 +283,8 @@ class DropOverlay extends Themable {
// Open in target group
const options = getActiveTextEditorOptions(sourceGroup, draggedEditor.editor, EditorOptions.create({
pinned: true, // always pin dropped editor
sticky: sourceGroup.isSticky(draggedEditor.editor) // preserve sticky state
sticky: sourceGroup.isSticky(draggedEditor.editor), // preserve sticky state
override: false, // Use `draggedEditor.editor` as is. If it is already a custom editor, it will stay so.
}));
const copyEditor = this.isCopyOperation(event, draggedEditor);
targetGroup.openEditor(draggedEditor.editor, options, copyEditor ? OpenEditorContext.COPY_EDITOR : OpenEditorContext.MOVE_EDITOR);

View File

@@ -322,8 +322,8 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
private createContainerContextMenu(): void {
const menu = this._register(this.menuService.createMenu(MenuId.EmptyEditorGroupContext, this.contextKeyService));
this._register(addDisposableListener(this.element, EventType.CONTEXT_MENU, event => this.onShowContainerContextMenu(menu, event)));
this._register(addDisposableListener(this.element, TouchEventType.Contextmenu, event => this.onShowContainerContextMenu(menu)));
this._register(addDisposableListener(this.element, EventType.CONTEXT_MENU, e => this.onShowContainerContextMenu(menu, e)));
this._register(addDisposableListener(this.element, TouchEventType.Contextmenu, () => this.onShowContainerContextMenu(menu)));
}
private onShowContainerContextMenu(menu: IMenu, e?: MouseEvent): void {
@@ -1704,13 +1704,15 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
layout(width: number, height: number): void {
this.dimension = new Dimension(width, height);
// Ensure editor container gets height as CSS depending on the preferred height of the title control
const titleHeight = this.titleDimensions.height;
const editorHeight = Math.max(0, height - titleHeight);
this.editorContainer.style.height = `${editorHeight}px`;
// Layout the title area first to receive the size it occupies
const titleAreaSize = this.titleAreaControl.layout({
container: this.dimension,
available: new Dimension(width, height - this.editorControl.minimumHeight)
});
// Forward to controls
this.titleAreaControl.layout(new Dimension(width, titleHeight));
// Pass the container width and remaining height to the editor layout
const editorHeight = Math.max(0, height - titleAreaSize.height);
this.editorContainer.style.height = `${editorHeight}px`;
this.editorControl.layout(new Dimension(width, editorHeight));
}
@@ -1769,7 +1771,7 @@ export interface EditorReplacement {
readonly options?: EditorOptions;
}
registerThemingParticipant((theme, collector, environment) => {
registerThemingParticipant((theme, collector) => {
// Letterpress
const letterpress = `./media/letterpress${theme.type === 'dark' ? '-dark' : theme.type === 'hc' ? '-hc' : ''}.svg`;

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/editorstatus';
import * as nls from 'vs/nls';
import { localize } from 'vs/nls';
import { runAtThisOrScheduleAtNextAnimationFrame } from 'vs/base/browser/dom';
import { format, compare, splitLines } from 'vs/base/common/strings';
import { extname, basename, isEqual } from 'vs/base/common/resources';
@@ -283,14 +283,15 @@ class State {
}
}
const nlsSingleSelectionRange = nls.localize('singleSelectionRange', "Ln {0}, Col {1} ({2} selected)");
const nlsSingleSelection = nls.localize('singleSelection', "Ln {0}, Col {1}");
const nlsMultiSelectionRange = nls.localize('multiSelectionRange', "{0} selections ({1} characters selected)");
const nlsMultiSelection = nls.localize('multiSelection', "{0} selections");
const nlsEOLLF = nls.localize('endOfLineLineFeed', "LF");
const nlsEOLCRLF = nls.localize('endOfLineCarriageReturnLineFeed', "CRLF");
const nlsSingleSelectionRange = localize('singleSelectionRange', "Ln {0}, Col {1} ({2} selected)");
const nlsSingleSelection = localize('singleSelection', "Ln {0}, Col {1}");
const nlsMultiSelectionRange = localize('multiSelectionRange', "{0} selections ({1} characters selected)");
const nlsMultiSelection = localize('multiSelection', "{0} selections");
const nlsEOLLF = localize('endOfLineLineFeed', "LF");
const nlsEOLCRLF = localize('endOfLineCarriageReturnLineFeed', "CRLF");
export class EditorStatus extends Disposable implements IWorkbenchContribution {
private readonly tabFocusModeElement = this._register(new MutableDisposable<IStatusbarEntryAccessor>());
private readonly columnSelectionModeElement = this._register(new MutableDisposable<IStatusbarEntryAccessor>());
private readonly screenRedearModeElement = this._register(new MutableDisposable<IStatusbarEntryAccessor>());
@@ -342,14 +343,14 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution {
if (!this.screenReaderNotification) {
this.screenReaderNotification = this.notificationService.prompt(
Severity.Info,
nls.localize('screenReaderDetectedExplanation.question', "Are you using a screen reader to operate VS Code? (word wrap is disabled when using a screen reader)"),
localize('screenReaderDetectedExplanation.question', "Are you using a screen reader to operate VS Code? (word wrap is disabled when using a screen reader)"),
[{
label: nls.localize('screenReaderDetectedExplanation.answerYes', "Yes"),
label: localize('screenReaderDetectedExplanation.answerYes', "Yes"),
run: () => {
this.configurationService.updateValue('editor.accessibilitySupport', 'on');
}
}, {
label: nls.localize('screenReaderDetectedExplanation.answerNo', "No"),
label: localize('screenReaderDetectedExplanation.answerNo', "No"),
run: () => {
this.configurationService.updateValue('editor.accessibilitySupport', 'off');
}
@@ -364,11 +365,11 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution {
private async showIndentationPicker(): Promise<unknown> {
const activeTextEditorControl = getCodeEditor(this.editorService.activeTextEditorControl);
if (!activeTextEditorControl) {
return this.quickInputService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]);
return this.quickInputService.pick([{ label: localize('noEditor', "No text editor active at this time") }]);
}
if (this.editorService.activeEditor?.isReadonly()) {
return this.quickInputService.pick([{ label: nls.localize('noWritableCodeEditor', "The active code editor is read-only.") }]);
return this.quickInputService.pick([{ label: localize('noWritableCodeEditor', "The active code editor is read-only.") }]);
}
const picks: QuickPickInput<IQuickPickItem & { run(): void; }>[] = [
@@ -390,25 +391,25 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution {
};
});
picks.splice(3, 0, { type: 'separator', label: nls.localize('indentConvert', "convert file") });
picks.unshift({ type: 'separator', label: nls.localize('indentView', "change view") });
picks.splice(3, 0, { type: 'separator', label: localize('indentConvert', "convert file") });
picks.unshift({ type: 'separator', label: localize('indentView', "change view") });
const action = await this.quickInputService.pick(picks, { placeHolder: nls.localize('pickAction', "Select Action"), matchOnDetail: true });
const action = await this.quickInputService.pick(picks, { placeHolder: localize('pickAction', "Select Action"), matchOnDetail: true });
return action?.run();
}
private updateTabFocusModeElement(visible: boolean): void {
if (visible) {
if (!this.tabFocusModeElement.value) {
const text = nls.localize('tabFocusModeEnabled', "Tab Moves Focus");
const text = localize('tabFocusModeEnabled', "Tab Moves Focus");
this.tabFocusModeElement.value = this.statusbarService.addEntry({
text,
ariaLabel: text,
tooltip: nls.localize('disableTabMode', "Disable Accessibility Mode"),
tooltip: localize('disableTabMode', "Disable Accessibility Mode"),
command: 'editor.action.toggleTabFocusMode',
backgroundColor: themeColorFromId(STATUS_BAR_PROMINENT_ITEM_BACKGROUND),
color: themeColorFromId(STATUS_BAR_PROMINENT_ITEM_FOREGROUND)
}, 'status.editor.tabFocusMode', nls.localize('status.editor.tabFocusMode', "Accessibility Mode"), StatusbarAlignment.RIGHT, 100.7);
}, 'status.editor.tabFocusMode', localize('status.editor.tabFocusMode', "Accessibility Mode"), StatusbarAlignment.RIGHT, 100.7);
}
} else {
this.tabFocusModeElement.clear();
@@ -418,15 +419,15 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution {
private updateColumnSelectionModeElement(visible: boolean): void {
if (visible) {
if (!this.columnSelectionModeElement.value) {
const text = nls.localize('columnSelectionModeEnabled', "Column Selection");
const text = localize('columnSelectionModeEnabled', "Column Selection");
this.columnSelectionModeElement.value = this.statusbarService.addEntry({
text,
ariaLabel: text,
tooltip: nls.localize('disableColumnSelectionMode', "Disable Column Selection Mode"),
tooltip: localize('disableColumnSelectionMode', "Disable Column Selection Mode"),
command: 'editor.action.toggleColumnSelection',
backgroundColor: themeColorFromId(STATUS_BAR_PROMINENT_ITEM_BACKGROUND),
color: themeColorFromId(STATUS_BAR_PROMINENT_ITEM_FOREGROUND)
}, 'status.editor.columnSelectionMode', nls.localize('status.editor.columnSelectionMode', "Column Selection Mode"), StatusbarAlignment.RIGHT, 100.8);
}, 'status.editor.columnSelectionMode', localize('status.editor.columnSelectionMode', "Column Selection Mode"), StatusbarAlignment.RIGHT, 100.8);
}
} else {
this.columnSelectionModeElement.clear();
@@ -436,14 +437,14 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution {
private updateScreenReaderModeElement(visible: boolean): void {
if (visible) {
if (!this.screenRedearModeElement.value) {
const text = nls.localize('screenReaderDetected', "Screen Reader Optimized");
const text = localize('screenReaderDetected', "Screen Reader Optimized");
this.screenRedearModeElement.value = this.statusbarService.addEntry({
text,
ariaLabel: text,
command: 'showEditorScreenReaderNotification',
backgroundColor: themeColorFromId(STATUS_BAR_PROMINENT_ITEM_BACKGROUND),
color: themeColorFromId(STATUS_BAR_PROMINENT_ITEM_FOREGROUND)
}, 'status.editor.screenReaderMode', nls.localize('status.editor.screenReaderMode', "Screen Reader Mode"), StatusbarAlignment.RIGHT, 100.6);
}, 'status.editor.screenReaderMode', localize('status.editor.screenReaderMode', "Screen Reader Mode"), StatusbarAlignment.RIGHT, 100.6);
}
} else {
this.screenRedearModeElement.clear();
@@ -459,11 +460,11 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution {
const props: IStatusbarEntry = {
text,
ariaLabel: text,
tooltip: nls.localize('gotoLine', "Go to Line/Column"),
tooltip: localize('gotoLine', "Go to Line/Column"),
command: 'workbench.action.gotoLine'
};
this.updateElement(this.selectionElement, props, 'status.editor.selection', nls.localize('status.editor.selection', "Editor Selection"), StatusbarAlignment.RIGHT, 100.5);
this.updateElement(this.selectionElement, props, 'status.editor.selection', localize('status.editor.selection', "Editor Selection"), StatusbarAlignment.RIGHT, 100.5);
}
private updateIndentationElement(text: string | undefined): void {
@@ -475,11 +476,11 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution {
const props: IStatusbarEntry = {
text,
ariaLabel: text,
tooltip: nls.localize('selectIndentation', "Select Indentation"),
tooltip: localize('selectIndentation', "Select Indentation"),
command: 'changeEditorIndentation'
};
this.updateElement(this.indentationElement, props, 'status.editor.indentation', nls.localize('status.editor.indentation', "Editor Indentation"), StatusbarAlignment.RIGHT, 100.4);
this.updateElement(this.indentationElement, props, 'status.editor.indentation', localize('status.editor.indentation', "Editor Indentation"), StatusbarAlignment.RIGHT, 100.4);
}
private updateEncodingElement(text: string | undefined): void {
@@ -491,11 +492,11 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution {
const props: IStatusbarEntry = {
text,
ariaLabel: text,
tooltip: nls.localize('selectEncoding', "Select Encoding"),
tooltip: localize('selectEncoding', "Select Encoding"),
command: 'workbench.action.editor.changeEncoding'
};
this.updateElement(this.encodingElement, props, 'status.editor.encoding', nls.localize('status.editor.encoding', "Editor Encoding"), StatusbarAlignment.RIGHT, 100.3);
this.updateElement(this.encodingElement, props, 'status.editor.encoding', localize('status.editor.encoding', "Editor Encoding"), StatusbarAlignment.RIGHT, 100.3);
}
private updateEOLElement(text: string | undefined): void {
@@ -507,11 +508,11 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution {
const props: IStatusbarEntry = {
text,
ariaLabel: text,
tooltip: nls.localize('selectEOL', "Select End of Line Sequence"),
tooltip: localize('selectEOL', "Select End of Line Sequence"),
command: 'workbench.action.editor.changeEOL'
};
this.updateElement(this.eolElement, props, 'status.editor.eol', nls.localize('status.editor.eol', "Editor End of Line"), StatusbarAlignment.RIGHT, 100.2);
this.updateElement(this.eolElement, props, 'status.editor.eol', localize('status.editor.eol', "Editor End of Line"), StatusbarAlignment.RIGHT, 100.2);
}
private updateModeElement(text: string | undefined): void {
@@ -523,11 +524,11 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution {
const props: IStatusbarEntry = {
text,
ariaLabel: text,
tooltip: nls.localize('selectLanguageMode', "Select Language Mode"),
tooltip: localize('selectLanguageMode', "Select Language Mode"),
command: 'workbench.action.editor.changeLanguageMode'
};
this.updateElement(this.modeElement, props, 'status.editor.mode', nls.localize('status.editor.mode', "Editor Language"), StatusbarAlignment.RIGHT, 100.1);
this.updateElement(this.modeElement, props, 'status.editor.mode', localize('status.editor.mode', "Editor Language"), StatusbarAlignment.RIGHT, 100.1);
}
private updateMetadataElement(text: string | undefined): void {
@@ -539,10 +540,10 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution {
const props: IStatusbarEntry = {
text,
ariaLabel: text,
tooltip: nls.localize('fileInfo', "File Information")
tooltip: localize('fileInfo', "File Information")
};
this.updateElement(this.metadataElement, props, 'status.editor.info', nls.localize('status.editor.info', "File Information"), StatusbarAlignment.RIGHT, 100);
this.updateElement(this.metadataElement, props, 'status.editor.info', localize('status.editor.info', "File Information"), StatusbarAlignment.RIGHT, 100);
}
private updateElement(element: MutableDisposable<IStatusbarEntryAccessor>, props: IStatusbarEntry, id: string, name: string, alignment: StatusbarAlignment, priority: number) {
@@ -730,8 +731,8 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution {
const modelOpts = model.getOptions();
update.indentation = (
modelOpts.insertSpaces
? nls.localize('spacesSize', "Spaces: {0}", modelOpts.indentSize)
: nls.localize({ key: 'tabSize', comment: ['Tab corresponds to the tab key'] }, "Tab Size: {0}", modelOpts.tabSize)
? localize('spacesSize', "Spaces: {0}", modelOpts.indentSize)
: localize({ key: 'tabSize', comment: ['Tab corresponds to the tab key'] }, "Tab Size: {0}", modelOpts.tabSize)
);
}
}
@@ -766,7 +767,7 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution {
if (editorWidget) {
const screenReaderDetected = this.accessibilityService.isScreenReaderOptimized();
if (screenReaderDetected) {
const screenReaderConfiguration = this.configurationService.getValue<IEditorOptions>('editor').accessibilitySupport;
const screenReaderConfiguration = this.configurationService.getValue<IEditorOptions>('editor')?.accessibilitySupport;
if (screenReaderConfiguration === 'auto') {
if (!this.promptedScreenReader) {
this.promptedScreenReader = true;
@@ -921,7 +922,7 @@ class ShowCurrentMarkerInStatusbarContribution extends Disposable {
const line = splitLines(this.currentMarker.message)[0];
const text = `${this.getType(this.currentMarker)} ${line}`;
if (!this.statusBarEntryAccessor.value) {
this.statusBarEntryAccessor.value = this.statusbarService.addEntry({ text: '', ariaLabel: '' }, 'statusbar.currentProblem', nls.localize('currentProblem', "Current Problem"), StatusbarAlignment.LEFT);
this.statusBarEntryAccessor.value = this.statusbarService.addEntry({ text: '', ariaLabel: '' }, 'statusbar.currentProblem', localize('currentProblem', "Current Problem"), StatusbarAlignment.LEFT);
}
this.statusBarEntryAccessor.value.update({ text, ariaLabel: text });
} else {
@@ -934,9 +935,11 @@ class ShowCurrentMarkerInStatusbarContribution extends Disposable {
if (!currentMarker) {
return true;
}
if (!previousMarker) {
return true;
}
return IMarkerData.makeKey(previousMarker) !== IMarkerData.makeKey(currentMarker);
}
@@ -946,6 +949,7 @@ class ShowCurrentMarkerInStatusbarContribution extends Disposable {
case MarkerSeverity.Warning: return '$(warning)';
case MarkerSeverity.Info: return '$(info)';
}
return '';
}
@@ -953,17 +957,21 @@ class ShowCurrentMarkerInStatusbarContribution extends Disposable {
if (!this.configurationService.getValue<boolean>('problems.showCurrentInStatus')) {
return null;
}
if (!this.editor) {
return null;
}
const model = this.editor.getModel();
if (!model) {
return null;
}
const position = this.editor.getPosition();
if (!position) {
return null;
}
return this.markers.find(marker => Range.containsPosition(marker, position)) || null;
}
@@ -971,13 +979,16 @@ class ShowCurrentMarkerInStatusbarContribution extends Disposable {
if (!this.editor) {
return;
}
const model = this.editor.getModel();
if (!model) {
return;
}
if (model && !changedResources.some(r => isEqual(model.uri, r))) {
return;
}
this.updateMarkers();
}
@@ -985,10 +996,12 @@ class ShowCurrentMarkerInStatusbarContribution extends Disposable {
if (!this.editor) {
return;
}
const model = this.editor.getModel();
if (!model) {
return;
}
if (model) {
this.markers = this.markerService.read({
resource: model.uri,
@@ -998,6 +1011,7 @@ class ShowCurrentMarkerInStatusbarContribution extends Disposable {
} else {
this.markers = [];
}
this.updateStatus();
}
}
@@ -1007,9 +1021,11 @@ function compareMarker(a: IMarker, b: IMarker): number {
if (res === 0) {
res = MarkerSeverity.compare(a.severity, b.severity);
}
if (res === 0) {
res = Range.compareRangesUsingStarts(a, b);
}
return res;
}
@@ -1022,7 +1038,7 @@ export class ShowLanguageExtensionsAction extends Action {
@ICommandService private readonly commandService: ICommandService,
@IExtensionGalleryService galleryService: IExtensionGalleryService
) {
super(ShowLanguageExtensionsAction.ID, nls.localize('showLanguageExtensions', "Search Marketplace Extensions for '{0}'...", fileExtension));
super(ShowLanguageExtensionsAction.ID, localize('showLanguageExtensions', "Search Marketplace Extensions for '{0}'...", fileExtension));
this.enabled = galleryService.isEnabled();
}
@@ -1035,7 +1051,7 @@ export class ShowLanguageExtensionsAction extends Action {
export class ChangeModeAction extends Action {
static readonly ID = 'workbench.action.editor.changeLanguageMode';
static readonly LABEL = nls.localize('changeMode', "Change Language Mode");
static readonly LABEL = localize('changeMode', "Change Language Mode");
constructor(
actionId: string,
@@ -1054,14 +1070,14 @@ export class ChangeModeAction extends Action {
async run(): Promise<void> {
const activeEditorPane = this.editorService.activeEditorPane as unknown as { isNotebookEditor?: boolean } | undefined;
if (activeEditorPane?.isNotebookEditor) {
if (activeEditorPane?.isNotebookEditor) { // TODO@rebornix TODO@jrieken debt: https://github.com/microsoft/vscode/issues/114554
// it's inside notebook editor
return this.commandService.executeCommand('notebook.cell.changeLanguage');
}
const activeTextEditorControl = getCodeEditor(this.editorService.activeTextEditorControl);
if (!activeTextEditorControl) {
await this.quickInputService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]);
await this.quickInputService.pick([{ label: localize('noEditor', "No text editor active at this time") }]);
return;
}
@@ -1085,22 +1101,24 @@ export class ChangeModeAction extends Action {
const languages = this.modeService.getRegisteredLanguageNames();
const picks: QuickPickInput[] = languages.sort().map((lang, index) => {
const modeId = this.modeService.getModeIdForLanguageName(lang.toLowerCase()) || 'unknown';
const extensions = this.modeService.getExtensions(lang).join(' ');
let description: string;
if (currentLanguageId === lang) {
description = nls.localize('languageDescription', "({0}) - Configured Language", modeId);
description = localize('languageDescription', "({0}) - Configured Language", modeId);
} else {
description = nls.localize('languageDescriptionConfigured', "({0})", modeId);
description = localize('languageDescriptionConfigured', "({0})", modeId);
}
return {
label: lang,
meta: extensions,
iconClasses: getIconClassesForModeId(modeId),
description
};
});
if (hasLanguageSupport) {
picks.unshift({ type: 'separator', label: nls.localize('languagesPicks', "languages (identifier)") });
picks.unshift({ type: 'separator', label: localize('languagesPicks', "languages (identifier)") });
}
// Offer action to configure via settings
@@ -1115,22 +1133,22 @@ export class ChangeModeAction extends Action {
picks.unshift(galleryAction);
}
configureModeSettings = { label: nls.localize('configureModeSettings', "Configure '{0}' language based settings...", currentLanguageId) };
configureModeSettings = { label: localize('configureModeSettings', "Configure '{0}' language based settings...", currentLanguageId) };
picks.unshift(configureModeSettings);
configureModeAssociations = { label: nls.localize('configureAssociationsExt', "Configure File Association for '{0}'...", ext) };
configureModeAssociations = { label: localize('configureAssociationsExt', "Configure File Association for '{0}'...", ext) };
picks.unshift(configureModeAssociations);
}
// Offer to "Auto Detect"
const autoDetectMode: IQuickPickItem = {
label: nls.localize('autoDetect', "Auto Detect")
label: localize('autoDetect', "Auto Detect")
};
if (hasLanguageSupport) {
picks.unshift(autoDetectMode);
}
const pick = await this.quickInputService.pick(picks, { placeHolder: nls.localize('pickLanguage', "Select Language Mode"), matchOnDescription: true });
const pick = await this.quickInputService.pick(picks, { placeHolder: localize('pickLanguage', "Select Language Mode"), matchOnDescription: true });
if (!pick) {
return;
}
@@ -1178,6 +1196,8 @@ export class ChangeModeAction extends Action {
modeSupport.setMode(languageSelection.languageIdentifier.language);
}
}
activeTextEditorControl.focus();
}
}
@@ -1194,12 +1214,12 @@ export class ChangeModeAction extends Action {
id,
label: lang,
iconClasses: getIconClassesForModeId(id),
description: (id === currentAssociation) ? nls.localize('currentAssociation', "Current Association") : undefined
description: (id === currentAssociation) ? localize('currentAssociation', "Current Association") : undefined
};
});
setTimeout(async () => {
const language = await this.quickInputService.pick(picks, { placeHolder: nls.localize('pickLanguageToConfigure', "Select Language Mode to Associate with '{0}'", extension || base) });
const language = await this.quickInputService.pick(picks, { placeHolder: localize('pickLanguageToConfigure', "Select Language Mode to Associate with '{0}'", extension || base) });
if (language) {
const fileAssociationsConfig = this.configurationService.inspect<{}>(FILES_ASSOCIATIONS_CONFIG);
@@ -1233,7 +1253,7 @@ export interface IChangeEOLEntry extends IQuickPickItem {
export class ChangeEOLAction extends Action {
static readonly ID = 'workbench.action.editor.changeEOL';
static readonly LABEL = nls.localize('changeEndOfLine', "Change End of Line Sequence");
static readonly LABEL = localize('changeEndOfLine', "Change End of Line Sequence");
constructor(
actionId: string,
@@ -1247,12 +1267,12 @@ export class ChangeEOLAction extends Action {
async run(): Promise<void> {
const activeTextEditorControl = getCodeEditor(this.editorService.activeTextEditorControl);
if (!activeTextEditorControl) {
await this.quickInputService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]);
await this.quickInputService.pick([{ label: localize('noEditor', "No text editor active at this time") }]);
return;
}
if (this.editorService.activeEditor?.isReadonly()) {
await this.quickInputService.pick([{ label: nls.localize('noWritableCodeEditor', "The active code editor is read-only.") }]);
await this.quickInputService.pick([{ label: localize('noWritableCodeEditor', "The active code editor is read-only.") }]);
return;
}
@@ -1265,7 +1285,7 @@ export class ChangeEOLAction extends Action {
const selectedIndex = (textModel?.getEOL() === '\n') ? 0 : 1;
const eol = await this.quickInputService.pick(EOLOptions, { placeHolder: nls.localize('pickEndOfLine', "Select End of Line Sequence"), activeItem: EOLOptions[selectedIndex] });
const eol = await this.quickInputService.pick(EOLOptions, { placeHolder: localize('pickEndOfLine', "Select End of Line Sequence"), activeItem: EOLOptions[selectedIndex] });
if (eol) {
const activeCodeEditor = getCodeEditor(this.editorService.activeTextEditorControl);
if (activeCodeEditor?.hasModel() && !this.editorService.activeEditor?.isReadonly()) {
@@ -1275,13 +1295,15 @@ export class ChangeEOLAction extends Action {
textModel.pushStackElement();
}
}
activeTextEditorControl.focus();
}
}
export class ChangeEncodingAction extends Action {
static readonly ID = 'workbench.action.editor.changeEncoding';
static readonly LABEL = nls.localize('changeEncoding', "Change File Encoding");
static readonly LABEL = localize('changeEncoding', "Change File Encoding");
constructor(
actionId: string,
@@ -1296,25 +1318,26 @@ export class ChangeEncodingAction extends Action {
}
async run(): Promise<void> {
if (!getCodeEditor(this.editorService.activeTextEditorControl)) {
await this.quickInputService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]);
const activeTextEditorControl = getCodeEditor(this.editorService.activeTextEditorControl);
if (!activeTextEditorControl) {
await this.quickInputService.pick([{ label: localize('noEditor', "No text editor active at this time") }]);
return;
}
const activeEditorPane = this.editorService.activeEditorPane;
if (!activeEditorPane) {
await this.quickInputService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]);
await this.quickInputService.pick([{ label: localize('noEditor', "No text editor active at this time") }]);
return;
}
const encodingSupport: IEncodingSupport | null = toEditorWithEncodingSupport(activeEditorPane.input);
if (!encodingSupport) {
await this.quickInputService.pick([{ label: nls.localize('noFileEditor', "No file active at this time") }]);
await this.quickInputService.pick([{ label: localize('noFileEditor', "No file active at this time") }]);
return;
}
const saveWithEncodingPick: IQuickPickItem = { label: nls.localize('saveWithEncoding', "Save with Encoding") };
const reopenWithEncodingPick: IQuickPickItem = { label: nls.localize('reopenWithEncoding', "Reopen with Encoding") };
const saveWithEncodingPick: IQuickPickItem = { label: localize('saveWithEncoding', "Save with Encoding") };
const reopenWithEncodingPick: IQuickPickItem = { label: localize('reopenWithEncoding', "Reopen with Encoding") };
if (!Language.isDefaultVariant()) {
const saveWithEncodingAlias = 'Save with Encoding';
@@ -1334,7 +1357,7 @@ export class ChangeEncodingAction extends Action {
} else if (activeEditorPane.input.isReadonly()) {
action = reopenWithEncodingPick;
} else {
action = await this.quickInputService.pick([reopenWithEncodingPick, saveWithEncodingPick], { placeHolder: nls.localize('pickAction', "Select Action"), matchOnDetail: true });
action = await this.quickInputService.pick([reopenWithEncodingPick, saveWithEncodingPick], { placeHolder: localize('pickAction', "Select Action"), matchOnDetail: true });
}
if (!action) {
@@ -1394,11 +1417,11 @@ export class ChangeEncodingAction extends Action {
// If we have a guessed encoding, show it first unless it matches the configured encoding
if (guessedEncoding && configuredEncoding !== guessedEncoding && SUPPORTED_ENCODINGS[guessedEncoding]) {
picks.unshift({ type: 'separator' });
picks.unshift({ id: guessedEncoding, label: SUPPORTED_ENCODINGS[guessedEncoding].labelLong, description: nls.localize('guessedEncoding', "Guessed from content") });
picks.unshift({ id: guessedEncoding, label: SUPPORTED_ENCODINGS[guessedEncoding].labelLong, description: localize('guessedEncoding', "Guessed from content") });
}
const encoding = await this.quickInputService.pick(picks, {
placeHolder: isReopenWithEncoding ? nls.localize('pickEncodingForReopen', "Select File Encoding to Reopen File") : nls.localize('pickEncodingForSave', "Select File Encoding to Save with"),
placeHolder: isReopenWithEncoding ? localize('pickEncodingForReopen', "Select File Encoding to Reopen File") : localize('pickEncodingForSave', "Select File Encoding to Save with"),
activeItem: items[typeof directMatchIndex === 'number' ? directMatchIndex : typeof aliasMatchIndex === 'number' ? aliasMatchIndex : -1]
});
@@ -1414,5 +1437,7 @@ export class ChangeEncodingAction extends Action {
if (typeof encoding.id !== 'undefined' && activeEncodingSupport && activeEncodingSupport.getEncoding() !== encoding.id) {
activeEncodingSupport.setEncoding(encoding.id, isReopenWithEncoding ? EncodingMode.Decode : EncodingMode.Encode); // Set new encoding
}
activeTextEditorControl.focus();
}
}

View File

@@ -14,7 +14,7 @@
flex: auto;
}
.monaco-workbench .part.editor > .content .editor-group-container > .title .title-label {
.monaco-workbench .part.editor > .content .editor-group-container > .title > .label-container > .title-label {
line-height: 35px;
overflow: hidden;
text-overflow: ellipsis;
@@ -22,16 +22,16 @@
padding-left: 20px;
}
.monaco-workbench .part.editor > .content .editor-group-container > .title.breadcrumbs .no-tabs.title-label {
flex: none;
}
.monaco-workbench .part.editor > .content .editor-group-container > .title .monaco-icon-label::before {
height: 35px; /* tweak the icon size of the editor labels when icons are enabled */
.monaco-workbench .part.editor > .content .editor-group-container > .title > .label-container > .title-label > .monaco-icon-label-container {
flex: none; /* helps to show decorations right next to the label and not at the end */
}
/* Breadcrumbs */
.monaco-workbench .part.editor > .content .editor-group-container > .title.breadcrumbs .no-tabs.title-label {
flex: none;
}
.monaco-workbench .part.editor > .content .editor-group-container > .title.breadcrumbs .breadcrumbs-control {
flex: 1 50%;
overflow: hidden;
@@ -62,13 +62,11 @@
.monaco-workbench .part.editor > .content .editor-group-container > .title.breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item.root_folder + .monaco-breadcrumb-item::before,
.monaco-workbench .part.editor > .content .editor-group-container > .title.breadcrumbs .breadcrumbs-control.relative-path .monaco-breadcrumb-item:nth-child(2)::before,
.monaco-workbench.windows .part.editor > .content .editor-group-container > .title.breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item:nth-child(2)::before {
/* workspace folder, item following workspace folder, or relative path -> hide first seperator */
display: none;
display: none; /* workspace folder, item following workspace folder, or relative path -> hide first seperator */
}
.monaco-workbench .part.editor > .content .editor-group-container > .title.breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item.root_folder::after {
/* use dot separator for workspace folder */
content: '\00a0•\00a0';
content: '\00a0•\00a0'; /* use dot separator for workspace folder */
padding: 0;
}
@@ -80,13 +78,18 @@
padding: 0 1px;
}
.monaco-workbench .part.editor > .content .editor-group-container > .title.breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item .codicon:last-child,
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item:last-child .codicon:last-child {
display: none; /* hides chevrons when no tabs visible and when last items */
.monaco-workbench .part.editor > .content .editor-group-container > .title.breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item .codicon:last-child {
display: none; /* hides chevrons when no tabs visible */
}
/* Title Actions */
.monaco-workbench .part.editor > .content .editor-group-container > .title .title-actions {
.monaco-workbench .part.editor > .content .editor-group-container > .title.breadcrumbs .breadcrumbs-control .monaco-icon-label::before {
height: 18px;
padding-right: 2px;
}
/* Editor Actions Toolbar (via title actions) */
.monaco-workbench .part.editor > .content .editor-group-container > .title > .title-actions {
display: flex;
flex: initial;
opacity: 0.5;
@@ -94,6 +97,6 @@
height: 35px;
}
.monaco-workbench .part.editor > .content .editor-group-container.active > .title .title-actions {
.monaco-workbench .part.editor > .content .editor-group-container.active > .title > .title-actions {
opacity: 1;
}

View File

@@ -3,17 +3,45 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/*
################################### z-index explainer ###################################
Tabs have various levels of z-index depending on state, typically:
- scrollbar should be above all
- sticky (compact, shrink) tabs need to be above non-sticky tabs for scroll under effect
including non-sticky tabs-top borders, otherwise these borders would not scroll under
(https://github.com/microsoft/vscode/issues/111641)
- bottom-border needs to be above tabs bottom border to win but also support sticky tabs
(https://github.com/microsoft/vscode/issues/99084) <- this currently cannot be done and
is still broken. putting sticky-tabs above tabs bottom border would not render this
border at all for sticky tabs.
On top of that there is 2 borders with a z-index for a general border below tabs
- tabs bottom border
- editor title bottom border (when breadcrumbs are disabled, this border will appear
same as tabs bottom border)
The following tabls shows the current stacking order:
[z-index] [kind]
7 scrollbar
6 active-tab border-bottom
5 tabs, title border bottom
4 sticky-tab
2 active/dirty-tab border top
0 tab
##########################################################################################
*/
/* Title Container */
.monaco-workbench .part.editor > .content .editor-group-container > .title.tabs > .tabs-and-actions-container {
.monaco-workbench .part.editor > .content .editor-group-container > .title > .tabs-and-actions-container {
display: flex;
position: relative; /* position tabs border bottom or editor actions (when tabs wrap) relative to this container */
}
.monaco-workbench .part.editor > .content .editor-group-container > .title.tabs > .tabs-and-actions-container.tabs-border-bottom {
position: relative;
}
.monaco-workbench .part.editor > .content .editor-group-container > .title.tabs > .tabs-and-actions-container.tabs-border-bottom::after {
.monaco-workbench .part.editor > .content .editor-group-container > .title > .tabs-and-actions-container.tabs-border-bottom::after {
content: '';
position: absolute;
bottom: 0;
@@ -25,12 +53,12 @@
height: 1px;
}
.monaco-workbench .part.editor > .content .editor-group-container > .title.tabs > .tabs-and-actions-container > .monaco-scrollable-element {
.monaco-workbench .part.editor > .content .editor-group-container > .title > .tabs-and-actions-container > .monaco-scrollable-element {
flex: 1;
}
.monaco-workbench .part.editor > .content .editor-group-container > .title.tabs > .tabs-and-actions-container > .monaco-scrollable-element .scrollbar {
z-index: 3; /* on top of tabs */
.monaco-workbench .part.editor > .content .editor-group-container > .title > .tabs-and-actions-container > .monaco-scrollable-element .scrollbar {
z-index: 7;
cursor: default;
}
@@ -46,6 +74,13 @@
overflow: scroll !important;
}
.monaco-workbench .part.editor > .content .editor-group-container > .title > .tabs-and-actions-container.wrapping .tabs-container {
/* Enable wrapping via flex layout and dynamic height */
height: auto;
flex-wrap: wrap;
}
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container::-webkit-scrollbar {
display: none; /* Chrome + Safari: hide scrollbar */
}
@@ -62,6 +97,10 @@
padding-left: 10px;
}
.monaco-workbench .part.editor > .content .editor-group-container > .title > .tabs-and-actions-container.wrapping .tabs-container > .tab:last-child {
margin-right: var(--last-tab-margin-right); /* when tabs wrap, we need a margin away from the absolute positioned editor actions */
}
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink.has-icon.tab-actions-right,
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink.has-icon.tab-actions-off:not(.sticky-compact) {
padding-left: 5px; /* reduce padding when we show icons and are in shrinking mode and tab actions is not left (unless sticky-compact) */
@@ -74,6 +113,10 @@
flex-shrink: 0;
}
.monaco-workbench .part.editor > .content .editor-group-container > .title > .tabs-and-actions-container.wrapping .tabs-container > .tab.sizing-fit {
flex-grow: 1; /* grow the tabs to fill each row for a more homogeneous look when tabs wrap */
}
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink {
min-width: 80px;
flex-basis: 0; /* all tabs are even */
@@ -89,7 +132,7 @@
/** Sticky compact/shrink tabs do not scroll in case of overflow and are always above unsticky tabs which scroll under */
position: sticky;
z-index: 1;
z-index: 4;
/** Sticky compact/shrink tabs are even and never grow */
flex-basis: 0;
@@ -118,9 +161,7 @@
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container.disable-sticky-tabs > .tab.sizing-shrink.sticky-compact,
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container.disable-sticky-tabs > .tab.sizing-fit.sticky-shrink,
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container.disable-sticky-tabs > .tab.sizing-shrink.sticky-shrink {
/** Disable sticky positions for sticky compact/shrink tabs if the available space is too little */
position: static;
position: static; /** disable sticky positions for sticky compact/shrink tabs if the available space is too little */
}
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.tab-actions-left .action-label {
@@ -132,7 +173,7 @@
content: '';
display: flex;
flex: 0;
width: 5px; /* Reserve space to hide tab fade when close button is left or off (fixes https://github.com/microsoft/vscode/issues/45728) */
width: 5px; /* reserve space to hide tab fade when close button is left or off (fixes https://github.com/microsoft/vscode/issues/45728) */
}
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink.tab-actions-left {
@@ -167,24 +208,26 @@
display: block;
position: absolute;
left: 0;
z-index: 6; /* over possible title border */
pointer-events: none;
width: 100%;
}
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.active.tab-border-top > .tab-border-top-container {
z-index: 2;
top: 0;
height: 1px;
background-color: var(--tab-border-top-color);
}
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.active.tab-border-bottom > .tab-border-bottom-container {
z-index: 6;
bottom: 0;
height: 1px;
background-color: var(--tab-border-bottom-color);
}
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.dirty-border-top > .tab-border-top-container {
z-index: 2;
top: 0;
height: 2px;
background-color: var(--tab-dirty-border-top-color);
@@ -203,13 +246,16 @@
}
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink > .tab-label::after {
content: '';
content: ''; /* enables a linear gradient to overlay the end of the label when tabs overflow */
position: absolute;
right: 0;
height: 100%;
width: 5px;
opacity: 1;
padding: 0;
/* the rules below ensure that the gradient does not impact top/bottom borders (https://github.com/microsoft/vscode/issues/115129) */
top: 1px;
bottom: 1px;
height: calc(100% - 2px);
}
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink:focus > .tab-label::after {
@@ -243,7 +289,7 @@
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.tab-actions-right.sizing-shrink > .tab-actions {
flex: 0;
overflow: hidden; /* let the tab actions be pushed out of view when sizing is set to shrink to make more room... */
overflow: hidden; /* let the tab actions be pushed out of view when sizing is set to shrink to make more room */
}
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.dirty.tab-actions-right.sizing-shrink > .tab-actions,
@@ -301,7 +347,7 @@
margin-right: 0.5em;
}
/* No Tab Actions */
/* Tab Actions: Off */
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.tab-actions-off {
padding-right: 10px; /* give a little bit more room if tab actions is off */
@@ -324,15 +370,6 @@
pointer-events: none; /* don't allow tab actions to be clicked when running without tab actions */
}
/* Editor Actions */
.monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions {
cursor: default;
flex: initial;
padding: 0 8px 0 4px;
height: 35px;
}
/* Breadcrumbs */
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-breadcrumbs .breadcrumbs-control {
@@ -350,11 +387,17 @@
height: 22px; /* tweak the icon size of the editor labels when icons are enabled */
}
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item {
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-breadcrumbs .breadcrumbs-control .outline-element-icon {
padding-right: 3px;
height: 22px; /* tweak the icon size of the editor labels when icons are enabled */
line-height: 22px;
}
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item {
max-width: 80%;
}
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item::before {
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item::before {
width: 16px;
height: 22px;
display: flex;
@@ -362,6 +405,27 @@
justify-content: center;
}
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item:last-child {
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item:last-child {
padding-right: 8px;
}
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item:last-child .codicon:last-child {
display: none; /* hides chevrons when last item */
}
/* Editor Actions Toolbar */
.monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions {
cursor: default;
flex: initial;
padding: 0 8px 0 4px;
height: 35px;
}
.monaco-workbench .part.editor > .content .editor-group-container > .title > .tabs-and-actions-container.wrapping .editor-actions {
/* When tabs are wrapped, position the editor actions at the end of the very last row */
position: absolute;
bottom: 0;
right: 0;
}

View File

@@ -26,6 +26,15 @@
cursor: pointer;
}
.monaco-workbench .part.editor > .content .editor-group-container > .title .monaco-icon-label::before {
height: 35px; /* tweak the icon size of the editor labels when icons are enabled */
}
.monaco-workbench .part.editor > .content .editor-group-container > .title.breadcrumbs .monaco-icon-label::after,
.monaco-workbench .part.editor > .content .editor-group-container > .title.tabs .monaco-icon-label::after {
padding-right: 0; /* by default the icon label has a padding right and this isn't wanted when not showing tabs and not showing breadcrumbs */
}
/* Title Actions */
.monaco-workbench .part.editor > .content .editor-group-container > .title .title-actions .action-label:not(span),
@@ -59,13 +68,12 @@
opacity: 0.4;
}
/* Drag Cursor */
/* Drag and Drop */
.monaco-workbench .part.editor > .content .editor-group-container > .title {
cursor: grab;
}
/* Drag and Drop Feedback */
.monaco-editor-group-drag-image {
display: inline-block;
padding: 1px 7px;

View File

@@ -5,7 +5,7 @@
import 'vs/css!./media/notabstitlecontrol';
import { EditorResourceAccessor, Verbosity, IEditorInput, IEditorPartOptions, SideBySideEditor } from 'vs/workbench/common/editor';
import { TitleControl, IToolbarActions } from 'vs/workbench/browser/parts/editor/titleControl';
import { TitleControl, IToolbarActions, ITitleControlDimensions } from 'vs/workbench/browser/parts/editor/titleControl';
import { ResourceLabel, IResourceLabel } from 'vs/workbench/browser/labels';
import { TAB_ACTIVE_FOREGROUND, TAB_UNFOCUSED_ACTIVE_FOREGROUND } from 'vs/workbench/common/theme';
import { EventType as TouchEventType, GestureEvent, Gesture } from 'vs/base/browser/touch';
@@ -15,6 +15,7 @@ import { CLOSE_EDITOR_COMMAND_ID } from 'vs/workbench/browser/parts/editor/edito
import { Color } from 'vs/base/common/color';
import { withNullAsUndefined, assertIsDefined, assertAllDefined } from 'vs/base/common/types';
import { IEditorGroupTitleDimensions } from 'vs/workbench/browser/parts/editor/editor';
import { equals } from 'vs/base/common/objects';
interface IRenderedEditorLabel {
editor?: IEditorInput;
@@ -50,7 +51,7 @@ export class NoTabsTitleControl extends TitleControl {
// Breadcrumbs
this.createBreadcrumbsControl(labelContainer, { showFileIcons: false, showSymbolIcons: true, showDecorationColors: false, breadcrumbsBackground: () => Color.transparent });
titleContainer.classList.toggle('breadcrumbs', Boolean(this.breadcrumbsControl));
this._register({ dispose: () => titleContainer.classList.remove('breadcrumbs') }); // import to remove because the container is a shared dom node
this._register({ dispose: () => titleContainer.classList.remove('breadcrumbs') }); // important to remove because the container is a shared dom node
// Right Actions Container
const actionsContainer = document.createElement('div');
@@ -67,16 +68,16 @@ export class NoTabsTitleControl extends TitleControl {
this.enableGroupDragging(titleContainer);
// Pin on double click
this._register(addDisposableListener(titleContainer, EventType.DBLCLICK, (e: MouseEvent) => this.onTitleDoubleClick(e)));
this._register(addDisposableListener(titleContainer, EventType.DBLCLICK, e => this.onTitleDoubleClick(e)));
// Detect mouse click
this._register(addDisposableListener(titleContainer, EventType.AUXCLICK, (e: MouseEvent) => this.onTitleAuxClick(e)));
this._register(addDisposableListener(titleContainer, EventType.AUXCLICK, e => this.onTitleAuxClick(e)));
// Detect touch
this._register(addDisposableListener(titleContainer, TouchEventType.Tap, (e: GestureEvent) => this.onTitleTap(e)));
// Context Menu
this._register(addDisposableListener(titleContainer, EventType.CONTEXT_MENU, (e: Event) => {
this._register(addDisposableListener(titleContainer, EventType.CONTEXT_MENU, e => {
if (this.group.activeEditor) {
this.onContextMenu(this.group.activeEditor, e, titleContainer);
}
@@ -188,7 +189,7 @@ export class NoTabsTitleControl extends TitleControl {
}
updateOptions(oldOptions: IEditorPartOptions, newOptions: IEditorPartOptions): void {
if (oldOptions.labelFormat !== newOptions.labelFormat) {
if (oldOptions.labelFormat !== newOptions.labelFormat || !equals(oldOptions.decorations, newOptions.decorations)) {
this.redraw();
}
}
@@ -236,6 +237,7 @@ export class NoTabsTitleControl extends TitleControl {
private redraw(): void {
const editor = withNullAsUndefined(this.group.activeEditor);
const options = this.accessor.partOptions;
const isEditorPinned = editor ? this.group.isPinned(editor) : false;
const isGroupActive = this.accessor.activeGroup === this.group;
@@ -291,14 +293,18 @@ export class NoTabsTitleControl extends TitleControl {
{
title,
italic: !isEditorPinned,
extraClasses: ['no-tabs', 'title-label']
extraClasses: ['no-tabs', 'title-label'],
fileDecorations: {
colors: Boolean(options.decorations?.colors),
badges: Boolean(options.decorations?.badges)
},
}
);
if (isGroupActive) {
editorLabel.element.style.color = this.getColor(TAB_ACTIVE_FOREGROUND) || '';
titleContainer.style.color = this.getColor(TAB_ACTIVE_FOREGROUND) || '';
} else {
editorLabel.element.style.color = this.getColor(TAB_UNFOCUSED_ACTIVE_FOREGROUND) || '';
titleContainer.style.color = this.getColor(TAB_UNFOCUSED_ACTIVE_FOREGROUND) || '';
}
// Update Editor Actions Toolbar
@@ -333,9 +339,11 @@ export class NoTabsTitleControl extends TitleControl {
};
}
layout(dimension: Dimension): void {
layout(dimensions: ITitleControlDimensions): Dimension {
if (this.breadcrumbsControl) {
this.breadcrumbsControl.layout(undefined);
}
return new Dimension(dimensions.container.width, this.getDimensions().height);
}
}

View File

@@ -1,121 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
import { Emitter } from 'vs/base/common/event';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IRange } from 'vs/editor/common/core/range';
import { CursorChangeReason, ICursorPositionChangedEvent } from 'vs/editor/common/controller/cursorEvents';
import { ModelDecorationOptions } from 'vs/editor/common/model/textModel';
import { ICodeEditor, isCodeEditor, isCompositeEditor } from 'vs/editor/browser/editorBrowser';
import { TrackedRangeStickiness, IModelDecorationsChangeAccessor } from 'vs/editor/common/model';
import { isEqual } from 'vs/base/common/resources';
export interface IRangeHighlightDecoration {
resource: URI;
range: IRange;
isWholeLine?: boolean;
}
export class RangeHighlightDecorations extends Disposable {
private rangeHighlightDecorationId: string | null = null;
private editor: ICodeEditor | null = null;
private readonly editorDisposables = this._register(new DisposableStore());
private readonly _onHighlightRemoved: Emitter<void> = this._register(new Emitter<void>());
readonly onHighlightRemoved = this._onHighlightRemoved.event;
constructor(
@IEditorService private readonly editorService: IEditorService
) {
super();
}
removeHighlightRange() {
if (this.editor && this.editor.getModel() && this.rangeHighlightDecorationId) {
this.editor.deltaDecorations([this.rangeHighlightDecorationId], []);
this._onHighlightRemoved.fire();
}
this.rangeHighlightDecorationId = null;
}
highlightRange(range: IRangeHighlightDecoration, editor?: any) {
editor = editor ?? this.getEditor(range);
if (isCodeEditor(editor)) {
this.doHighlightRange(editor, range);
} else if (isCompositeEditor(editor) && isCodeEditor(editor.activeCodeEditor)) {
this.doHighlightRange(editor.activeCodeEditor, range);
}
}
private doHighlightRange(editor: ICodeEditor, selectionRange: IRangeHighlightDecoration) {
this.removeHighlightRange();
editor.changeDecorations((changeAccessor: IModelDecorationsChangeAccessor) => {
this.rangeHighlightDecorationId = changeAccessor.addDecoration(selectionRange.range, this.createRangeHighlightDecoration(selectionRange.isWholeLine));
});
this.setEditor(editor);
}
private getEditor(resourceRange: IRangeHighlightDecoration): ICodeEditor | undefined {
const activeEditor = this.editorService.activeEditor;
const resource = activeEditor && activeEditor.resource;
if (resource && isEqual(resource, resourceRange.resource)) {
return this.editorService.activeTextEditorControl as ICodeEditor;
}
return undefined;
}
private setEditor(editor: ICodeEditor) {
if (this.editor !== editor) {
this.editorDisposables.clear();
this.editor = editor;
this.editorDisposables.add(this.editor.onDidChangeCursorPosition((e: ICursorPositionChangedEvent) => {
if (
e.reason === CursorChangeReason.NotSet
|| e.reason === CursorChangeReason.Explicit
|| e.reason === CursorChangeReason.Undo
|| e.reason === CursorChangeReason.Redo
) {
this.removeHighlightRange();
}
}));
this.editorDisposables.add(this.editor.onDidChangeModel(() => { this.removeHighlightRange(); }));
this.editorDisposables.add(this.editor.onDidDispose(() => {
this.removeHighlightRange();
this.editor = null;
}));
}
}
private static readonly _WHOLE_LINE_RANGE_HIGHLIGHT = ModelDecorationOptions.register({
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
className: 'rangeHighlight',
isWholeLine: true
});
private static readonly _RANGE_HIGHLIGHT = ModelDecorationOptions.register({
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
className: 'rangeHighlight'
});
private createRangeHighlightDecoration(isWholeLine: boolean = true): ModelDecorationOptions {
return (isWholeLine ? RangeHighlightDecorations._WHOLE_LINE_RANGE_HIGHLIGHT : RangeHighlightDecorations._RANGE_HIGHLIGHT);
}
dispose() {
super.dispose();
if (this.editor && this.editor.getModel()) {
this.removeHighlightRange();
this.editor = null;
}
}
}

View File

@@ -18,19 +18,18 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IMenuService } from 'vs/platform/actions/common/actions';
import { TitleControl } from 'vs/workbench/browser/parts/editor/titleControl';
import { ITitleControlDimensions, TitleControl } from 'vs/workbench/browser/parts/editor/titleControl';
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
import { IDisposable, dispose, DisposableStore, combinedDisposable, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { ScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
import { ScrollbarVisibility } from 'vs/base/common/scrollable';
import { getOrSet } from 'vs/base/common/map';
import { IThemeService, registerThemingParticipant, IColorTheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService';
import { IThemeService, registerThemingParticipant } 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_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 } from 'vs/workbench/common/theme';
import { activeContrastBorder, contrastBorder, editorBackground, breadcrumbsBackground } from 'vs/platform/theme/common/colorRegistry';
import { ResourcesDropHandler, DraggedEditorIdentifier, DraggedEditorGroupIdentifier, DragAndDropObserver } from 'vs/workbench/browser/dnd';
import { Color } from 'vs/base/common/color';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { MergeGroupMode, IMergeGroupOptions, GroupsArrangement, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { addDisposableListener, EventType, EventHelper, Dimension, scheduleAtNextAnimationFrame, findParentWithClass, clearNode } from 'vs/base/browser/dom';
import { localize } from 'vs/nls';
@@ -48,6 +47,7 @@ import { IPath, win32, posix } from 'vs/base/common/path';
import { insert } from 'vs/base/common/arrays';
import { ColorScheme } from 'vs/platform/theme/common/theme';
import { isSafari } from 'vs/base/browser/browser';
import { equals } from 'vs/base/common/objects';
interface IEditorInputLabel {
name?: string;
@@ -73,6 +73,9 @@ export class TabsTitleControl extends TitleControl {
private static readonly TAB_HEIGHT = 35;
private static readonly MOUSE_WHEEL_EVENT_THRESHOLD = 150;
private static readonly MOUSE_WHEEL_DISTANCE_THRESHOLD = 1.5;
private titleContainer: HTMLElement | undefined;
private tabsAndActionsContainer: HTMLElement | undefined;
private tabsContainer: HTMLElement | undefined;
@@ -87,12 +90,18 @@ export class TabsTitleControl extends TitleControl {
private tabActionBars: ActionBar[] = [];
private tabDisposables: IDisposable[] = [];
private dimension: Dimension | undefined;
private dimensions: ITitleControlDimensions & { used?: Dimension } = {
container: Dimension.None,
available: Dimension.None
};
private readonly layoutScheduled = this._register(new MutableDisposable());
private blockRevealActiveTab: boolean | undefined;
private path: IPath = isWindows ? win32 : posix;
private lastMouseWheelEventTime = 0;
constructor(
parent: HTMLElement,
accessor: IEditorGroupsAccessor,
@@ -106,19 +115,21 @@ export class TabsTitleControl extends TitleControl {
@IMenuService menuService: IMenuService,
@IQuickInputService quickInputService: IQuickInputService,
@IThemeService themeService: IThemeService,
@IExtensionService extensionService: IExtensionService,
@IConfigurationService configurationService: IConfigurationService,
@IFileService fileService: IFileService,
@IEditorService private readonly editorService: EditorServiceImpl,
@IPathService private readonly pathService: IPathService,
@IEditorGroupsService private readonly editorGroupService: IEditorGroupsService
) {
super(parent, accessor, group, contextMenuService, instantiationService, contextKeyService, keybindingService, telemetryService, notificationService, menuService, quickInputService, themeService, extensionService, configurationService, fileService);
super(parent, accessor, group, contextMenuService, instantiationService, contextKeyService, keybindingService, telemetryService, notificationService, menuService, quickInputService, themeService, configurationService, fileService);
// Resolve the correct path library for the OS we are on
// If we are connected to remote, this accounts for the
// remote OS.
(async () => this.path = await this.pathService.path)();
// React to decorations changing for our resource labels
this._register(this.tabResourceLabels.onDidChangeDecorations(() => this.doHandleDecorationsChange()));
}
protected create(parent: HTMLElement): void {
@@ -151,7 +162,7 @@ export class TabsTitleControl extends TitleControl {
// Editor Actions Toolbar
this.createEditorActionsToolBar(this.editorToolbarContainer);
// Breadcrumbs (are on a separate row below tabs and actions)
// Breadcrumbs
const breadcrumbsContainer = document.createElement('div');
breadcrumbsContainer.classList.add('tabs-breadcrumbs');
this.titleContainer.appendChild(breadcrumbsContainer);
@@ -242,7 +253,7 @@ export class TabsTitleControl extends TitleControl {
});
// Prevent auto-scrolling (https://github.com/microsoft/vscode/issues/16690)
this._register(addDisposableListener(tabsContainer, EventType.MOUSE_DOWN, (e: MouseEvent) => {
this._register(addDisposableListener(tabsContainer, EventType.MOUSE_DOWN, e => {
if (e.button === 1) {
e.preventDefault();
}
@@ -337,8 +348,27 @@ export class TabsTitleControl extends TitleControl {
}
}
// Figure out scrolling direction
const nextEditor = this.group.getEditorByIndex(this.group.getIndexOfEditor(activeEditor) + (e.deltaX < 0 || e.deltaY < 0 /* scrolling up */ ? -1 : 1));
// Ignore event if the last one happened too recently (https://github.com/microsoft/vscode/issues/96409)
// The restriction is relaxed according to the absolute value of `deltaX` and `deltaY`
// to support discrete (mouse wheel) and contiguous scrolling (touchpad) equally well
const now = Date.now();
if (now - this.lastMouseWheelEventTime < TabsTitleControl.MOUSE_WHEEL_EVENT_THRESHOLD - 2 * (Math.abs(e.deltaX) + Math.abs(e.deltaY))) {
return;
}
this.lastMouseWheelEventTime = now;
// Figure out scrolling direction but ignore it if too subtle
let tabSwitchDirection: number;
if (e.deltaX + e.deltaY < - TabsTitleControl.MOUSE_WHEEL_DISTANCE_THRESHOLD) {
tabSwitchDirection = -1;
} else if (e.deltaX + e.deltaY > TabsTitleControl.MOUSE_WHEEL_DISTANCE_THRESHOLD) {
tabSwitchDirection = 1;
} else {
return;
}
const nextEditor = this.group.getEditorByIndex(this.group.getIndexOfEditor(activeEditor) + tabSwitchDirection);
if (!nextEditor) {
return;
}
@@ -351,12 +381,19 @@ export class TabsTitleControl extends TitleControl {
}));
}
private doHandleDecorationsChange(): void {
// A change to decorations potentially has an impact on the size of tabs
// so we need to trigger a layout in that case to adjust things
this.layout(this.dimensions);
}
protected updateEditorActionsToolbar(): void {
super.updateEditorActionsToolbar();
// Changing the actions in the toolbar can have an impact on the size of the
// tab container, so we need to layout the tabs to make sure the active is visible
this.layout(this.dimension);
this.layout(this.dimensions);
}
openEditor(editor: IEditorInput): void {
@@ -439,7 +476,7 @@ export class TabsTitleControl extends TitleControl {
});
// Moving an editor requires a layout to keep the active editor visible
this.layout(this.dimension);
this.layout(this.dimensions);
}
pinEditor(editor: IEditorInput): void {
@@ -466,7 +503,7 @@ export class TabsTitleControl extends TitleControl {
});
// A change to the sticky state requires a layout to keep the active editor visible
this.layout(this.dimension);
this.layout(this.dimensions);
}
setActive(isGroupActive: boolean): void {
@@ -478,7 +515,7 @@ export class TabsTitleControl extends TitleControl {
// Activity has an impact on the toolbar, so we need to update and layout
this.updateEditorActionsToolbar();
this.layout(this.dimension);
this.layout(this.dimensions);
}
private updateEditorLabelAggregator = this._register(new RunOnceScheduler(() => this.updateEditorLabels(), 0));
@@ -504,7 +541,7 @@ export class TabsTitleControl extends TitleControl {
});
// A change to a label requires a layout to keep the active editor visible
this.layout(this.dimension);
this.layout(this.dimensions);
}
updateEditorDirty(editor: IEditorInput): void {
@@ -531,7 +568,9 @@ export class TabsTitleControl extends TitleControl {
oldOptions.pinnedTabSizing !== newOptions.pinnedTabSizing ||
oldOptions.showIcons !== newOptions.showIcons ||
oldOptions.hasIcons !== newOptions.hasIcons ||
oldOptions.highlightModifiedTabs !== newOptions.highlightModifiedTabs
oldOptions.highlightModifiedTabs !== newOptions.highlightModifiedTabs ||
oldOptions.wrapTabs !== newOptions.wrapTabs ||
!equals(oldOptions.decorations, newOptions.decorations)
) {
this.redraw();
}
@@ -648,7 +687,7 @@ export class TabsTitleControl extends TitleControl {
};
// Open on Click / Touch
disposables.add(addDisposableListener(tab, EventType.MOUSE_DOWN, (e: MouseEvent) => handleClickOrTouch(e)));
disposables.add(addDisposableListener(tab, EventType.MOUSE_DOWN, e => handleClickOrTouch(e)));
disposables.add(addDisposableListener(tab, TouchEventType.Tap, (e: GestureEvent) => handleClickOrTouch(e)));
// Touch Scroll Support
@@ -657,14 +696,14 @@ export class TabsTitleControl extends TitleControl {
}));
// Prevent flicker of focus outline on tab until editor got focus
disposables.add(addDisposableListener(tab, EventType.MOUSE_UP, (e: MouseEvent) => {
disposables.add(addDisposableListener(tab, EventType.MOUSE_UP, e => {
EventHelper.stop(e);
tab.blur();
}));
// Close on mouse middle click
disposables.add(addDisposableListener(tab, EventType.AUXCLICK, (e: MouseEvent) => {
disposables.add(addDisposableListener(tab, EventType.AUXCLICK, e => {
if (e.button === 1 /* Middle Button*/) {
EventHelper.stop(e, true /* for https://github.com/microsoft/vscode/issues/56715 */);
@@ -674,7 +713,7 @@ export class TabsTitleControl extends TitleControl {
}));
// Context menu on Shift+F10
disposables.add(addDisposableListener(tab, EventType.KEY_DOWN, (e: KeyboardEvent) => {
disposables.add(addDisposableListener(tab, EventType.KEY_DOWN, e => {
const event = new StandardKeyboardEvent(e);
if (event.shiftKey && event.keyCode === KeyCode.F10) {
showContextMenu(e);
@@ -687,7 +726,7 @@ export class TabsTitleControl extends TitleControl {
}));
// Keyboard accessibility
disposables.add(addDisposableListener(tab, EventType.KEY_UP, (e: KeyboardEvent) => {
disposables.add(addDisposableListener(tab, EventType.KEY_UP, e => {
const event = new StandardKeyboardEvent(e);
let handled = false;
@@ -750,7 +789,7 @@ export class TabsTitleControl extends TitleControl {
});
// Context menu
disposables.add(addDisposableListener(tab, EventType.CONTEXT_MENU, (e: Event) => {
disposables.add(addDisposableListener(tab, EventType.CONTEXT_MENU, e => {
EventHelper.stop(e, true);
const input = this.group.getEditorByIndex(index);
@@ -760,7 +799,7 @@ export class TabsTitleControl extends TitleControl {
}, true /* use capture to fix https://github.com/microsoft/vscode/issues/19145 */));
// Drag support
disposables.add(addDisposableListener(tab, EventType.DRAG_START, (e: DragEvent) => {
disposables.add(addDisposableListener(tab, EventType.DRAG_START, e => {
const editor = this.group.getEditorByIndex(index);
if (!editor) {
return;
@@ -1022,7 +1061,7 @@ export class TabsTitleControl extends TitleControl {
this.updateEditorActionsToolbar();
// Ensure the active tab is always revealed
this.layout(this.dimension);
this.layout(this.dimensions);
}
private redrawTab(editor: IEditorInput, index: number, tabContainer: HTMLElement, tabLabelWidget: IResourceLabel, tabLabel: IEditorInputLabel, tabActionBar: ActionBar): void {
@@ -1092,12 +1131,14 @@ export class TabsTitleControl extends TitleControl {
// or their first character of the name otherwise
let name: string | undefined;
let forceLabel = false;
let forceDisableBadgeDecorations = false;
let description: string;
if (options.pinnedTabSizing === 'compact' && this.group.isSticky(index)) {
const isShowingIcons = options.showIcons && options.hasIcons;
name = isShowingIcons ? '' : tabLabel.name?.charAt(0).toUpperCase();
description = '';
forceLabel = true;
forceDisableBadgeDecorations = true; // not enough space when sticky tabs are compact
} else {
name = tabLabel.name;
description = tabLabel.description || '';
@@ -1116,7 +1157,16 @@ export class TabsTitleControl extends TitleControl {
// Label
tabLabelWidget.setResource(
{ name, description, resource: EditorResourceAccessor.getOriginalUri(editor, { supportSideBySide: SideBySideEditor.BOTH }) },
{ title, extraClasses: ['tab-label'], italic: !this.group.isPinned(editor), forceLabel }
{
title,
extraClasses: ['tab-label'],
italic: !this.group.isPinned(editor),
forceLabel,
fileDecorations: {
colors: Boolean(options.decorations?.colors),
badges: forceDisableBadgeDecorations ? false : Boolean(options.decorations?.badges)
}
}
);
// Tests helper
@@ -1234,62 +1284,179 @@ export class TabsTitleControl extends TitleControl {
}
getDimensions(): IEditorGroupTitleDimensions {
let height = TabsTitleControl.TAB_HEIGHT;
if (this.breadcrumbsControl && !this.breadcrumbsControl.isHidden()) {
height += BreadcrumbsControl.HEIGHT;
let height: number;
// Wrap: we need to ask `offsetHeight` to get
// the real height of the title area with wrapping.
if (this.accessor.partOptions.wrapTabs && this.tabsAndActionsContainer?.classList.contains('wrapping')) {
height = this.tabsAndActionsContainer.offsetHeight;
} else {
height = TabsTitleControl.TAB_HEIGHT;
}
return {
height,
offset: TabsTitleControl.TAB_HEIGHT
};
const offset = height;
// Account for breadcrumbs if visible
if (this.breadcrumbsControl && !this.breadcrumbsControl.isHidden()) {
height += BreadcrumbsControl.HEIGHT; // Account for breadcrumbs if visible
}
return { height, offset };
}
layout(dimension: Dimension | undefined): void {
this.dimension = dimension;
layout(dimensions: ITitleControlDimensions): Dimension {
const activeTabAndIndex = this.group.activeEditor ? this.getTabAndIndex(this.group.activeEditor) : undefined;
if (!activeTabAndIndex || !this.dimension) {
return;
}
// Remember dimensions that we get
Object.assign(this.dimensions, dimensions);
// The layout of tabs can be an expensive operation because we access DOM properties
// that can result in the browser doing a full page layout to validate them. To buffer
// this a little bit we try at least to schedule this work on the next animation frame.
if (!this.layoutScheduled.value) {
this.layoutScheduled.value = scheduleAtNextAnimationFrame(() => {
const dimension = assertIsDefined(this.dimension);
this.doLayout(dimension);
this.doLayout(this.dimensions);
this.layoutScheduled.clear();
});
}
// First time layout: compute the dimensions and store it
if (!this.dimensions.used) {
this.dimensions.used = new Dimension(dimensions.container.width, this.getDimensions().height);
}
return this.dimensions.used;
}
private doLayout(dimension: Dimension): void {
private doLayout(dimensions: ITitleControlDimensions): void {
// Only layout if we have valid tab index and dimensions
const activeTabAndIndex = this.group.activeEditor ? this.getTabAndIndex(this.group.activeEditor) : undefined;
if (!activeTabAndIndex) {
return; // nothing to do if not editor opened
if (activeTabAndIndex && dimensions.container !== Dimension.None && dimensions.available !== Dimension.None) {
// Breadcrumbs
this.doLayoutBreadcrumbs(dimensions);
// Tabs
const [activeTab, activeIndex] = activeTabAndIndex;
this.doLayoutTabs(activeTab, activeIndex, dimensions);
}
// Breadcrumbs
this.doLayoutBreadcrumbs(dimension);
// Remember the dimensions used in the control so that we can
// return it fast from the `layout` call without having to
// compute it over and over again
const oldDimension = this.dimensions.used;
const newDimension = this.dimensions.used = new Dimension(dimensions.container.width, this.getDimensions().height);
// Tabs
const [activeTab, activeIndex] = activeTabAndIndex;
this.doLayoutTabs(activeTab, activeIndex);
// In case the height of the title control changed from before
// (currently only possible if wrapping changed on/off), we need
// to signal this to the outside via a `relayout` call so that
// e.g. the editor control can be adjusted accordingly.
if (oldDimension && oldDimension.height !== newDimension.height) {
this.group.relayout();
}
}
private doLayoutBreadcrumbs(dimension: Dimension): void {
private doLayoutBreadcrumbs(dimensions: ITitleControlDimensions): void {
if (this.breadcrumbsControl && !this.breadcrumbsControl.isHidden()) {
const tabsScrollbar = assertIsDefined(this.tabsScrollbar);
this.breadcrumbsControl.layout(new Dimension(dimension.width, BreadcrumbsControl.HEIGHT));
tabsScrollbar.getDomNode().style.height = `${dimension.height - BreadcrumbsControl.HEIGHT}px`;
this.breadcrumbsControl.layout(new Dimension(dimensions.container.width, BreadcrumbsControl.HEIGHT));
}
}
private doLayoutTabs(activeTab: HTMLElement, activeIndex: number): void {
private doLayoutTabs(activeTab: HTMLElement, activeIndex: number, dimensions: ITitleControlDimensions): void {
// Always first layout tabs with wrapping support even if wrapping
// is disabled. The result indicates if tabs wrap and if not, we
// need to proceed with the layout without wrapping because even
// if wrapping is enabled in settings, there are cases where
// wrapping is disabled (e.g. due to space constraints)
const tabsWrapMultiLine = this.doLayoutTabsWrapping(dimensions);
if (!tabsWrapMultiLine) {
this.doLayoutTabsNonWrapping(activeTab, activeIndex);
}
}
private doLayoutTabsWrapping(dimensions: ITitleControlDimensions): boolean {
const [tabsAndActionsContainer, tabsContainer, editorToolbarContainer, tabsScrollbar] = assertAllDefined(this.tabsAndActionsContainer, this.tabsContainer, this.editorToolbarContainer, this.tabsScrollbar);
// Handle wrapping tabs according to setting:
// - enabled: only add class if tabs wrap and don't exceed available dimensions
// - disabled: remove class and margin-right variable
const didTabsWrapMultiLine = tabsAndActionsContainer.classList.contains('wrapping');
let tabsWrapMultiLine = didTabsWrapMultiLine;
function updateTabsWrapping(enabled: boolean): void {
tabsWrapMultiLine = enabled;
// Toggle the `wrapped` class to enable wrapping
tabsAndActionsContainer.classList.toggle('wrapping', tabsWrapMultiLine);
// Update `last-tab-margin-right` CSS variable to account for the absolute
// positioned editor actions container when tabs wrap. The margin needs to
// be the width of the editor actions container to avoid screen cheese.
tabsContainer.style.setProperty('--last-tab-margin-right', tabsWrapMultiLine ? `${editorToolbarContainer.offsetWidth}px` : '0');
}
// Setting enabled: selectively enable wrapping if possible
if (this.accessor.partOptions.wrapTabs) {
const visibleTabsWidth = tabsContainer.offsetWidth;
const allTabsWidth = tabsContainer.scrollWidth;
const lastTabFitsWrapped = () => {
const lastTab = this.getLastTab();
if (!lastTab) {
return true; // no tab always fits
}
return lastTab.offsetWidth <= (dimensions.available.width - editorToolbarContainer.offsetWidth);
};
// If tabs wrap or should start to wrap (when width exceeds visible width)
// we must trigger `updateWrapping` to set the `last-tab-margin-right`
// accordingly based on the number of actions. The margin is important to
// properly position the last tab apart from the actions
//
// We already check here if the last tab would fit when wrapped given the
// editor toolbar will also show right next to it. This ensures we are not
// enabling wrapping only to disable it again in the code below (this fixes
// flickering issue https://github.com/microsoft/vscode/issues/115050)
if (tabsWrapMultiLine || (allTabsWidth > visibleTabsWidth && lastTabFitsWrapped())) {
updateTabsWrapping(true);
}
// Tabs wrap multiline: remove wrapping under certain size constraint conditions
if (tabsWrapMultiLine) {
if (
(tabsContainer.offsetHeight > dimensions.available.height) || // if height exceeds available height
(allTabsWidth === visibleTabsWidth && tabsContainer.offsetHeight === TabsTitleControl.TAB_HEIGHT) || // if wrapping is not needed anymore
(!lastTabFitsWrapped()) // if last tab does not fit anymore
) {
updateTabsWrapping(false);
}
}
}
// Setting disabled: remove CSS traces only if tabs did wrap
else if (didTabsWrapMultiLine) {
updateTabsWrapping(false);
}
// If we transitioned from non-wrapping to wrapping, we need
// to update the scrollbar to have an equal `width` and
// `scrollWidth`. Otherwise a scrollbar would appear which is
// never desired when wrapping.
if (tabsWrapMultiLine && !didTabsWrapMultiLine) {
const visibleTabsWidth = tabsContainer.offsetWidth;
tabsScrollbar.setScrollDimensions({
width: visibleTabsWidth,
scrollWidth: visibleTabsWidth
});
}
return tabsWrapMultiLine;
}
private doLayoutTabsNonWrapping(activeTab: HTMLElement, activeIndex: number): void {
const [tabsContainer, tabsScrollbar] = assertAllDefined(this.tabsContainer, this.tabsScrollbar);
//
@@ -1308,7 +1475,7 @@ export class TabsTitleControl extends TitleControl {
// [-- Sticky Tabs Width --]
//
const visibleTabsContainerWidth = tabsContainer.offsetWidth;
const visibleTabsWidth = tabsContainer.offsetWidth;
const allTabsWidth = tabsContainer.scrollWidth;
// Compute width of sticky tabs depending on pinned tab sizing
@@ -1331,17 +1498,17 @@ export class TabsTitleControl extends TitleControl {
}
// Figure out if active tab is positioned static which has an
// impact on wether to reveal the tab or not later
// impact on whether to reveal the tab or not later
let activeTabPositionStatic = this.accessor.partOptions.pinnedTabSizing !== 'normal' && this.group.isSticky(activeIndex);
// Special case: we have sticky tabs but the available space for showing tabs
// is little enough that we need to disable sticky tabs sticky positioning
// so that tabs can be scrolled at naturally.
let availableTabsContainerWidth = visibleTabsContainerWidth - stickyTabsWidth;
let availableTabsContainerWidth = visibleTabsWidth - stickyTabsWidth;
if (this.group.stickyCount > 0 && availableTabsContainerWidth < TabsTitleControl.TAB_WIDTH.fit) {
tabsContainer.classList.add('disable-sticky-tabs');
availableTabsContainerWidth = visibleTabsContainerWidth;
availableTabsContainerWidth = visibleTabsWidth;
stickyTabsWidth = 0;
activeTabPositionStatic = false;
} else {
@@ -1358,7 +1525,7 @@ export class TabsTitleControl extends TitleControl {
// Update scrollbar
tabsScrollbar.setScrollDimensions({
width: visibleTabsContainerWidth,
width: visibleTabsWidth,
scrollWidth: allTabsWidth
});
@@ -1426,15 +1593,28 @@ export class TabsTitleControl extends TitleControl {
private getTabAndIndex(editor: IEditorInput): [HTMLElement, number /* index */] | undefined {
const editorIndex = this.group.getIndexOfEditor(editor);
if (editorIndex >= 0) {
const tabsContainer = assertIsDefined(this.tabsContainer);
return [tabsContainer.children[editorIndex] as HTMLElement, editorIndex];
const tab = this.getTabAtIndex(editorIndex);
if (tab) {
return [tab, editorIndex];
}
return undefined;
}
private getTabAtIndex(editorIndex: number): HTMLElement | undefined {
if (editorIndex >= 0) {
const tabsContainer = assertIsDefined(this.tabsContainer);
return tabsContainer.children[editorIndex] as HTMLElement | undefined;
}
return undefined;
}
private getLastTab(): HTMLElement | undefined {
return this.getTabAtIndex(this.group.count - 1);
}
private blockRevealActiveTabOnce(): void {
// When closing tabs through the tab close button or gesture, the user
@@ -1527,16 +1707,28 @@ export class TabsTitleControl extends TitleControl {
}
}
registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => {
registerThemingParticipant((theme, collector) => {
// Add border between tabs and breadcrumbs in high contrast mode.
if (theme.type === ColorScheme.HIGH_CONTRAST) {
const borderColor = (theme.getColor(TAB_BORDER) || theme.getColor(contrastBorder));
if (borderColor) {
collector.addRule(`
.monaco-workbench .part.editor > .content .editor-group-container > .title > .tabs-and-actions-container {
border-bottom: 1px solid ${borderColor};
}
`);
}
}
// Add bottom border to tabs when wrapping
const borderColor = theme.getColor(TAB_BORDER);
if (borderColor) {
collector.addRule(`
.monaco-workbench .part.editor > .content .editor-group-container > .title.tabs > .tabs-and-actions-container {
border-bottom: 1px solid ${borderColor};
}
`);
.monaco-workbench .part.editor > .content .editor-group-container > .title > .tabs-and-actions-container.wrapping .tabs-container > .tab {
border-bottom: 1px solid ${borderColor};
}
`);
}
// Styling with Outline color (e.g. high contrast theme)

View File

@@ -5,7 +5,7 @@
import * as nls from 'vs/nls';
import * as objects from 'vs/base/common/objects';
import { isFunction, isObject, isArray, assertIsDefined } from 'vs/base/common/types';
import { isFunction, isObject, isArray, assertIsDefined, withUndefinedAsNull } from 'vs/base/common/types';
import { IDiffEditor } from 'vs/editor/browser/editorBrowser';
import { IDiffEditorOptions, IEditorOptions as ICodeEditorOptions } from 'vs/editor/common/config/editorOptions';
import { BaseTextEditor, IEditorConfiguration } from 'vs/workbench/browser/parts/editor/textEditor';
@@ -103,7 +103,7 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditorPan
}
createEditorControl(parent: HTMLElement, configuration: ICodeEditorOptions): IDiffEditor {
return this.instantiationService.createInstance(DiffEditorWidget, parent, configuration);
return this.instantiationService.createInstance(DiffEditorWidget, parent, configuration, {});
}
async setInput(input: EditorInput, options: EditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise<void> {
@@ -132,8 +132,8 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditorPan
// Set Editor Model
const diffEditor = assertIsDefined(this.getControl());
const resolvedDiffEditorModel = <TextDiffEditorModel>resolvedModel;
diffEditor.setModel(resolvedDiffEditorModel.textDiffEditorModel);
const resolvedDiffEditorModel = resolvedModel as TextDiffEditorModel;
diffEditor.setModel(withUndefinedAsNull(resolvedDiffEditorModel.textDiffEditorModel));
// Apply Options from TextOptions
let optionsGotApplied = false;

View File

@@ -233,7 +233,6 @@ export abstract class BaseTextEditor extends EditorPane implements ITextEditorPa
return undefined;
}
protected saveTextEditorViewState(resource: URI, cleanUpOnDispose?: IEditorInput): void {
const editorViewState = this.retrieveTextEditorViewState(resource);
if (!editorViewState || !this.group) {

View File

@@ -9,14 +9,13 @@ import { addDisposableListener, Dimension, EventType } from 'vs/base/browser/dom
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
import { ActionsOrientation, prepareActions } from 'vs/base/browser/ui/actionbar/actionbar';
import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar';
import { IAction, IRunEvent, WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification, IActionViewItem } from 'vs/base/common/actions';
import * as arrays from 'vs/base/common/arrays';
import { IAction, WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification, IActionViewItem } from 'vs/base/common/actions';
import { ResolvedKeybinding } from 'vs/base/common/keyCodes';
import { dispose, DisposableStore } from 'vs/base/common/lifecycle';
import { isCodeEditor } from 'vs/editor/browser/editorBrowser';
import { localize } from 'vs/nls';
import { createAndFillInActionBarActions, createAndFillInContextMenuActions, MenuEntryActionViewItem, SubmenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem';
import { ExecuteCommandAction, IMenu, IMenuService, MenuId, MenuItemAction, SubmenuItemAction } from 'vs/platform/actions/common/actions';
import { createActionViewItem, createAndFillInActionBarActions, createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem';
import { IMenu, IMenuService, MenuId } from 'vs/platform/actions/common/actions';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
@@ -26,7 +25,7 @@ import { INotificationService } from 'vs/platform/notification/common/notificati
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { listActiveSelectionBackground, listActiveSelectionForeground } from 'vs/platform/theme/common/colorRegistry';
import { ICssStyleCollector, IColorTheme, IThemeService, registerThemingParticipant, Themable } from 'vs/platform/theme/common/themeService';
import { IThemeService, registerThemingParticipant, Themable } from 'vs/platform/theme/common/themeService';
import { DraggedEditorGroupIdentifier, DraggedEditorIdentifier, fillResourceDataTransfers, LocalSelectionTransfer } from 'vs/workbench/browser/dnd';
import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane';
import { BreadcrumbsConfig } from 'vs/workbench/browser/parts/editor/breadcrumbs';
@@ -34,7 +33,6 @@ import { BreadcrumbsControl, IBreadcrumbsControlOptions } from 'vs/workbench/bro
import { IEditorGroupsAccessor, IEditorGroupTitleDimensions, IEditorGroupView } from 'vs/workbench/browser/parts/editor/editor';
import { EditorCommandsContextActionRunner, IEditorCommandsContext, IEditorInput, EditorResourceAccessor, IEditorPartOptions, SideBySideEditor, ActiveEditorPinnedContext, ActiveEditorStickyContext } from 'vs/workbench/common/editor';
import { ResourceContextKey } from 'vs/workbench/common/resources';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview';
import { IFileService } from 'vs/platform/files/common/files';
import { withNullAsUndefined, withUndefinedAsNull, assertIsDefined } from 'vs/base/common/types';
@@ -46,6 +44,20 @@ export interface IToolbarActions {
secondary: IAction[];
}
export interface ITitleControlDimensions {
/**
* The size of the parent container the title control is layed out in.
*/
container: Dimension;
/**
* The maximum size the title control is allowed to consume based on
* other controls that are positioned inside the container.
*/
available: Dimension;
}
export abstract class TitleControl extends Themable {
protected readonly groupTransfer = LocalSelectionTransfer.getInstance<DraggedEditorGroupIdentifier>();
@@ -53,9 +65,6 @@ export abstract class TitleControl extends Themable {
protected breadcrumbsControl: BreadcrumbsControl | undefined = undefined;
private currentPrimaryEditorActionIds: string[] = [];
private currentSecondaryEditorActionIds: string[] = [];
private editorActionsToolbar: ToolBar | undefined;
private resourceContext: ResourceContextKey;
@@ -79,7 +88,6 @@ export abstract class TitleControl extends Themable {
@IMenuService private readonly menuService: IMenuService,
@IQuickInputService protected quickInputService: IQuickInputService,
@IThemeService themeService: IThemeService,
@IExtensionService private readonly extensionService: IExtensionService,
@IConfigurationService protected configurationService: IConfigurationService,
@IFileService private readonly fileService: IFileService
) {
@@ -92,13 +100,6 @@ export abstract class TitleControl extends Themable {
this.contextMenu = this._register(this.menuService.createMenu(MenuId.EditorTitleContext, this.contextKeyService));
this.create(parent);
this.registerListeners();
}
protected registerListeners(): void {
// Update actions toolbar when extension register that may contribute them
this._register(this.extensionService.onDidRegisterExtensions(() => this.updateEditorActionsToolbar()));
}
protected abstract create(parent: HTMLElement): void;
@@ -147,7 +148,7 @@ export abstract class TitleControl extends Themable {
this.editorActionsToolbar.context = context;
// Action Run Handling
this._register(this.editorActionsToolbar.actionRunner.onDidRun((e: IRunEvent) => {
this._register(this.editorActionsToolbar.actionRunner.onDidRun(e => {
// Notify for Error
this.notificationService.error(e.error);
@@ -172,35 +173,14 @@ export abstract class TitleControl extends Themable {
}
// Check extensions
if (action instanceof MenuItemAction) {
return this.instantiationService.createInstance(MenuEntryActionViewItem, action);
} else if (action instanceof SubmenuItemAction) {
return this.instantiationService.createInstance(SubmenuEntryActionViewItem, action);
}
return undefined;
return createActionViewItem(this.instantiationService, action);
}
protected updateEditorActionsToolbar(): void {
// Update Editor Actions Toolbar
const { primaryEditorActions, secondaryEditorActions } = this.prepareEditorActions(this.getEditorActions());
// Only update if something actually has changed
const primaryEditorActionIds = primaryEditorActions.map(a => a.id);
const secondaryEditorActionIds = secondaryEditorActions.map(a => a.id);
if (
!arrays.equals(primaryEditorActionIds, this.currentPrimaryEditorActionIds) ||
!arrays.equals(secondaryEditorActionIds, this.currentSecondaryEditorActionIds) ||
primaryEditorActions.some(action => action instanceof ExecuteCommandAction) || // execute command actions can have the same ID but different arguments
secondaryEditorActions.some(action => action instanceof ExecuteCommandAction) // see also https://github.com/microsoft/vscode/issues/16298
) {
const editorActionsToolbar = assertIsDefined(this.editorActionsToolbar);
editorActionsToolbar.setActions(primaryEditorActions, secondaryEditorActions);
this.currentPrimaryEditorActionIds = primaryEditorActionIds;
this.currentSecondaryEditorActionIds = secondaryEditorActionIds;
}
const editorActionsToolbar = assertIsDefined(this.editorActionsToolbar);
editorActionsToolbar.setActions(primaryEditorActions, secondaryEditorActions);
}
protected prepareEditorActions(editorActions: IToolbarActions): { primaryEditorActions: IAction[]; secondaryEditorActions: IAction[]; } {
@@ -251,18 +231,13 @@ export abstract class TitleControl extends Themable {
}
protected clearEditorActionsToolbar(): void {
if (this.editorActionsToolbar) {
this.editorActionsToolbar.setActions([], []);
}
this.currentPrimaryEditorActionIds = [];
this.currentSecondaryEditorActionIds = [];
this.editorActionsToolbar?.setActions([], []);
}
protected enableGroupDragging(element: HTMLElement): void {
// Drag start
this._register(addDisposableListener(element, EventType.DRAG_START, (e: DragEvent) => {
this._register(addDisposableListener(element, EventType.DRAG_START, e => {
if (e.target !== element) {
return; // only if originating from tabs container
}
@@ -354,7 +329,7 @@ export abstract class TitleControl extends Themable {
getAnchor: () => anchor,
getActions: () => actions,
getActionsContext: () => ({ groupId: this.group.id, editorIndex: this.group.getIndexOfEditor(editor) }),
getKeyBinding: (action) => this.getKeybinding(action),
getKeyBinding: action => this.getKeybinding(action),
onHide: () => {
// restore previous contexts
@@ -407,7 +382,7 @@ export abstract class TitleControl extends Themable {
abstract updateStyles(): void;
abstract layout(dimension: Dimension): void;
abstract layout(dimensions: ITitleControlDimensions): Dimension;
abstract getDimensions(): IEditorGroupTitleDimensions;
@@ -419,7 +394,7 @@ export abstract class TitleControl extends Themable {
}
}
registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => {
registerThemingParticipant((theme, collector) => {
// Drag Feedback
const dragImageBackground = theme.getColor(listActiveSelectionBackground);

View File

@@ -6,7 +6,7 @@
import 'vs/css!./media/notificationsCenter';
import 'vs/css!./media/notificationsActions';
import { NOTIFICATIONS_BORDER, NOTIFICATIONS_CENTER_HEADER_FOREGROUND, NOTIFICATIONS_CENTER_HEADER_BACKGROUND, NOTIFICATIONS_CENTER_BORDER } from 'vs/workbench/common/theme';
import { IThemeService, registerThemingParticipant, IColorTheme, ICssStyleCollector, Themable } from 'vs/platform/theme/common/themeService';
import { IThemeService, registerThemingParticipant, Themable } from 'vs/platform/theme/common/themeService';
import { INotificationsModel, INotificationChangeEvent, NotificationChangeType, NotificationViewItemContentChangeKind } from 'vs/workbench/common/notifications';
import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService';
import { Emitter } from 'vs/base/common/event';
@@ -313,7 +313,7 @@ export class NotificationsCenter extends Themable implements INotificationsCente
}
}
registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => {
registerThemingParticipant((theme, collector) => {
const notificationBorderColor = theme.getColor(NOTIFICATIONS_BORDER);
if (notificationBorderColor) {
collector.addRule(`.monaco-workbench > .notifications-center .notifications-list-container .monaco-list-row[data-last-element="false"] > .notification-list-item { border-bottom: 1px solid ${notificationBorderColor}; }`);

View File

@@ -10,7 +10,7 @@ import { WorkbenchList } from 'vs/platform/list/browser/listService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IListOptions } from 'vs/base/browser/ui/list/listWidget';
import { NOTIFICATIONS_LINKS, NOTIFICATIONS_BACKGROUND, NOTIFICATIONS_FOREGROUND, NOTIFICATIONS_ERROR_ICON_FOREGROUND, NOTIFICATIONS_WARNING_ICON_FOREGROUND, NOTIFICATIONS_INFO_ICON_FOREGROUND } from 'vs/workbench/common/theme';
import { IThemeService, registerThemingParticipant, IColorTheme, ICssStyleCollector, Themable } from 'vs/platform/theme/common/themeService';
import { IThemeService, registerThemingParticipant, Themable } from 'vs/platform/theme/common/themeService';
import { contrastBorder, focusBorder } from 'vs/platform/theme/common/colorRegistry';
import { INotificationViewItem } from 'vs/workbench/common/notifications';
import { NotificationsListDelegate, NotificationRenderer } from 'vs/workbench/browser/parts/notifications/notificationsViewer';
@@ -278,7 +278,7 @@ export class NotificationsList extends Themable {
}
}
registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => {
registerThemingParticipant((theme, collector) => {
const linkColor = theme.getColor(NOTIFICATIONS_LINKS);
if (linkColor) {
collector.addRule(`.monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-message a { color: ${linkColor}; }`);

View File

@@ -470,7 +470,9 @@ export class NotificationTemplateRenderer extends Disposable {
: buttonToolbar.addButton(buttonOptions));
button.label = action.label;
this.inputDisposables.add(button.onDidClick(e => {
EventHelper.stop(e, true);
if (e) {
EventHelper.stop(e, true);
}
actionRunner.run(action);
}));

View File

@@ -5,45 +5,26 @@
import 'vs/css!./media/panelpart';
import * as nls from 'vs/nls';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { KeyMod, KeyCode } from 'vs/base/common/keyCodes';
import { Action } from 'vs/base/common/actions';
import { Registry } from 'vs/platform/registry/common/platform';
import { SyncActionDescriptor, MenuId, MenuRegistry } from 'vs/platform/actions/common/actions';
import { SyncActionDescriptor, MenuId, MenuRegistry, registerAction2, Action2 } from 'vs/platform/actions/common/actions';
import { IWorkbenchActionRegistry, Extensions as WorkbenchExtensions, CATEGORIES } from 'vs/workbench/common/actions';
import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
import { IWorkbenchLayoutService, Parts, Position, positionToString } from 'vs/workbench/services/layout/browser/layoutService';
import { ActivityAction, ToggleCompositePinnedAction, ICompositeBar } from 'vs/workbench/browser/parts/compositeBarActions';
import { IActivity } from 'vs/workbench/common/activity';
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { ActivePanelContext, PanelPositionContext } from 'vs/workbench/common/panel';
import { ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey';
import { ActivePanelContext, PanelMaximizedContext, PanelPositionContext, PanelVisibleContext } from 'vs/workbench/common/panel';
import { ContextKeyExpr, ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey';
import { Codicon } from 'vs/base/common/codicons';
import { registerIcon } from 'vs/platform/theme/common/iconRegistry';
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
import { ServicesAccessor } from 'vs/editor/browser/editorExtensions';
import { ViewContainerLocationToString, ViewContainerLocation } from 'vs/workbench/common/views';
const maximizeIcon = registerIcon('panel-maximize', Codicon.chevronUp, nls.localize('maximizeIcon', 'Icon to maximize a panel.'));
const restoreIcon = registerIcon('panel-restore', Codicon.chevronDown, nls.localize('restoreIcon', 'Icon to restore a panel.'));
const closeIcon = registerIcon('panel-close', Codicon.close, nls.localize('closeIcon', 'Icon to close a panel.'));
export class ClosePanelAction extends Action {
static readonly ID = 'workbench.action.closePanel';
static readonly LABEL = nls.localize('closePanel', "Close Panel");
constructor(
id: string,
name: string,
@IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService
) {
super(id, name, ThemeIcon.asClassName(closeIcon));
}
async run(): Promise<void> {
this.layoutService.setPanelHidden(true);
}
}
export class TogglePanelAction extends Action {
static readonly ID = 'workbench.action.togglePanel';
@@ -91,46 +72,6 @@ class FocusPanelAction extends Action {
}
}
export class ToggleMaximizedPanelAction extends Action {
static readonly ID = 'workbench.action.toggleMaximizedPanel';
static readonly LABEL = nls.localize('toggleMaximizedPanel', "Toggle Maximized Panel");
private static readonly MAXIMIZE_LABEL = nls.localize('maximizePanel', "Maximize Panel Size");
private static readonly RESTORE_LABEL = nls.localize('minimizePanel', "Restore Panel Size");
private readonly toDispose = this._register(new DisposableStore());
constructor(
id: string,
label: string,
@IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService,
@IEditorGroupsService editorGroupsService: IEditorGroupsService
) {
super(id, label, layoutService.isPanelMaximized() ? ThemeIcon.asClassName(restoreIcon) : ThemeIcon.asClassName(maximizeIcon));
this.toDispose.add(editorGroupsService.onDidLayout(() => {
const maximized = this.layoutService.isPanelMaximized();
this.class = maximized ? ThemeIcon.asClassName(restoreIcon) : ThemeIcon.asClassName(maximizeIcon);
this.label = maximized ? ToggleMaximizedPanelAction.RESTORE_LABEL : ToggleMaximizedPanelAction.MAXIMIZE_LABEL;
}));
}
async run(): Promise<void> {
if (!this.layoutService.isVisible(Parts.PANEL_PART)) {
this.layoutService.setPanelHidden(false);
// If the panel is not already maximized, maximize it
if (!this.layoutService.isPanelMaximized()) {
this.layoutService.toggleMaximizedPanel();
}
}
else {
this.layoutService.toggleMaximizedPanel();
}
}
}
const PositionPanelActionId = {
LEFT: 'workbench.action.positionPanelLeft',
RIGHT: 'workbench.action.positionPanelRight',
@@ -218,7 +159,6 @@ export class PlaceHolderToggleCompositePinnedAction extends ToggleCompositePinne
}
}
export class SwitchPanelViewAction extends Action {
constructor(
@@ -287,35 +227,117 @@ export class NextPanelViewAction extends SwitchPanelViewAction {
const actionRegistry = Registry.as<IWorkbenchActionRegistry>(WorkbenchExtensions.WorkbenchActions);
actionRegistry.registerWorkbenchAction(SyncActionDescriptor.from(TogglePanelAction, { primary: KeyMod.CtrlCmd | KeyCode.KEY_J }), 'View: Toggle Panel', CATEGORIES.View.value);
actionRegistry.registerWorkbenchAction(SyncActionDescriptor.from(FocusPanelAction), 'View: Focus into Panel', CATEGORIES.View.value);
actionRegistry.registerWorkbenchAction(SyncActionDescriptor.from(ToggleMaximizedPanelAction), 'View: Toggle Maximized Panel', CATEGORIES.View.value);
actionRegistry.registerWorkbenchAction(SyncActionDescriptor.from(ClosePanelAction), 'View: Close Panel', CATEGORIES.View.value);
actionRegistry.registerWorkbenchAction(SyncActionDescriptor.from(PreviousPanelViewAction), 'View: Previous Panel View', CATEGORIES.View.value);
actionRegistry.registerWorkbenchAction(SyncActionDescriptor.from(NextPanelViewAction), 'View: Next Panel View', CATEGORIES.View.value);
MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, {
group: '2_workbench_layout',
command: {
id: TogglePanelAction.ID,
title: nls.localize({ key: 'miShowPanel', comment: ['&& denotes a mnemonic'] }, "Show &&Panel"),
toggled: ActivePanelContext
},
order: 5
registerAction2(class extends Action2 {
constructor() {
super({
id: 'workbench.action.toggleMaximizedPanel',
title: { value: nls.localize('toggleMaximizedPanel', "Toggle Maximized Panel"), original: 'Toggle Maximized Panel' },
tooltip: nls.localize('maximizePanel', "Maximize Panel Size"),
category: CATEGORIES.View,
f1: true,
icon: maximizeIcon,
toggled: { condition: PanelMaximizedContext, icon: restoreIcon, tooltip: nls.localize('minimizePanel', "Restore Panel Size") },
menu: [{
id: MenuId.PanelTitle,
group: 'navigation',
order: 1
}]
});
}
run(accessor: ServicesAccessor) {
const layoutService = accessor.get(IWorkbenchLayoutService);
if (!layoutService.isVisible(Parts.PANEL_PART)) {
layoutService.setPanelHidden(false);
// If the panel is not already maximized, maximize it
if (!layoutService.isPanelMaximized()) {
layoutService.toggleMaximizedPanel();
}
}
else {
layoutService.toggleMaximizedPanel();
}
}
});
registerAction2(class extends Action2 {
constructor() {
super({
id: 'workbench.action.closePanel',
title: { value: nls.localize('closePanel', "Close Panel"), original: 'Close Panel' },
category: CATEGORIES.View,
icon: closeIcon,
menu: [{
id: MenuId.CommandPalette,
when: PanelVisibleContext,
}, {
id: MenuId.PanelTitle,
group: 'navigation',
order: 2
}]
});
}
run(accessor: ServicesAccessor) {
accessor.get(IWorkbenchLayoutService).setPanelHidden(true);
}
});
MenuRegistry.appendMenuItems([
{
id: MenuId.MenubarAppearanceMenu,
item: {
group: '2_workbench_layout',
command: {
id: TogglePanelAction.ID,
title: nls.localize({ key: 'miShowPanel', comment: ['&& denotes a mnemonic'] }, "Show &&Panel"),
toggled: ActivePanelContext
},
order: 5
}
}, {
id: MenuId.ViewTitleContext,
item: {
group: '3_workbench_layout_move',
command: {
id: TogglePanelAction.ID,
title: { value: nls.localize('hidePanel', "Hide Panel"), original: 'Hide Panel' },
},
when: ContextKeyExpr.and(PanelVisibleContext, ContextKeyExpr.equals('viewLocation', ViewContainerLocationToString(ViewContainerLocation.Panel))),
order: 2
}
}
]);
function registerPositionPanelActionById(config: PanelActionConfig<Position>) {
const { id, label, alias, when } = config;
// register the workbench action
actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(SetPanelPositionAction, id, label), alias, CATEGORIES.View.value, when);
// register as a menu item
MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, {
group: '3_workbench_layout_move',
command: {
id,
title: label
},
when,
order: 5
});
MenuRegistry.appendMenuItems([{
id: MenuId.MenubarAppearanceMenu,
item: {
group: '3_workbench_layout_move',
command: {
id,
title: label
},
when,
order: 5
}
}, {
id: MenuId.ViewTitleContext,
item: {
group: '3_workbench_layout_move',
command: {
id: id,
title: label,
},
when: ContextKeyExpr.and(when, ContextKeyExpr.equals('viewLocation', ViewContainerLocationToString(ViewContainerLocation.Panel))),
order: 1
}
}]);
}
// register each position panel action

View File

@@ -4,7 +4,8 @@
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/panelpart';
import { IAction, Action } from 'vs/base/common/actions';
import { localize } from 'vs/nls';
import { IAction, Separator, toAction } from 'vs/base/common/actions';
import { Event } from 'vs/base/common/event';
import { Registry } from 'vs/platform/registry/common/platform';
import { ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar';
@@ -18,8 +19,8 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ClosePanelAction, PanelActivityAction, ToggleMaximizedPanelAction, TogglePanelAction, PlaceHolderPanelActivityAction, PlaceHolderToggleCompositePinnedAction, PositionPanelActionConfigs, SetPanelPositionAction } from 'vs/workbench/browser/parts/panel/panelActions';
import { IThemeService, registerThemingParticipant, IColorTheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService';
import { PanelActivityAction, TogglePanelAction, PlaceHolderPanelActivityAction, PlaceHolderToggleCompositePinnedAction, PositionPanelActionConfigs, SetPanelPositionAction } from 'vs/workbench/browser/parts/panel/panelActions';
import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
import { PANEL_BACKGROUND, PANEL_BORDER, PANEL_ACTIVE_TITLE_FOREGROUND, PANEL_INACTIVE_TITLE_FOREGROUND, PANEL_ACTIVE_TITLE_BORDER, PANEL_INPUT_BORDER, EDITOR_DRAG_AND_DROP_BACKGROUND, PANEL_DRAG_AND_DROP_BORDER } from 'vs/workbench/common/theme';
import { activeContrastBorder, focusBorder, contrastBorder, editorBackground, badgeBackground, badgeForeground } from 'vs/platform/theme/common/colorRegistry';
import { CompositeBar, ICompositeBarItem, CompositeDragAndDrop } from 'vs/workbench/browser/parts/compositeBar';
@@ -27,15 +28,12 @@ import { ToggleCompositePinnedAction } from 'vs/workbench/browser/parts/composit
import { IBadge } from 'vs/workbench/services/activity/common/activity';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { Dimension, trackFocus, EventHelper } from 'vs/base/browser/dom';
import { localize } from 'vs/nls';
import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle';
import { IContextKey, IContextKeyService, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { isUndefinedOrNull, assertIsDefined } from 'vs/base/common/types';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { ViewContainer, IViewDescriptorService, IViewContainerModel, ViewContainerLocation } from 'vs/workbench/common/views';
import { MenuId } from 'vs/platform/actions/common/actions';
import { ViewMenuActions, ViewContainerMenuActions } from 'vs/workbench/browser/parts/views/viewMenuActions';
import { IPaneComposite } from 'vs/workbench/common/panecomposite';
import { Before2D, CompositeDragAndDropObserver, ICompositeDragAndDrop, toggleDropEffect } from 'vs/workbench/browser/dnd';
import { IActivity } from 'vs/workbench/common/activity';
@@ -46,7 +44,7 @@ interface ICachedPanel {
pinned: boolean;
order?: number;
visible: boolean;
views?: { when?: string }[];
views?: { when?: string; }[];
}
interface IPlaceholderViewContainer {
@@ -148,24 +146,27 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
this.compositeBar = this._register(this.instantiationService.createInstance(CompositeBar, this.getCachedPanels(), {
icon: false,
orientation: ActionsOrientation.HORIZONTAL,
openComposite: (compositeId: string) => this.openPanel(compositeId, true).then(panel => panel || null),
getActivityAction: (compositeId: string) => this.getCompositeActions(compositeId).activityAction,
getCompositePinnedAction: (compositeId: string) => this.getCompositeActions(compositeId).pinnedAction,
getOnCompositeClickAction: (compositeId: string) => this.instantiationService.createInstance(PanelActivityAction, assertIsDefined(this.getPanel(compositeId))),
getContextMenuActions: () => [
...PositionPanelActionConfigs
// show the contextual menu item if it is not in that position
.filter(({ when }) => contextKeyService.contextMatchesRules(when))
.map(({ id, label }) => this.instantiationService.createInstance(SetPanelPositionAction, id, label)),
this.instantiationService.createInstance(TogglePanelAction, TogglePanelAction.ID, localize('hidePanel', "Hide Panel"))
] as Action[],
getContextMenuActionsForComposite: (compositeId: string) => this.getContextMenuActionsForComposite(compositeId) as Action[],
openComposite: compositeId => this.openPanel(compositeId, true).then(panel => panel || null),
getActivityAction: compositeId => this.getCompositeActions(compositeId).activityAction,
getCompositePinnedAction: compositeId => this.getCompositeActions(compositeId).pinnedAction,
getOnCompositeClickAction: compositeId => this.instantiationService.createInstance(PanelActivityAction, assertIsDefined(this.getPanel(compositeId))),
fillExtraContextMenuActions: actions => {
actions.push(...[
new Separator(),
...PositionPanelActionConfigs
// show the contextual menu item if it is not in that position
.filter(({ when }) => contextKeyService.contextMatchesRules(when))
.map(({ id, label }) => this.instantiationService.createInstance(SetPanelPositionAction, id, label)),
this.instantiationService.createInstance(TogglePanelAction, TogglePanelAction.ID, localize('hidePanel', "Hide Panel"))
]);
},
getContextMenuActionsForComposite: compositeId => this.getContextMenuActionsForComposite(compositeId),
getDefaultCompositeId: () => this.panelRegistry.getDefaultPanelId(),
hidePart: () => this.layoutService.setPanelHidden(true),
dndHandler: this.dndHandler,
compositeSize: 0,
overflowActionSize: 44,
colors: (theme: IColorTheme) => ({
colors: theme => ({
activeBackgroundColor: theme.getColor(PANEL_BACKGROUND), // Background color for overflow action
inactiveBackgroundColor: theme.getColor(PANEL_BACKGROUND), // Background color for overflow action
activeBorderBottomColor: theme.getColor(PANEL_ACTIVE_TITLE_BORDER),
@@ -184,20 +185,21 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
this.onDidRegisterPanels([...this.getPanels()]);
}
private getContextMenuActionsForComposite(compositeId: string): readonly IAction[] {
private getContextMenuActionsForComposite(compositeId: string): IAction[] {
const result: IAction[] = [];
const container = this.getViewContainer(compositeId);
if (container) {
const viewContainerModel = this.viewDescriptorService.getViewContainerModel(container);
const viewContainer = this.viewDescriptorService.getViewContainerById(compositeId)!;
const defaultLocation = this.viewDescriptorService.getDefaultViewContainerLocation(viewContainer)!;
if (defaultLocation !== this.viewDescriptorService.getViewContainerLocation(viewContainer)) {
result.push(toAction({ id: 'resetLocationAction', label: localize('resetLocation', "Reset Location"), run: () => this.viewDescriptorService.moveViewContainerToLocation(viewContainer, defaultLocation) }));
} else {
const viewContainerModel = this.viewDescriptorService.getViewContainerModel(viewContainer);
if (viewContainerModel.allViewDescriptors.length === 1) {
const viewMenuActions = this.instantiationService.createInstance(ViewMenuActions, viewContainerModel.allViewDescriptors[0].id, MenuId.ViewTitle, MenuId.ViewTitleContext);
result.push(...viewMenuActions.getContextMenuActions());
viewMenuActions.dispose();
const viewToReset = viewContainerModel.allViewDescriptors[0];
const defaultContainer = this.viewDescriptorService.getDefaultContainerById(viewToReset.id)!;
if (defaultContainer !== viewContainer) {
result.push(toAction({ id: 'resetLocationAction', label: localize('resetLocation', "Reset Location"), run: () => this.viewDescriptorService.moveViewsToContainer([viewToReset], defaultContainer) }));
}
}
const viewContainerMenuActions = this.instantiationService.createInstance(ViewContainerMenuActions, container.id, MenuId.ViewContainerTitleContext);
result.push(...viewContainerMenuActions.getContextMenuActions());
viewContainerMenuActions.dispose();
}
return result;
}
@@ -534,13 +536,6 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
.sort((p1, p2) => pinnedCompositeIds.indexOf(p1.id) - pinnedCompositeIds.indexOf(p2.id));
}
protected getActions(): ReadonlyArray<IAction> {
return [
this.instantiationService.createInstance(ToggleMaximizedPanelAction, ToggleMaximizedPanelAction.ID, ToggleMaximizedPanelAction.LABEL),
this.instantiationService.createInstance(ClosePanelAction, ClosePanelAction.ID, ClosePanelAction.LABEL)
];
}
getActivePanel(): IPanel | undefined {
return this.getActiveComposite();
}
@@ -803,7 +798,7 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
}
}
registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => {
registerThemingParticipant((theme, collector) => {
// Panel Background: since panels can host editors, we apply a background rule if the panel background
// color is different from the editor background color. This is a bit of a hack though. The better way

View File

@@ -3,11 +3,6 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/* Removed to allow progress bar positioning to escape */
/* .monaco-workbench .sidebar > .content {
overflow: hidden;
} */
.monaco-workbench.nosidebar > .part.sidebar {
display: none !important;
visibility: hidden !important;

View File

@@ -6,7 +6,6 @@
import 'vs/css!./media/sidebarpart';
import * as nls from 'vs/nls';
import { Registry } from 'vs/platform/registry/common/platform';
import { Action } from 'vs/base/common/actions';
import { CompositePart } from 'vs/workbench/browser/parts/compositePart';
import { Viewlet, ViewletRegistry, Extensions as ViewletExtensions, ViewletDescriptor } from 'vs/workbench/browser/viewlet';
import { Action2, registerAction2 } from 'vs/platform/actions/common/actions';
@@ -288,8 +287,8 @@ export class SidebarPart extends CompositePart<Viewlet> implements IViewletServi
const anchor: { x: number, y: number } = { x: event.posx, y: event.posy };
this.contextMenuService.showContextMenu({
getAnchor: () => anchor,
getActions: () => contextMenuActions,
getActionViewItem: action => this.actionViewItemProvider(action as Action),
getActions: () => contextMenuActions.slice(),
getActionViewItem: action => this.actionViewItemProvider(action),
actionRunner: activeViewlet.getActionRunner()
});
}

View File

@@ -7,7 +7,7 @@ import 'vs/css!./media/statusbarpart';
import * as nls from 'vs/nls';
import { toErrorMessage } from 'vs/base/common/errorMessage';
import { dispose, IDisposable, Disposable, toDisposable, MutableDisposable } from 'vs/base/common/lifecycle';
import { CodiconLabel } from 'vs/base/browser/ui/codicons/codiconLabel';
import { SimpleIconLabel } from 'vs/base/browser/ui/iconLabel/simpleIconLabel';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { Part } from 'vs/workbench/browser/part';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
@@ -15,13 +15,12 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { StatusbarAlignment, IStatusbarService, IStatusbarEntry, IStatusbarEntryAccessor } from 'vs/workbench/services/statusbar/common/statusbar';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { Action, IAction, WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification, Separator } from 'vs/base/common/actions';
import { IThemeService, registerThemingParticipant, IColorTheme, ICssStyleCollector, ThemeColor } from 'vs/platform/theme/common/themeService';
import { IThemeService, registerThemingParticipant, ThemeColor } from 'vs/platform/theme/common/themeService';
import { STATUS_BAR_BACKGROUND, STATUS_BAR_FOREGROUND, STATUS_BAR_NO_FOLDER_BACKGROUND, STATUS_BAR_ITEM_HOVER_BACKGROUND, STATUS_BAR_ITEM_ACTIVE_BACKGROUND, STATUS_BAR_PROMINENT_ITEM_FOREGROUND, STATUS_BAR_PROMINENT_ITEM_BACKGROUND, STATUS_BAR_PROMINENT_ITEM_HOVER_BACKGROUND, STATUS_BAR_BORDER, STATUS_BAR_NO_FOLDER_FOREGROUND, STATUS_BAR_NO_FOLDER_BORDER } from 'vs/workbench/common/theme';
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { contrastBorder, activeContrastBorder } from 'vs/platform/theme/common/colorRegistry';
import { isThemeColor } from 'vs/editor/common/editorCommon';
import { Color } from 'vs/base/common/color';
import { EventHelper, createStyleSheet, addDisposableListener, EventType, hide, show, isAncestor, appendChildren } from 'vs/base/browser/dom';
import { EventHelper, createStyleSheet, addDisposableListener, EventType, hide, show, isAncestor, append } from 'vs/base/browser/dom';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { IStorageService, StorageScope, IStorageValueChangeEvent, StorageTarget } from 'vs/platform/storage/common/storage';
import { Parts, IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
@@ -38,7 +37,8 @@ import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/co
import { ServicesAccessor } from 'vs/editor/browser/editorExtensions';
import { RawContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { ColorScheme } from 'vs/platform/theme/common/theme';
import { renderCodicon, renderCodicons } from 'vs/base/browser/codicons';
import { renderIcon, renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels';
import { syncing } from 'vs/platform/theme/common/iconRegistry';
interface IPendingStatusbarEntry {
id: string;
@@ -610,7 +610,7 @@ export class StatusbarPart extends Part implements IStatusbarService {
}
private getContextMenuActions(event: StandardMouseEvent): IAction[] {
const actions: Action[] = [];
const actions: IAction[] = [];
// Provide an action to hide the status bar at last
actions.push(this.instantiationService.createInstance(ToggleStatusbarVisibilityAction, ToggleStatusbarVisibilityAction.ID, nls.localize('hideStatusBar', "Hide Status Bar")));
@@ -704,9 +704,9 @@ export class StatusbarPart extends Part implements IStatusbarService {
}
}
class StatusBarCodiconLabel extends CodiconLabel {
class StatusBarCodiconLabel extends SimpleIconLabel {
private readonly progressCodicon = renderCodicon('sync', 'spin');
private readonly progressCodicon = renderIcon(syncing);
private currentText = '';
private currentShowProgress = false;
@@ -749,7 +749,7 @@ class StatusBarCodiconLabel extends CodiconLabel {
}
// Append new elements
appendChildren(this.container, ...renderCodicons(textContent));
append(this.container, ...renderLabelWithIcons(textContent));
}
// No Progress: no special handling
@@ -868,12 +868,8 @@ class StatusbarEntryItem extends Disposable {
// Update: Background
if (!this.entry || entry.backgroundColor !== this.entry.backgroundColor) {
if (entry.backgroundColor) {
this.applyColor(this.container, entry.backgroundColor, true);
this.container.classList.add('has-background-color');
} else {
this.container.classList.remove('has-background-color');
}
this.container.classList.toggle('has-background-color', !!entry.backgroundColor);
this.applyColor(this.container, entry.backgroundColor, true);
}
// Remember for next round
@@ -893,7 +889,7 @@ class StatusbarEntryItem extends Disposable {
}
private applyColor(container: HTMLElement, color: string | ThemeColor | undefined, isBackground?: boolean): void {
let colorResult: string | null = null;
let colorResult: string | undefined = undefined;
if (isBackground) {
this.backgroundListener.clear();
@@ -903,15 +899,15 @@ class StatusbarEntryItem extends Disposable {
if (color) {
if (isThemeColor(color)) {
colorResult = (this.themeService.getColorTheme().getColor(color.id) || Color.transparent).toString();
colorResult = this.themeService.getColorTheme().getColor(color.id)?.toString();
const listener = this.themeService.onDidColorThemeChange(theme => {
const colorValue = (theme.getColor(color.id) || Color.transparent).toString();
const colorValue = theme.getColor(color.id)?.toString();
if (isBackground) {
container.style.backgroundColor = colorValue;
container.style.backgroundColor = colorValue ?? '';
} else {
container.style.color = colorValue;
container.style.color = colorValue ?? '';
}
});
@@ -926,9 +922,9 @@ class StatusbarEntryItem extends Disposable {
}
if (isBackground) {
container.style.backgroundColor = colorResult || '';
container.style.backgroundColor = colorResult ?? '';
} else {
container.style.color = colorResult || '';
container.style.color = colorResult ?? '';
}
}
@@ -942,7 +938,7 @@ class StatusbarEntryItem extends Disposable {
}
}
registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => {
registerThemingParticipant((theme, collector) => {
if (theme.type !== ColorScheme.HIGH_CONTRAST) {
const statusBarItemHoverBackground = theme.getColor(STATUS_BAR_ITEM_HOVER_BACKGROUND);
if (statusBarItemHoverBackground) {

View File

@@ -85,11 +85,34 @@
height: 100%;
position: relative;
z-index: 3000;
flex-shrink: 0;
}
.monaco-workbench .part.titlebar > .window-appicon:not(.codicon) {
background-image: url('../../../media/code-icon.svg');
background-repeat: no-repeat;
background-position: center center;
background-size: 16px;
flex-shrink: 0;
}
.monaco-workbench .part.titlebar .window-appicon > .home-bar-icon-badge {
position: absolute;
right: 9px;
bottom: 6px;
width: 8px;
height: 8px;
z-index: 1; /* on top of home indicator */
background-image: url('../../../media/code-icon.svg');
background-repeat: no-repeat;
background-position: center center;
background-size: 8px;
pointer-events: none;
border-top: 1px solid transparent;
border-left: 1px solid transparent;
}
.monaco-workbench .part.titlebar > .window-appicon.codicon {
line-height: 30px;
}
.monaco-workbench.fullscreen .part.titlebar > .window-appicon {

View File

@@ -4,10 +4,10 @@
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
import { IMenuService, MenuId, IMenu, SubmenuItemAction, registerAction2, Action2 } from 'vs/platform/actions/common/actions';
import { registerThemingParticipant, IColorTheme, ICssStyleCollector, IThemeService } from 'vs/platform/theme/common/themeService';
import { IMenuService, MenuId, IMenu, SubmenuItemAction, registerAction2, Action2, MenuRegistry, MenuItemAction } from 'vs/platform/actions/common/actions';
import { registerThemingParticipant, IThemeService } from 'vs/platform/theme/common/themeService';
import { MenuBarVisibility, getTitleBarStyle, IWindowOpenable, getMenuBarVisibility } from 'vs/platform/windows/common/windows';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IAction, Action, SubmenuAction, Separator } from 'vs/base/common/actions';
import * as DOM from 'vs/base/browser/dom';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
@@ -37,6 +37,7 @@ import { BrowserFeatures } from 'vs/base/browser/canIUse';
import { KeyCode } from 'vs/base/common/keyCodes';
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { IsWebContext } from 'vs/platform/contextkey/common/contextkeys';
import { ICommandService } from 'vs/platform/commands/common/commands';
export abstract class MenubarControl extends Disposable {
@@ -91,7 +92,8 @@ export abstract class MenubarControl extends Disposable {
protected readonly preferencesService: IPreferencesService,
protected readonly environmentService: IWorkbenchEnvironmentService,
protected readonly accessibilityService: IAccessibilityService,
protected readonly hostService: IHostService
protected readonly hostService: IHostService,
protected readonly commandService: ICommandService
) {
super();
@@ -308,23 +310,10 @@ export class CustomMenubarControl extends MenubarControl {
@IAccessibilityService accessibilityService: IAccessibilityService,
@IThemeService private readonly themeService: IThemeService,
@IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService,
@IHostService protected readonly hostService: IHostService
@IHostService protected readonly hostService: IHostService,
@ICommandService commandService: ICommandService
) {
super(
menuService,
workspacesService,
contextKeyService,
keybindingService,
configurationService,
labelService,
updateService,
storageService,
notificationService,
preferencesService,
environmentService,
accessibilityService,
hostService
);
super(menuService, workspacesService, contextKeyService, keybindingService, configurationService, labelService, updateService, storageService, notificationService, preferencesService, environmentService, accessibilityService, hostService, commandService);
this._onVisibilityChange = this._register(new Emitter<boolean>());
this._onFocusStateChange = this._register(new Emitter<boolean>());
@@ -337,7 +326,17 @@ export class CustomMenubarControl extends MenubarControl {
this.registerActions();
registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => {
// Register web menu actions to the file menu when its in the title
this.getWebNavigationMenuItemActions().forEach(actionItem => {
this._register(MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, {
command: actionItem.item,
title: actionItem.item.title,
group: 'z_Web',
when: ContextKeyExpr.and(IsWebContext, ContextKeyExpr.notEquals('config.window.menuBarVisibility', 'compact'))
}));
});
registerThemingParticipant((theme, collector) => {
const menubarActiveWindowFgColor = theme.getColor(TITLE_BAR_ACTIVE_FOREGROUND);
if (menubarActiveWindowFgColor) {
collector.addRule(`
@@ -356,7 +355,6 @@ export class CustomMenubarControl extends MenubarControl {
color: ${activityBarInactiveFgColor};
}
`);
}
const activityBarFgColor = theme.getColor(ACTIVITY_BAR_FOREGROUND);
@@ -383,7 +381,6 @@ export class CustomMenubarControl extends MenubarControl {
`);
}
const menubarSelectedFgColor = theme.getColor(MENUBAR_SELECTION_FOREGROUND);
if (menubarSelectedFgColor) {
collector.addRule(`
@@ -608,6 +605,12 @@ export class CustomMenubarControl extends MenubarControl {
for (let action of actions) {
this.insertActionsBefore(action, target);
// use mnemonicTitle whenever possible
const title = typeof action.item.title === 'string'
? action.item.title
: action.item.title.mnemonicTitle ?? action.item.title.value;
if (action instanceof SubmenuItemAction) {
let submenu = this.menus[action.item.submenu.id];
if (!submenu) {
@@ -625,10 +628,12 @@ export class CustomMenubarControl extends MenubarControl {
const submenuActions: SubmenuAction[] = [];
updateActions(submenu, submenuActions, topLevelTitle);
target.push(new SubmenuAction(action.id, mnemonicMenuLabel(action.label), submenuActions));
target.push(new SubmenuAction(action.id, mnemonicMenuLabel(title), submenuActions));
} else {
action.label = mnemonicMenuLabel(this.calculateActionLabel(action));
target.push(action);
const newAction = new Action(action.id, mnemonicMenuLabel(title), action.class, action.enabled, () => this.commandService.executeCommand(action.id));
newAction.tooltip = action.tooltip;
newAction.checked = action.checked;
target.push(newAction);
}
}
@@ -667,6 +672,27 @@ export class CustomMenubarControl extends MenubarControl {
}
}
private getWebNavigationMenuItemActions(): MenuItemAction[] {
if (!isWeb) {
return []; // only for web
}
const webNavigationActions = [];
const webNavigationMenu = this.menuService.createMenu(MenuId.MenubarHomeMenu, this.contextKeyService);
for (const groups of webNavigationMenu.getActions()) {
const [, actions] = groups;
for (const action of actions) {
if (action instanceof MenuItemAction) {
webNavigationActions.push(action);
}
}
}
webNavigationMenu.dispose();
return webNavigationActions;
}
private getMenuBarOptions(): IMenuBarOptions {
return {
enableMnemonics: this.currentEnableMenuBarMnemonics,
@@ -674,7 +700,35 @@ export class CustomMenubarControl extends MenubarControl {
visibility: this.currentMenubarVisibility,
getKeybinding: (action) => this.keybindingService.lookupKeybinding(action.id),
alwaysOnMnemonics: this.alwaysOnMnemonics,
compactMode: this.currentCompactMenuMode
compactMode: this.currentCompactMenuMode,
getCompactMenuActions: () => {
if (!isWeb) {
return []; // only for web
}
const webNavigationActions: IAction[] = [];
const href = this.environmentService.options?.homeIndicator?.href;
if (href) {
webNavigationActions.push(new Action('goHome', nls.localize('goHome', "Go Home"), undefined, true,
async (event?: MouseEvent) => {
if ((!isMacintosh && event?.ctrlKey) || (isMacintosh && event?.metaKey)) {
window.open(href, '_blank');
} else {
window.location.href = href;
}
}));
}
const otherActions = this.getWebNavigationMenuItemActions().map(action => {
const title = typeof action.item.title === 'string'
? action.item.title
: action.item.title.mnemonicTitle ?? action.item.title.value;
return new Action(action.id, mnemonicMenuLabel(title), action.class, action.enabled, () => this.commandService.executeCommand(action.id));
});
webNavigationActions.push(...otherActions);
return webNavigationActions;
}
};
}

View File

@@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/titlebarpart';
import { localize } from 'vs/nls';
import { dirname, basename } from 'vs/base/common/resources';
import { Part } from 'vs/workbench/browser/part';
import { ITitleService, ITitleProperties } from 'vs/workbench/services/title/common/titleService';
@@ -15,17 +16,16 @@ import { IAction } from 'vs/base/common/actions';
import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { DisposableStore, dispose } from 'vs/base/common/lifecycle';
import * as nls from 'vs/nls';
import { EditorResourceAccessor, Verbosity, SideBySideEditor } from 'vs/workbench/common/editor';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { IWorkspaceContextService, WorkbenchState, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { IThemeService, registerThemingParticipant, IColorTheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService';
import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
import { TITLE_BAR_ACTIVE_BACKGROUND, TITLE_BAR_ACTIVE_FOREGROUND, TITLE_BAR_INACTIVE_FOREGROUND, TITLE_BAR_INACTIVE_BACKGROUND, TITLE_BAR_BORDER, WORKBENCH_BACKGROUND } from 'vs/workbench/common/theme';
import { isMacintosh, isWindows, isLinux, isWeb } from 'vs/base/common/platform';
import { URI } from 'vs/base/common/uri';
import { Color } from 'vs/base/common/color';
import { trim } from 'vs/base/common/strings';
import { EventType, EventHelper, Dimension, isAncestor, append, $, addDisposableListener, runAtThisOrScheduleAtNextAnimationFrame } from 'vs/base/browser/dom';
import { EventType, EventHelper, Dimension, isAncestor, append, $, addDisposableListener, runAtThisOrScheduleAtNextAnimationFrame, prepend } from 'vs/base/browser/dom';
import { CustomMenubarControl } from 'vs/workbench/browser/parts/titlebar/menubarControl';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { template } from 'vs/base/common/labels';
@@ -41,12 +41,13 @@ import { IHostService } from 'vs/workbench/services/host/browser/host';
import { IProductService } from 'vs/platform/product/common/productService';
import { Schemas } from 'vs/base/common/network';
import { withNullAsUndefined } from 'vs/base/common/types';
import { Codicon, iconRegistry } from 'vs/base/common/codicons';
export class TitlebarPart extends Part implements ITitleService {
private static readonly NLS_UNSUPPORTED = nls.localize('patchedWindowTitle', "[Unsupported]");
private static readonly NLS_USER_IS_ADMIN = isWindows ? nls.localize('userIsAdmin', "[Administrator]") : nls.localize('userIsSudo', "[Superuser]");
private static readonly NLS_EXTENSION_HOST = nls.localize('devExtensionWindowTitlePrefix', "[Extension Development Host]");
private static readonly NLS_UNSUPPORTED = localize('patchedWindowTitle', "[Unsupported]");
private static readonly NLS_USER_IS_ADMIN = isWindows ? localize('userIsAdmin', "[Administrator]") : localize('userIsSudo', "[Superuser]");
private static readonly NLS_EXTENSION_HOST = localize('devExtensionWindowTitlePrefix', "[Extension Development Host]");
private static readonly TITLE_DIRTY = '\u25cf ';
//#region IView
@@ -65,6 +66,8 @@ export class TitlebarPart extends Part implements ITitleService {
protected title!: HTMLElement;
protected customMenubar: CustomMenubarControl | undefined;
protected appIcon: HTMLElement | undefined;
private appIconBadge: HTMLElement | undefined;
protected menubar?: HTMLElement;
protected lastLayoutDimensions: Dimension | undefined;
private titleBarStyle: 'native' | 'custom';
@@ -86,7 +89,7 @@ export class TitlebarPart extends Part implements ITitleService {
@IEditorService private readonly editorService: IEditorService,
@IWorkbenchEnvironmentService protected readonly environmentService: IWorkbenchEnvironmentService,
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IInstantiationService protected readonly instantiationService: IInstantiationService,
@IThemeService themeService: IThemeService,
@ILabelService private readonly labelService: ILabelService,
@IStorageService storageService: IStorageService,
@@ -341,6 +344,28 @@ export class TitlebarPart extends Part implements ITitleService {
createContentArea(parent: HTMLElement): HTMLElement {
this.element = parent;
// App Icon (Native Windows/Linux and Web)
if (!isMacintosh || isWeb) {
this.appIcon = prepend(this.element, $('a.window-appicon'));
// Web-only home indicator and menu
if (isWeb) {
const homeIndicator = this.environmentService.options?.homeIndicator;
if (homeIndicator) {
let codicon = iconRegistry.get(homeIndicator.icon);
if (!codicon) {
codicon = Codicon.code;
}
this.appIcon.setAttribute('href', homeIndicator.href);
this.appIcon.classList.add(...codicon.classNamesArray);
this.appIconBadge = document.createElement('div');
this.appIconBadge.classList.add('home-bar-icon-badge');
this.appIcon.appendChild(this.appIconBadge);
}
}
}
// Menubar: install a custom menu bar depending on configuration
// and when not in activity bar
if (this.titleBarStyle !== 'native'
@@ -407,6 +432,11 @@ export class TitlebarPart extends Part implements ITitleService {
return color.isOpaque() ? color : color.makeOpaque(WORKBENCH_BACKGROUND(theme));
}) || '';
this.element.style.backgroundColor = titleBackground;
if (this.appIconBadge) {
this.appIconBadge.style.backgroundColor = titleBackground;
}
if (titleBackground && Color.fromHex(titleBackground).isLighter()) {
this.element.classList.add('light');
} else {
@@ -441,7 +471,7 @@ export class TitlebarPart extends Part implements ITitleService {
protected adjustTitleMarginToCenter(): void {
if (this.customMenubar && this.menubar) {
const leftMarker = this.menubar.clientWidth + 10;
const leftMarker = (this.appIcon ? this.appIcon.clientWidth : 0) + this.menubar.clientWidth + 10;
const rightMarker = this.element.clientWidth - 10;
// Not enough space to center the titlebar within window,
@@ -497,7 +527,7 @@ export class TitlebarPart extends Part implements ITitleService {
}
}
registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => {
registerThemingParticipant((theme, collector) => {
const titlebarActiveFg = theme.getColor(TITLE_BAR_ACTIVE_FOREGROUND);
if (titlebarActiveFg) {
collector.addRule(`

View File

@@ -8,19 +8,19 @@ import { toDisposable, IDisposable, Disposable, DisposableStore } from 'vs/base/
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { MenuId, IMenuService, MenuItemAction, registerAction2, Action2, SubmenuItemAction } from 'vs/platform/actions/common/actions';
import { MenuId, IMenuService, registerAction2, Action2 } from 'vs/platform/actions/common/actions';
import { IContextKeyService, ContextKeyExpr, ContextKeyEqualsExpr, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { ITreeView, ITreeViewDescriptor, IViewsRegistry, Extensions, IViewDescriptorService, ITreeItem, TreeItemCollapsibleState, ITreeViewDataProvider, TreeViewItemHandleArg, ITreeItemLabel, ViewContainer, ViewContainerLocation, ResolvableTreeItem } from 'vs/workbench/common/views';
import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IThemeService, FileThemeIcon, FolderThemeIcon, registerThemingParticipant, ThemeIcon } from 'vs/platform/theme/common/themeService';
import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer';
import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPane';
import { Registry } from 'vs/platform/registry/common/platform';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { Event, Emitter } from 'vs/base/common/event';
import { IAction, ActionRunner, IActionViewItemProvider } from 'vs/base/common/actions';
import { MenuEntryActionViewItem, createAndFillInContextMenuActions, SubmenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem';
import { createAndFillInContextMenuActions, createActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { IProgressService } from 'vs/platform/progress/common/progress';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
@@ -52,6 +52,8 @@ import { IIconLabelMarkdownString } from 'vs/base/browser/ui/iconLabel/iconLabel
import { renderMarkdownAsPlaintext } from 'vs/base/browser/markdownRenderer';
import { API_OPEN_DIFF_EDITOR_COMMAND_ID, API_OPEN_EDITOR_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands';
import { Codicon } from 'vs/base/common/codicons';
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
import { Command } from 'vs/editor/common/modes';
export class TreeViewPane extends ViewPane {
@@ -453,15 +455,7 @@ export class TreeView extends Disposable implements ITreeView {
}
private createTree() {
const actionViewItemProvider = (action: IAction) => {
if (action instanceof MenuItemAction) {
return this.instantiationService.createInstance(MenuEntryActionViewItem, action);
} else if (action instanceof SubmenuItemAction) {
return this.instantiationService.createInstance(SubmenuEntryActionViewItem, action);
}
return undefined;
};
const actionViewItemProvider = createActionViewItem.bind(undefined, this.instantiationService);
const treeMenus = this._register(this.instantiationService.createInstance(TreeMenus, this.id));
this.treeLabels = this._register(this.instantiationService.createInstance(ResourceLabels, this));
const dataSource = this.instantiationService.createInstance(TreeDataSource, this, <T>(task: Promise<T>) => this.progressService.withProgress({ location: this.id }, () => task));
@@ -534,12 +528,13 @@ export class TreeView extends Disposable implements ITreeView {
}));
this.tree.setInput(this.root).then(() => this.updateContentAreas());
this._register(this.tree.onDidOpen(e => {
this._register(this.tree.onDidOpen(async (e) => {
if (!e.browserEvent) {
return;
}
const selection = this.tree!.getSelection();
const command = selection.length === 1 ? selection[0].command : undefined;
const command = await this.resolveCommand(selection.length === 1 ? selection[0] : undefined);
if (command) {
let args = command.arguments || [];
if (command.id === API_OPEN_EDITOR_COMMAND_ID || command.id === API_OPEN_DIFF_EDITOR_COMMAND_ID) {
@@ -554,6 +549,17 @@ export class TreeView extends Disposable implements ITreeView {
}
private async resolveCommand(element: ITreeItem | undefined): Promise<Command | undefined> {
let command = element?.command;
if (element && !command) {
if ((element instanceof ResolvableTreeItem) && element.hasResolve) {
await element.resolve(new CancellationTokenSource().token);
command = element.command;
}
}
return command;
}
private onContextMenu(treeMenus: TreeMenus, treeEvent: ITreeContextMenuEvent<ITreeItem>, actionRunner: MultipleSelectionActionRunner): void {
this.hoverService.hideHover();
const node: ITreeItem | null = treeEvent.element;
@@ -776,10 +782,17 @@ class TreeDataSource implements IAsyncDataSource<ITreeItem, ITreeItem> {
}
async getChildren(element: ITreeItem): Promise<ITreeItem[]> {
let result: ITreeItem[] = [];
if (this.treeView.dataProvider) {
return this.withProgress(this.treeView.dataProvider.getChildren(element));
try {
result = await this.withProgress(this.treeView.dataProvider.getChildren(element));
} catch (e) {
if (!(<string>e.message).startsWith('Bad progress location:')) {
throw e;
}
}
}
return [];
return result;
}
}
@@ -840,6 +853,9 @@ class TreeRenderer extends Disposable implements ITreeRenderer<ITreeItem, FuzzyS
this._hoverDelegate = {
showHover: (options: IHoverDelegateOptions): IDisposable | undefined => {
return this.hoverService.showHover(options);
},
hideHover: () => {
return this.hoverService.hideHover();
}
};
}
@@ -868,7 +884,7 @@ class TreeRenderer extends Disposable implements ITreeRenderer<ITreeItem, FuzzyS
private getHover(label: string | undefined, resource: URI | null, node: ITreeItem): string | IIconLabelMarkdownString | undefined {
if (!(node instanceof ResolvableTreeItem) || !node.hasResolve) {
if (resource) {
if (resource && !node.tooltip) {
return undefined;
} else if (!node.tooltip) {
return label;
@@ -880,13 +896,13 @@ class TreeRenderer extends Disposable implements ITreeRenderer<ITreeItem, FuzzyS
}
return {
markdown: (): Promise<IMarkdownString | string | undefined> => {
markdown: (token: CancellationToken): Promise<IMarkdownString | string | undefined> => {
return new Promise<IMarkdownString | string | undefined>(async (resolve) => {
await node.resolve();
await node.resolve(token);
resolve(node.tooltip);
});
},
markdownNotSupportedFallback: resource ? undefined : '' // Passing undefined as the fallback for a resource falls back to the old native hover
markdownNotSupportedFallback: resource ? undefined : label ?? '' // Passing undefined as the fallback for a resource falls back to the old native hover
};
}
@@ -928,7 +944,7 @@ class TreeRenderer extends Disposable implements ITreeRenderer<ITreeItem, FuzzyS
templateData.resourceLabel.setResource({ name: label, description, resource: labelResource }, {
fileKind: this.getFileKind(node),
title,
hideIcon: !!iconUrl,
hideIcon: !!iconUrl || (!!node.themeIcon && !this.isFileKindThemeIcon(node.themeIcon)),
fileDecorations,
extraClasses: ['custom-view-tree-node-item-resourceLabel'],
matches: matches ? matches : createMatches(element.filterData),
@@ -1168,4 +1184,3 @@ export class CustomTreeView extends TreeView {
}
}
}

View File

@@ -1,105 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IAction } from 'vs/base/common/actions';
import { Disposable, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { Emitter, Event } from 'vs/base/common/event';
import { MenuId, IMenuService } from 'vs/platform/actions/common/actions';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem';
export class ViewMenuActions extends Disposable {
private primaryActions: IAction[] = [];
private readonly titleActionsDisposable = this._register(new MutableDisposable());
private secondaryActions: IAction[] = [];
private contextMenuActions: IAction[] = [];
private _onDidChangeTitle = this._register(new Emitter<void>());
readonly onDidChangeTitle: Event<void> = this._onDidChangeTitle.event;
constructor(
viewId: string,
menuId: MenuId,
contextMenuId: MenuId,
@IContextKeyService private readonly contextKeyService: IContextKeyService,
@IMenuService private readonly menuService: IMenuService,
) {
super();
const scopedContextKeyService = this._register(this.contextKeyService.createScoped());
scopedContextKeyService.createKey('view', viewId);
const menu = this._register(this.menuService.createMenu(menuId, scopedContextKeyService));
const updateActions = () => {
this.primaryActions = [];
this.secondaryActions = [];
this.titleActionsDisposable.value = createAndFillInActionBarActions(menu, { shouldForwardArgs: true }, { primary: this.primaryActions, secondary: this.secondaryActions });
this._onDidChangeTitle.fire();
};
this._register(menu.onDidChange(updateActions));
updateActions();
const contextMenu = this._register(this.menuService.createMenu(contextMenuId, scopedContextKeyService));
const updateContextMenuActions = () => {
this.contextMenuActions = [];
this.titleActionsDisposable.value = createAndFillInActionBarActions(contextMenu, { shouldForwardArgs: true }, { primary: [], secondary: this.contextMenuActions });
};
this._register(contextMenu.onDidChange(updateContextMenuActions));
updateContextMenuActions();
this._register(toDisposable(() => {
this.primaryActions = [];
this.secondaryActions = [];
this.contextMenuActions = [];
}));
}
getPrimaryActions(): IAction[] {
return this.primaryActions;
}
getSecondaryActions(): IAction[] {
return this.secondaryActions;
}
getContextMenuActions(): IAction[] {
return this.contextMenuActions;
}
}
export class ViewContainerMenuActions extends Disposable {
private readonly titleActionsDisposable = this._register(new MutableDisposable());
private contextMenuActions: IAction[] = [];
constructor(
containerId: string,
contextMenuId: MenuId,
@IContextKeyService private readonly contextKeyService: IContextKeyService,
@IMenuService private readonly menuService: IMenuService,
) {
super();
const scopedContextKeyService = this._register(this.contextKeyService.createScoped());
scopedContextKeyService.createKey('container', containerId);
const contextMenu = this._register(this.menuService.createMenu(contextMenuId, scopedContextKeyService));
const updateContextMenuActions = () => {
this.contextMenuActions = [];
this.titleActionsDisposable.value = createAndFillInActionBarActions(contextMenu, { shouldForwardArgs: true }, { primary: [], secondary: this.contextMenuActions });
};
this._register(contextMenu.onDidChange(updateContextMenuActions));
updateContextMenuActions();
this._register(toDisposable(() => {
this.contextMenuActions = [];
}));
}
getContextMenuActions(): IAction[] {
return this.contextMenuActions;
}
}

View File

@@ -0,0 +1,642 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/paneviewlet';
import * as nls from 'vs/nls';
import { Event, Emitter } from 'vs/base/common/event';
import { foreground } from 'vs/platform/theme/common/colorRegistry';
import { attachButtonStyler, attachLinkStyler, attachProgressBarStyler } from 'vs/platform/theme/common/styler';
import { PANEL_BACKGROUND, SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme';
import { after, append, $, trackFocus, EventType, addDisposableListener, createCSSRule, asCSSUrl } from 'vs/base/browser/dom';
import { IDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecycle';
import { IAction, IActionViewItem } from 'vs/base/common/actions';
import { ActionsOrientation, prepareActions } from 'vs/base/browser/ui/actionbar/actionbar';
import { Registry } from 'vs/platform/registry/common/platform';
import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService';
import { IPaneOptions, Pane, IPaneStyles } from 'vs/base/browser/ui/splitview/paneview';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { Extensions as ViewContainerExtensions, IView, FocusedViewContext, IViewDescriptorService, ViewContainerLocation, IViewsRegistry, IViewContentDescriptor, defaultViewIcon, IViewsService, ViewContainerLocationToString } from 'vs/workbench/common/views';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { assertIsDefined } from 'vs/base/common/types';
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { MenuId, Action2, IAction2Options, IMenuService } from 'vs/platform/actions/common/actions';
import { createActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem';
import { parseLinkedText } from 'vs/base/common/linkedText';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { Button } from 'vs/base/browser/ui/button/button';
import { Link } from 'vs/platform/opener/browser/link';
import { Orientation } from 'vs/base/browser/ui/sash/sash';
import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar';
import { CompositeProgressIndicator } from 'vs/workbench/services/progress/browser/progressIndicator';
import { IProgressIndicator } from 'vs/platform/progress/common/progress';
import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
import { ScrollbarVisibility } from 'vs/base/common/scrollable';
import { URI } from 'vs/base/common/uri';
import { registerIcon } from 'vs/platform/theme/common/iconRegistry';
import { Codicon } from 'vs/base/common/codicons';
import { CompositeMenuActions } from 'vs/workbench/browser/menuActions';
export interface IViewPaneOptions extends IPaneOptions {
id: string;
showActionsAlways?: boolean;
titleMenuId?: MenuId;
}
type WelcomeActionClassification = {
viewId: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
uri: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
};
const viewPaneContainerExpandedIcon = registerIcon('view-pane-container-expanded', Codicon.chevronDown, nls.localize('viewPaneContainerExpandedIcon', 'Icon for an expanded view pane container.'));
const viewPaneContainerCollapsedIcon = registerIcon('view-pane-container-collapsed', Codicon.chevronRight, nls.localize('viewPaneContainerCollapsedIcon', 'Icon for a collapsed view pane container.'));
const viewsRegistry = Registry.as<IViewsRegistry>(ViewContainerExtensions.ViewsRegistry);
interface IItem {
readonly descriptor: IViewContentDescriptor;
visible: boolean;
}
class ViewWelcomeController {
private _onDidChange = new Emitter<void>();
readonly onDidChange = this._onDidChange.event;
private defaultItem: IItem | undefined;
private items: IItem[] = [];
get contents(): IViewContentDescriptor[] {
const visibleItems = this.items.filter(v => v.visible);
if (visibleItems.length === 0 && this.defaultItem) {
return [this.defaultItem.descriptor];
}
return visibleItems.map(v => v.descriptor);
}
private contextKeyService: IContextKeyService;
private disposables = new DisposableStore();
constructor(
private id: string,
@IContextKeyService contextKeyService: IContextKeyService,
) {
this.contextKeyService = contextKeyService.createScoped();
this.disposables.add(this.contextKeyService);
contextKeyService.onDidChangeContext(this.onDidChangeContext, this, this.disposables);
Event.filter(viewsRegistry.onDidChangeViewWelcomeContent, id => id === this.id)(this.onDidChangeViewWelcomeContent, this, this.disposables);
this.onDidChangeViewWelcomeContent();
}
private onDidChangeViewWelcomeContent(): void {
const descriptors = viewsRegistry.getViewWelcomeContent(this.id);
this.items = [];
for (const descriptor of descriptors) {
if (descriptor.when === 'default') {
this.defaultItem = { descriptor, visible: true };
} else {
const visible = descriptor.when ? this.contextKeyService.contextMatchesRules(descriptor.when) : true;
this.items.push({ descriptor, visible });
}
}
this._onDidChange.fire();
}
private onDidChangeContext(): void {
let didChange = false;
for (const item of this.items) {
if (!item.descriptor.when || item.descriptor.when === 'default') {
continue;
}
const visible = this.contextKeyService.contextMatchesRules(item.descriptor.when);
if (item.visible === visible) {
continue;
}
item.visible = visible;
didChange = true;
}
if (didChange) {
this._onDidChange.fire();
}
}
dispose(): void {
this.disposables.dispose();
}
}
class ViewMenuActions extends CompositeMenuActions {
constructor(
viewId: string,
menuId: MenuId,
contextMenuId: MenuId,
@IContextKeyService contextKeyService: IContextKeyService,
@IMenuService menuService: IMenuService,
@IViewDescriptorService viewDescriptorService: IViewDescriptorService,
) {
const scopedContextKeyService = contextKeyService.createScoped();
scopedContextKeyService.createKey('view', viewId);
const viewLocationKey = scopedContextKeyService.createKey('viewLocation', ViewContainerLocationToString(viewDescriptorService.getViewLocationById(viewId)!));
super(menuId, contextMenuId, { shouldForwardArgs: true }, scopedContextKeyService, menuService);
this._register(scopedContextKeyService);
this._register(Event.filter(viewDescriptorService.onDidChangeLocation, e => e.views.some(view => view.id === viewId))(() => viewLocationKey.set(ViewContainerLocationToString(viewDescriptorService.getViewLocationById(viewId)!))));
}
}
export abstract class ViewPane extends Pane implements IView {
private static readonly AlwaysShowActionsConfig = 'workbench.view.alwaysShowHeaderActions';
private _onDidFocus = this._register(new Emitter<void>());
readonly onDidFocus: Event<void> = this._onDidFocus.event;
private _onDidBlur = this._register(new Emitter<void>());
readonly onDidBlur: Event<void> = this._onDidBlur.event;
private _onDidChangeBodyVisibility = this._register(new Emitter<boolean>());
readonly onDidChangeBodyVisibility: Event<boolean> = this._onDidChangeBodyVisibility.event;
protected _onDidChangeTitleArea = this._register(new Emitter<void>());
readonly onDidChangeTitleArea: Event<void> = this._onDidChangeTitleArea.event;
protected _onDidChangeViewWelcomeState = this._register(new Emitter<void>());
readonly onDidChangeViewWelcomeState: Event<void> = this._onDidChangeViewWelcomeState.event;
private focusedViewContextKey: IContextKey<string>;
private _isVisible: boolean = false;
readonly id: string;
private _title: string;
public get title(): string {
return this._title;
}
private _titleDescription: string | undefined;
public get titleDescription(): string | undefined {
return this._titleDescription;
}
private readonly menuActions: ViewMenuActions;
private progressBar!: ProgressBar;
private progressIndicator!: IProgressIndicator;
private toolbar?: ToolBar;
private readonly showActionsAlways: boolean = false;
private headerContainer?: HTMLElement;
private titleContainer?: HTMLElement;
private titleDescriptionContainer?: HTMLElement;
private iconContainer?: HTMLElement;
protected twistiesContainer?: HTMLElement;
private bodyContainer!: HTMLElement;
private viewWelcomeContainer!: HTMLElement;
private viewWelcomeDisposable: IDisposable = Disposable.None;
private viewWelcomeController: ViewWelcomeController;
constructor(
options: IViewPaneOptions,
@IKeybindingService protected keybindingService: IKeybindingService,
@IContextMenuService protected contextMenuService: IContextMenuService,
@IConfigurationService protected readonly configurationService: IConfigurationService,
@IContextKeyService protected contextKeyService: IContextKeyService,
@IViewDescriptorService protected viewDescriptorService: IViewDescriptorService,
@IInstantiationService protected instantiationService: IInstantiationService,
@IOpenerService protected openerService: IOpenerService,
@IThemeService protected themeService: IThemeService,
@ITelemetryService protected telemetryService: ITelemetryService,
) {
super({ ...options, ...{ orientation: viewDescriptorService.getViewLocationById(options.id) === ViewContainerLocation.Panel ? Orientation.HORIZONTAL : Orientation.VERTICAL } });
this.id = options.id;
this._title = options.title;
this._titleDescription = options.titleDescription;
this.showActionsAlways = !!options.showActionsAlways;
this.focusedViewContextKey = FocusedViewContext.bindTo(contextKeyService);
this.menuActions = this._register(instantiationService.createInstance(ViewMenuActions, this.id, options.titleMenuId || MenuId.ViewTitle, MenuId.ViewTitleContext));
this._register(this.menuActions.onDidChange(() => this.updateActions()));
this.viewWelcomeController = new ViewWelcomeController(this.id, contextKeyService);
}
get headerVisible(): boolean {
return super.headerVisible;
}
set headerVisible(visible: boolean) {
super.headerVisible = visible;
this.element.classList.toggle('merged-header', !visible);
}
setVisible(visible: boolean): void {
if (this._isVisible !== visible) {
this._isVisible = visible;
if (this.isExpanded()) {
this._onDidChangeBodyVisibility.fire(visible);
}
}
}
isVisible(): boolean {
return this._isVisible;
}
isBodyVisible(): boolean {
return this._isVisible && this.isExpanded();
}
setExpanded(expanded: boolean): boolean {
const changed = super.setExpanded(expanded);
if (changed) {
this._onDidChangeBodyVisibility.fire(expanded);
}
if (this.twistiesContainer) {
this.twistiesContainer.classList.remove(...ThemeIcon.asClassNameArray(this.getTwistyIcon(!expanded)));
this.twistiesContainer.classList.add(...ThemeIcon.asClassNameArray(this.getTwistyIcon(expanded)));
}
return changed;
}
render(): void {
super.render();
const focusTracker = trackFocus(this.element);
this._register(focusTracker);
this._register(focusTracker.onDidFocus(() => {
this.focusedViewContextKey.set(this.id);
this._onDidFocus.fire();
}));
this._register(focusTracker.onDidBlur(() => {
if (this.focusedViewContextKey.get() === this.id) {
this.focusedViewContextKey.reset();
}
this._onDidBlur.fire();
}));
}
protected renderHeader(container: HTMLElement): void {
this.headerContainer = container;
this.twistiesContainer = append(container, $(ThemeIcon.asCSSSelector(this.getTwistyIcon(this.isExpanded()))));
this.renderHeaderTitle(container, this.title);
const actions = append(container, $('.actions'));
actions.classList.toggle('show', this.showActionsAlways);
this.toolbar = new ToolBar(actions, this.contextMenuService, {
orientation: ActionsOrientation.HORIZONTAL,
actionViewItemProvider: action => this.getActionViewItem(action),
ariaLabel: nls.localize('viewToolbarAriaLabel', "{0} actions", this.title),
getKeyBinding: action => this.keybindingService.lookupKeybinding(action.id),
renderDropdownAsChildElement: true
});
this._register(this.toolbar);
this.setActions();
this._register(addDisposableListener(actions, EventType.CLICK, e => e.preventDefault()));
this._register(this.viewDescriptorService.getViewContainerModel(this.viewDescriptorService.getViewContainerByViewId(this.id)!)!.onDidChangeContainerInfo(({ title }) => {
this.updateTitle(this.title);
}));
const onDidRelevantConfigurationChange = Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration(ViewPane.AlwaysShowActionsConfig));
this._register(onDidRelevantConfigurationChange(this.updateActionsVisibility, this));
this.updateActionsVisibility();
}
protected getTwistyIcon(expanded: boolean): ThemeIcon {
return expanded ? viewPaneContainerExpandedIcon : viewPaneContainerCollapsedIcon;
}
style(styles: IPaneStyles): void {
super.style(styles);
const icon = this.getIcon();
if (this.iconContainer) {
const fgColor = styles.headerForeground || this.themeService.getColorTheme().getColor(foreground);
if (URI.isUri(icon)) {
// Apply background color to activity bar item provided with iconUrls
this.iconContainer.style.backgroundColor = fgColor ? fgColor.toString() : '';
this.iconContainer.style.color = '';
} else {
// Apply foreground color to activity bar items provided with codicons
this.iconContainer.style.color = fgColor ? fgColor.toString() : '';
this.iconContainer.style.backgroundColor = '';
}
}
}
private getIcon(): ThemeIcon | URI {
return this.viewDescriptorService.getViewDescriptorById(this.id)?.containerIcon || defaultViewIcon;
}
protected renderHeaderTitle(container: HTMLElement, title: string): void {
this.iconContainer = append(container, $('.icon', undefined));
const icon = this.getIcon();
let cssClass: string | undefined = undefined;
if (URI.isUri(icon)) {
cssClass = `view-${this.id.replace(/[\.\:]/g, '-')}`;
const iconClass = `.pane-header .icon.${cssClass}`;
createCSSRule(iconClass, `
mask: ${asCSSUrl(icon)} no-repeat 50% 50%;
mask-size: 24px;
-webkit-mask: ${asCSSUrl(icon)} no-repeat 50% 50%;
-webkit-mask-size: 16px;
`);
} else if (ThemeIcon.isThemeIcon(icon)) {
cssClass = ThemeIcon.asClassName(icon);
}
if (cssClass) {
this.iconContainer.classList.add(...cssClass.split(' '));
}
const calculatedTitle = this.calculateTitle(title);
this.titleContainer = append(container, $('h3.title', { title: calculatedTitle }, calculatedTitle));
if (this._titleDescription) {
this.setTitleDescription(this._titleDescription);
}
this.iconContainer.title = calculatedTitle;
this.iconContainer.setAttribute('aria-label', calculatedTitle);
}
protected updateTitle(title: string): void {
const calculatedTitle = this.calculateTitle(title);
if (this.titleContainer) {
this.titleContainer.textContent = calculatedTitle;
this.titleContainer.setAttribute('title', calculatedTitle);
}
if (this.iconContainer) {
this.iconContainer.title = calculatedTitle;
this.iconContainer.setAttribute('aria-label', calculatedTitle);
}
this._title = title;
this._onDidChangeTitleArea.fire();
}
private setTitleDescription(description: string | undefined) {
if (this.titleDescriptionContainer) {
this.titleDescriptionContainer.textContent = description ?? '';
this.titleDescriptionContainer.setAttribute('title', description ?? '');
}
else if (description && this.titleContainer) {
this.titleDescriptionContainer = after(this.titleContainer, $('span.description', { title: description }, description));
}
}
protected updateTitleDescription(description?: string | undefined): void {
this.setTitleDescription(description);
this._titleDescription = description;
this._onDidChangeTitleArea.fire();
}
private calculateTitle(title: string): string {
const viewContainer = this.viewDescriptorService.getViewContainerByViewId(this.id)!;
const model = this.viewDescriptorService.getViewContainerModel(viewContainer);
const viewDescriptor = this.viewDescriptorService.getViewDescriptorById(this.id);
const isDefault = this.viewDescriptorService.getDefaultContainerById(this.id) === viewContainer;
if (!isDefault && viewDescriptor?.containerTitle && model.title !== viewDescriptor.containerTitle) {
return `${viewDescriptor.containerTitle}: ${title}`;
}
return title;
}
private scrollableElement!: DomScrollableElement;
protected renderBody(container: HTMLElement): void {
this.bodyContainer = container;
const viewWelcomeContainer = append(container, $('.welcome-view'));
this.viewWelcomeContainer = $('.welcome-view-content', { tabIndex: 0 });
this.scrollableElement = this._register(new DomScrollableElement(this.viewWelcomeContainer, {
alwaysConsumeMouseWheel: true,
horizontal: ScrollbarVisibility.Hidden,
vertical: ScrollbarVisibility.Visible,
}));
append(viewWelcomeContainer, this.scrollableElement.getDomNode());
const onViewWelcomeChange = Event.any(this.viewWelcomeController.onDidChange, this.onDidChangeViewWelcomeState);
this._register(onViewWelcomeChange(this.updateViewWelcome, this));
this.updateViewWelcome();
}
protected layoutBody(height: number, width: number): void {
this.viewWelcomeContainer.style.height = `${height}px`;
this.viewWelcomeContainer.style.width = `${width}px`;
this.scrollableElement.scanDomNode();
}
getProgressIndicator() {
if (this.progressBar === undefined) {
// Progress bar
this.progressBar = this._register(new ProgressBar(this.element));
this._register(attachProgressBarStyler(this.progressBar, this.themeService));
this.progressBar.hide();
}
if (this.progressIndicator === undefined) {
this.progressIndicator = this.instantiationService.createInstance(CompositeProgressIndicator, assertIsDefined(this.progressBar), this.id, this.isBodyVisible());
}
return this.progressIndicator;
}
protected getProgressLocation(): string {
return this.viewDescriptorService.getViewContainerByViewId(this.id)!.id;
}
protected getBackgroundColor(): string {
return this.viewDescriptorService.getViewLocationById(this.id) === ViewContainerLocation.Panel ? PANEL_BACKGROUND : SIDE_BAR_BACKGROUND;
}
focus(): void {
if (this.shouldShowWelcome()) {
this.viewWelcomeContainer.focus();
} else if (this.element) {
this.element.focus();
this._onDidFocus.fire();
}
}
private setActions(): void {
if (this.toolbar) {
this.toolbar.setActions(prepareActions(this.getActions()), prepareActions(this.getSecondaryActions()));
this.toolbar.context = this.getActionsContext();
}
}
private updateActionsVisibility(): void {
if (!this.headerContainer) {
return;
}
const shouldAlwaysShowActions = this.configurationService.getValue<boolean>('workbench.view.alwaysShowHeaderActions');
this.headerContainer.classList.toggle('actions-always-visible', shouldAlwaysShowActions);
}
protected updateActions(): void {
this.setActions();
this._onDidChangeTitleArea.fire();
}
getActions(): IAction[] {
return this.menuActions.getPrimaryActions();
}
getSecondaryActions(): IAction[] {
return this.menuActions.getSecondaryActions();
}
getContextMenuActions(): IAction[] {
return this.menuActions.getContextMenuActions();
}
getActionViewItem(action: IAction): IActionViewItem | undefined {
return createActionViewItem(this.instantiationService, action);
}
getActionsContext(): unknown {
return undefined;
}
getOptimalWidth(): number {
return 0;
}
saveState(): void {
// Subclasses to implement for saving state
}
private updateViewWelcome(): void {
this.viewWelcomeDisposable.dispose();
if (!this.shouldShowWelcome()) {
this.bodyContainer.classList.remove('welcome');
this.viewWelcomeContainer.innerText = '';
this.scrollableElement.scanDomNode();
return;
}
const contents = this.viewWelcomeController.contents;
if (contents.length === 0) {
this.bodyContainer.classList.remove('welcome');
this.viewWelcomeContainer.innerText = '';
this.scrollableElement.scanDomNode();
return;
}
const disposables = new DisposableStore();
this.bodyContainer.classList.add('welcome');
this.viewWelcomeContainer.innerText = '';
for (const { content, precondition } of contents) {
const lines = content.split('\n');
for (let line of lines) {
line = line.trim();
if (!line) {
continue;
}
const linkedText = parseLinkedText(line);
if (linkedText.nodes.length === 1 && typeof linkedText.nodes[0] !== 'string') {
const node = linkedText.nodes[0];
const button = new Button(this.viewWelcomeContainer, { title: node.title, supportIcons: true });
button.label = node.label;
button.onDidClick(_ => {
this.telemetryService.publicLog2<{ viewId: string, uri: string }, WelcomeActionClassification>('views.welcomeAction', { viewId: this.id, uri: node.href });
this.openerService.open(node.href);
}, null, disposables);
disposables.add(button);
disposables.add(attachButtonStyler(button, this.themeService));
if (precondition) {
const updateEnablement = () => button.enabled = this.contextKeyService.contextMatchesRules(precondition);
updateEnablement();
const keys = new Set();
precondition.keys().forEach(key => keys.add(key));
const onDidChangeContext = Event.filter(this.contextKeyService.onDidChangeContext, e => e.affectsSome(keys));
onDidChangeContext(updateEnablement, null, disposables);
}
} else {
const p = append(this.viewWelcomeContainer, $('p'));
for (const node of linkedText.nodes) {
if (typeof node === 'string') {
append(p, document.createTextNode(node));
} else {
const link = this.instantiationService.createInstance(Link, node);
append(p, link.el);
disposables.add(link);
disposables.add(attachLinkStyler(link, this.themeService));
if (precondition && node.href.startsWith('command:')) {
const updateEnablement = () => link.style({ disabled: !this.contextKeyService.contextMatchesRules(precondition) });
updateEnablement();
const keys = new Set();
precondition.keys().forEach(key => keys.add(key));
const onDidChangeContext = Event.filter(this.contextKeyService.onDidChangeContext, e => e.affectsSome(keys));
onDidChangeContext(updateEnablement, null, disposables);
}
}
}
}
}
}
this.scrollableElement.scanDomNode();
this.viewWelcomeDisposable = disposables;
}
shouldShowWelcome(): boolean {
return false;
}
}
export abstract class ViewAction<T extends IView> extends Action2 {
constructor(readonly desc: Readonly<IAction2Options> & { viewId: string }) {
super(desc);
}
run(accessor: ServicesAccessor, ...args: any[]) {
const view = accessor.get(IViewsService).getActiveViewWithId(this.desc.viewId);
if (view) {
return this.runInView(accessor, <T>view, ...args);
}
}
abstract runInView(accessor: ServicesAccessor, view: T, ...args: any[]): any;
}

View File

@@ -6,52 +6,45 @@
import 'vs/css!./media/paneviewlet';
import * as nls from 'vs/nls';
import { Event, Emitter } from 'vs/base/common/event';
import { ColorIdentifier, activeContrastBorder, foreground } from 'vs/platform/theme/common/colorRegistry';
import { attachStyler, IColorMapping, attachButtonStyler, attachLinkStyler, attachProgressBarStyler } from 'vs/platform/theme/common/styler';
import { SIDE_BAR_DRAG_AND_DROP_BACKGROUND, SIDE_BAR_SECTION_HEADER_FOREGROUND, SIDE_BAR_SECTION_HEADER_BACKGROUND, SIDE_BAR_SECTION_HEADER_BORDER, PANEL_BACKGROUND, SIDE_BAR_BACKGROUND, PANEL_SECTION_HEADER_FOREGROUND, PANEL_SECTION_HEADER_BACKGROUND, PANEL_SECTION_HEADER_BORDER, PANEL_SECTION_DRAG_AND_DROP_BACKGROUND, PANEL_SECTION_BORDER } from 'vs/workbench/common/theme';
import { after, append, $, trackFocus, EventType, isAncestor, Dimension, addDisposableListener, createCSSRule, asCSSUrl } from 'vs/base/browser/dom';
import { IDisposable, combinedDisposable, dispose, toDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecycle';
import { IAction, Separator, IActionViewItem } from 'vs/base/common/actions';
import { ActionsOrientation, prepareActions } from 'vs/base/browser/ui/actionbar/actionbar';
import { Registry } from 'vs/platform/registry/common/platform';
import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { ColorIdentifier, activeContrastBorder } from 'vs/platform/theme/common/colorRegistry';
import { attachStyler, IColorMapping } from 'vs/platform/theme/common/styler';
import { SIDE_BAR_DRAG_AND_DROP_BACKGROUND, SIDE_BAR_SECTION_HEADER_FOREGROUND, SIDE_BAR_SECTION_HEADER_BACKGROUND, SIDE_BAR_SECTION_HEADER_BORDER, PANEL_SECTION_HEADER_FOREGROUND, PANEL_SECTION_HEADER_BACKGROUND, PANEL_SECTION_HEADER_BORDER, PANEL_SECTION_DRAG_AND_DROP_BACKGROUND, PANEL_SECTION_BORDER } from 'vs/workbench/common/theme';
import { EventType, Dimension, addDisposableListener, isAncestor } from 'vs/base/browser/dom';
import { IDisposable, combinedDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle';
import { IAction, IActionViewItem, Separator } from 'vs/base/common/actions';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IThemeService, Themable, ThemeIcon } from 'vs/platform/theme/common/themeService';
import { PaneView, IPaneViewOptions, IPaneOptions, Pane, IPaneStyles } from 'vs/base/browser/ui/splitview/paneview';
import { IThemeService, Themable } from 'vs/platform/theme/common/themeService';
import { PaneView, IPaneViewOptions } from 'vs/base/browser/ui/splitview/paneview';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IWorkbenchLayoutService, Position } from 'vs/workbench/services/layout/browser/layoutService';
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
import { Extensions as ViewContainerExtensions, IView, FocusedViewContext, IViewDescriptor, ViewContainer, IViewDescriptorService, ViewContainerLocation, IViewPaneContainer, IViewsRegistry, IViewContentDescriptor, IAddedViewDescriptorRef, IViewDescriptorRef, IViewContainerModel, defaultViewIcon } from 'vs/workbench/common/views';
import { IView, FocusedViewContext, IViewDescriptor, ViewContainer, IViewDescriptorService, ViewContainerLocation, IViewPaneContainer, IAddedViewDescriptorRef, IViewDescriptorRef, IViewContainerModel, IViewsService, ViewContainerLocationToString } from 'vs/workbench/common/views';
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { ContextKeyEqualsExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { assertIsDefined } from 'vs/base/common/types';
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { Component } from 'vs/workbench/common/component';
import { MenuId, MenuItemAction, registerAction2, Action2, IAction2Options, SubmenuItemAction } from 'vs/platform/actions/common/actions';
import { MenuEntryActionViewItem, SubmenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem';
import { ViewMenuActions } from 'vs/workbench/browser/parts/views/viewMenuActions';
import { parseLinkedText } from 'vs/base/common/linkedText';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { Button } from 'vs/base/browser/ui/button/button';
import { Link } from 'vs/platform/opener/browser/link';
import { registerAction2, Action2, IAction2Options, IMenuService, MenuId, MenuRegistry, ISubmenuItem, SubmenuItemAction } from 'vs/platform/actions/common/actions';
import { CompositeDragAndDropObserver, DragAndDropObserver, toggleDropEffect } from 'vs/workbench/browser/dnd';
import { Orientation } from 'vs/base/browser/ui/sash/sash';
import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar';
import { CompositeProgressIndicator } from 'vs/workbench/services/progress/browser/progressIndicator';
import { IProgressIndicator } from 'vs/platform/progress/common/progress';
import { RunOnceScheduler } from 'vs/base/common/async';
import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
import { ScrollbarVisibility } from 'vs/base/common/scrollable';
import { URI } from 'vs/base/common/uri';
import { KeyMod, KeyCode, KeyChord } from 'vs/base/common/keyCodes';
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { registerIcon } from 'vs/platform/theme/common/iconRegistry';
import { Codicon } from 'vs/base/common/codicons';
import { ViewPane } from 'vs/workbench/browser/parts/views/viewPane';
import { CompositeMenuActions } from 'vs/workbench/browser/menuActions';
import { createActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem';
export const ViewsSubMenu = new MenuId('Views');
MenuRegistry.appendMenuItem(MenuId.ViewContainerTitle, <ISubmenuItem>{
submenu: ViewsSubMenu,
title: nls.localize('views', "Views"),
order: 1,
when: ContextKeyEqualsExpr.create('viewContainerLocation', ViewContainerLocationToString(ViewContainerLocation.Sidebar)),
});
export interface IPaneColors extends IColorMapping {
dropBackground?: ColorIdentifier;
@@ -61,566 +54,6 @@ export interface IPaneColors extends IColorMapping {
leftBorder?: ColorIdentifier;
}
export interface IViewPaneOptions extends IPaneOptions {
id: string;
showActionsAlways?: boolean;
titleMenuId?: MenuId;
}
type WelcomeActionClassification = {
viewId: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
uri: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
};
const viewPaneContainerExpandedIcon = registerIcon('view-pane-container-expanded', Codicon.chevronDown, nls.localize('viewPaneContainerExpandedIcon', 'Icon for an expanded view pane container.'));
const viewPaneContainerCollapsedIcon = registerIcon('view-pane-container-collapsed', Codicon.chevronRight, nls.localize('viewPaneContainerCollapsedIcon', 'Icon for a collapsed view pane container.'));
const viewsRegistry = Registry.as<IViewsRegistry>(ViewContainerExtensions.ViewsRegistry);
interface IItem {
readonly descriptor: IViewContentDescriptor;
visible: boolean;
}
class ViewWelcomeController {
private _onDidChange = new Emitter<void>();
readonly onDidChange = this._onDidChange.event;
private defaultItem: IItem | undefined;
private items: IItem[] = [];
get contents(): IViewContentDescriptor[] {
const visibleItems = this.items.filter(v => v.visible);
if (visibleItems.length === 0 && this.defaultItem) {
return [this.defaultItem.descriptor];
}
return visibleItems.map(v => v.descriptor);
}
private contextKeyService: IContextKeyService;
private disposables = new DisposableStore();
constructor(
private id: string,
@IContextKeyService contextKeyService: IContextKeyService,
) {
this.contextKeyService = contextKeyService.createScoped();
this.disposables.add(this.contextKeyService);
contextKeyService.onDidChangeContext(this.onDidChangeContext, this, this.disposables);
Event.filter(viewsRegistry.onDidChangeViewWelcomeContent, id => id === this.id)(this.onDidChangeViewWelcomeContent, this, this.disposables);
this.onDidChangeViewWelcomeContent();
}
private onDidChangeViewWelcomeContent(): void {
const descriptors = viewsRegistry.getViewWelcomeContent(this.id);
this.items = [];
for (const descriptor of descriptors) {
if (descriptor.when === 'default') {
this.defaultItem = { descriptor, visible: true };
} else {
const visible = descriptor.when ? this.contextKeyService.contextMatchesRules(descriptor.when) : true;
this.items.push({ descriptor, visible });
}
}
this._onDidChange.fire();
}
private onDidChangeContext(): void {
let didChange = false;
for (const item of this.items) {
if (!item.descriptor.when || item.descriptor.when === 'default') {
continue;
}
const visible = this.contextKeyService.contextMatchesRules(item.descriptor.when);
if (item.visible === visible) {
continue;
}
item.visible = visible;
didChange = true;
}
if (didChange) {
this._onDidChange.fire();
}
}
dispose(): void {
this.disposables.dispose();
}
}
export abstract class ViewPane extends Pane implements IView {
private static readonly AlwaysShowActionsConfig = 'workbench.view.alwaysShowHeaderActions';
private _onDidFocus = this._register(new Emitter<void>());
readonly onDidFocus: Event<void> = this._onDidFocus.event;
private _onDidBlur = this._register(new Emitter<void>());
readonly onDidBlur: Event<void> = this._onDidBlur.event;
private _onDidChangeBodyVisibility = this._register(new Emitter<boolean>());
readonly onDidChangeBodyVisibility: Event<boolean> = this._onDidChangeBodyVisibility.event;
protected _onDidChangeTitleArea = this._register(new Emitter<void>());
readonly onDidChangeTitleArea: Event<void> = this._onDidChangeTitleArea.event;
protected _onDidChangeViewWelcomeState = this._register(new Emitter<void>());
readonly onDidChangeViewWelcomeState: Event<void> = this._onDidChangeViewWelcomeState.event;
private focusedViewContextKey: IContextKey<string>;
private _isVisible: boolean = false;
readonly id: string;
private _title: string;
public get title(): string {
return this._title;
}
private _titleDescription: string | undefined;
public get titleDescription(): string | undefined {
return this._titleDescription;
}
private readonly menuActions: ViewMenuActions;
private progressBar!: ProgressBar;
private progressIndicator!: IProgressIndicator;
private toolbar?: ToolBar;
private readonly showActionsAlways: boolean = false;
private headerContainer?: HTMLElement;
private titleContainer?: HTMLElement;
private titleDescriptionContainer?: HTMLElement;
private iconContainer?: HTMLElement;
protected twistiesContainer?: HTMLElement;
private bodyContainer!: HTMLElement;
private viewWelcomeContainer!: HTMLElement;
private viewWelcomeDisposable: IDisposable = Disposable.None;
private viewWelcomeController: ViewWelcomeController;
constructor(
options: IViewPaneOptions,
@IKeybindingService protected keybindingService: IKeybindingService,
@IContextMenuService protected contextMenuService: IContextMenuService,
@IConfigurationService protected readonly configurationService: IConfigurationService,
@IContextKeyService protected contextKeyService: IContextKeyService,
@IViewDescriptorService protected viewDescriptorService: IViewDescriptorService,
@IInstantiationService protected instantiationService: IInstantiationService,
@IOpenerService protected openerService: IOpenerService,
@IThemeService protected themeService: IThemeService,
@ITelemetryService protected telemetryService: ITelemetryService,
) {
super({ ...options, ...{ orientation: viewDescriptorService.getViewLocationById(options.id) === ViewContainerLocation.Panel ? Orientation.HORIZONTAL : Orientation.VERTICAL } });
this.id = options.id;
this._title = options.title;
this._titleDescription = options.titleDescription;
this.showActionsAlways = !!options.showActionsAlways;
this.focusedViewContextKey = FocusedViewContext.bindTo(contextKeyService);
this.menuActions = this._register(instantiationService.createInstance(ViewMenuActions, this.id, options.titleMenuId || MenuId.ViewTitle, MenuId.ViewTitleContext));
this._register(this.menuActions.onDidChangeTitle(() => this.updateActions()));
this.viewWelcomeController = new ViewWelcomeController(this.id, contextKeyService);
}
get headerVisible(): boolean {
return super.headerVisible;
}
set headerVisible(visible: boolean) {
super.headerVisible = visible;
this.element.classList.toggle('merged-header', !visible);
}
setVisible(visible: boolean): void {
if (this._isVisible !== visible) {
this._isVisible = visible;
if (this.isExpanded()) {
this._onDidChangeBodyVisibility.fire(visible);
}
}
}
isVisible(): boolean {
return this._isVisible;
}
isBodyVisible(): boolean {
return this._isVisible && this.isExpanded();
}
setExpanded(expanded: boolean): boolean {
const changed = super.setExpanded(expanded);
if (changed) {
this._onDidChangeBodyVisibility.fire(expanded);
}
if (this.twistiesContainer) {
this.twistiesContainer.classList.remove(...ThemeIcon.asClassNameArray(this.getTwistyIcon(!expanded)));
this.twistiesContainer.classList.add(...ThemeIcon.asClassNameArray(this.getTwistyIcon(expanded)));
}
return changed;
}
render(): void {
super.render();
const focusTracker = trackFocus(this.element);
this._register(focusTracker);
this._register(focusTracker.onDidFocus(() => {
this.focusedViewContextKey.set(this.id);
this._onDidFocus.fire();
}));
this._register(focusTracker.onDidBlur(() => {
if (this.focusedViewContextKey.get() === this.id) {
this.focusedViewContextKey.reset();
}
this._onDidBlur.fire();
}));
}
protected renderHeader(container: HTMLElement): void {
this.headerContainer = container;
this.twistiesContainer = append(container, $(ThemeIcon.asCSSSelector(this.getTwistyIcon(this.isExpanded()))));
this.renderHeaderTitle(container, this.title);
const actions = append(container, $('.actions'));
actions.classList.toggle('show', this.showActionsAlways);
this.toolbar = new ToolBar(actions, this.contextMenuService, {
orientation: ActionsOrientation.HORIZONTAL,
actionViewItemProvider: action => this.getActionViewItem(action),
ariaLabel: nls.localize('viewToolbarAriaLabel', "{0} actions", this.title),
getKeyBinding: action => this.keybindingService.lookupKeybinding(action.id),
renderDropdownAsChildElement: true
});
this._register(this.toolbar);
this.setActions();
this._register(addDisposableListener(actions, EventType.CLICK, e => e.preventDefault()));
this._register(this.viewDescriptorService.getViewContainerModel(this.viewDescriptorService.getViewContainerByViewId(this.id)!)!.onDidChangeContainerInfo(({ title }) => {
this.updateTitle(this.title);
}));
const onDidRelevantConfigurationChange = Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration(ViewPane.AlwaysShowActionsConfig));
this._register(onDidRelevantConfigurationChange(this.updateActionsVisibility, this));
this.updateActionsVisibility();
}
protected getTwistyIcon(expanded: boolean): ThemeIcon {
return expanded ? viewPaneContainerExpandedIcon : viewPaneContainerCollapsedIcon;
}
style(styles: IPaneStyles): void {
super.style(styles);
const icon = this.getIcon();
if (this.iconContainer) {
const fgColor = styles.headerForeground || this.themeService.getColorTheme().getColor(foreground);
if (URI.isUri(icon)) {
// Apply background color to activity bar item provided with iconUrls
this.iconContainer.style.backgroundColor = fgColor ? fgColor.toString() : '';
this.iconContainer.style.color = '';
} else {
// Apply foreground color to activity bar items provided with codicons
this.iconContainer.style.color = fgColor ? fgColor.toString() : '';
this.iconContainer.style.backgroundColor = '';
}
}
}
private getIcon(): ThemeIcon | URI {
return this.viewDescriptorService.getViewDescriptorById(this.id)?.containerIcon || defaultViewIcon;
}
protected renderHeaderTitle(container: HTMLElement, title: string): void {
this.iconContainer = append(container, $('.icon', undefined));
const icon = this.getIcon();
let cssClass: string | undefined = undefined;
if (URI.isUri(icon)) {
cssClass = `view-${this.id.replace(/[\.\:]/g, '-')}`;
const iconClass = `.pane-header .icon.${cssClass}`;
createCSSRule(iconClass, `
mask: ${asCSSUrl(icon)} no-repeat 50% 50%;
mask-size: 24px;
-webkit-mask: ${asCSSUrl(icon)} no-repeat 50% 50%;
-webkit-mask-size: 16px;
`);
} else if (ThemeIcon.isThemeIcon(icon)) {
cssClass = ThemeIcon.asClassName(icon);
}
if (cssClass) {
this.iconContainer.classList.add(...cssClass.split(' '));
}
const calculatedTitle = this.calculateTitle(title);
this.titleContainer = append(container, $('h3.title', { title: calculatedTitle }, calculatedTitle));
if (this._titleDescription) {
this.setTitleDescription(this._titleDescription);
}
this.iconContainer.title = calculatedTitle;
this.iconContainer.setAttribute('aria-label', calculatedTitle);
}
protected updateTitle(title: string): void {
const calculatedTitle = this.calculateTitle(title);
if (this.titleContainer) {
this.titleContainer.textContent = calculatedTitle;
this.titleContainer.setAttribute('title', calculatedTitle);
}
if (this.iconContainer) {
this.iconContainer.title = calculatedTitle;
this.iconContainer.setAttribute('aria-label', calculatedTitle);
}
this._title = title;
this._onDidChangeTitleArea.fire();
}
private setTitleDescription(description: string | undefined) {
if (this.titleDescriptionContainer) {
this.titleDescriptionContainer.textContent = description ?? '';
this.titleDescriptionContainer.setAttribute('title', description ?? '');
}
else if (description && this.titleContainer) {
this.titleDescriptionContainer = after(this.titleContainer, $('span.description', { title: description }, description));
}
}
protected updateTitleDescription(description?: string | undefined): void {
this.setTitleDescription(description);
this._titleDescription = description;
this._onDidChangeTitleArea.fire();
}
private calculateTitle(title: string): string {
const viewContainer = this.viewDescriptorService.getViewContainerByViewId(this.id)!;
const model = this.viewDescriptorService.getViewContainerModel(viewContainer);
const viewDescriptor = this.viewDescriptorService.getViewDescriptorById(this.id);
const isDefault = this.viewDescriptorService.getDefaultContainerById(this.id) === viewContainer;
if (!isDefault && viewDescriptor?.containerTitle && model.title !== viewDescriptor.containerTitle) {
return `${viewDescriptor.containerTitle}: ${title}`;
}
return title;
}
private scrollableElement!: DomScrollableElement;
protected renderBody(container: HTMLElement): void {
this.bodyContainer = container;
const viewWelcomeContainer = append(container, $('.welcome-view'));
this.viewWelcomeContainer = $('.welcome-view-content', { tabIndex: 0 });
this.scrollableElement = this._register(new DomScrollableElement(this.viewWelcomeContainer, {
alwaysConsumeMouseWheel: true,
horizontal: ScrollbarVisibility.Hidden,
vertical: ScrollbarVisibility.Visible,
}));
append(viewWelcomeContainer, this.scrollableElement.getDomNode());
const onViewWelcomeChange = Event.any(this.viewWelcomeController.onDidChange, this.onDidChangeViewWelcomeState);
this._register(onViewWelcomeChange(this.updateViewWelcome, this));
this.updateViewWelcome();
}
protected layoutBody(height: number, width: number): void {
this.viewWelcomeContainer.style.height = `${height}px`;
this.viewWelcomeContainer.style.width = `${width}px`;
this.scrollableElement.scanDomNode();
}
getProgressIndicator() {
if (this.progressBar === undefined) {
// Progress bar
this.progressBar = this._register(new ProgressBar(this.element));
this._register(attachProgressBarStyler(this.progressBar, this.themeService));
this.progressBar.hide();
}
if (this.progressIndicator === undefined) {
this.progressIndicator = this.instantiationService.createInstance(CompositeProgressIndicator, assertIsDefined(this.progressBar), this.id, this.isBodyVisible());
}
return this.progressIndicator;
}
protected getProgressLocation(): string {
return this.viewDescriptorService.getViewContainerByViewId(this.id)!.id;
}
protected getBackgroundColor(): string {
return this.viewDescriptorService.getViewLocationById(this.id) === ViewContainerLocation.Panel ? PANEL_BACKGROUND : SIDE_BAR_BACKGROUND;
}
focus(): void {
if (this.shouldShowWelcome()) {
this.viewWelcomeContainer.focus();
} else if (this.element) {
this.element.focus();
this._onDidFocus.fire();
}
}
private setActions(): void {
if (this.toolbar) {
this.toolbar.setActions(prepareActions(this.getActions()), prepareActions(this.getSecondaryActions()));
this.toolbar.context = this.getActionsContext();
}
}
private updateActionsVisibility(): void {
if (!this.headerContainer) {
return;
}
const shouldAlwaysShowActions = this.configurationService.getValue<boolean>('workbench.view.alwaysShowHeaderActions');
this.headerContainer.classList.toggle('actions-always-visible', shouldAlwaysShowActions);
}
protected updateActions(): void {
this.setActions();
this._onDidChangeTitleArea.fire();
}
getActions(): IAction[] {
return this.menuActions.getPrimaryActions();
}
getSecondaryActions(): IAction[] {
return this.menuActions.getSecondaryActions();
}
getContextMenuActions(): IAction[] {
return this.menuActions.getContextMenuActions();
}
getActionViewItem(action: IAction): IActionViewItem | undefined {
if (action instanceof MenuItemAction) {
return this.instantiationService.createInstance(MenuEntryActionViewItem, action);
} else if (action instanceof SubmenuItemAction) {
return this.instantiationService.createInstance(SubmenuEntryActionViewItem, action);
}
return undefined;
}
getActionsContext(): unknown {
return undefined;
}
getOptimalWidth(): number {
return 0;
}
saveState(): void {
// Subclasses to implement for saving state
}
private updateViewWelcome(): void {
this.viewWelcomeDisposable.dispose();
if (!this.shouldShowWelcome()) {
this.bodyContainer.classList.remove('welcome');
this.viewWelcomeContainer.innerText = '';
this.scrollableElement.scanDomNode();
return;
}
const contents = this.viewWelcomeController.contents;
if (contents.length === 0) {
this.bodyContainer.classList.remove('welcome');
this.viewWelcomeContainer.innerText = '';
this.scrollableElement.scanDomNode();
return;
}
const disposables = new DisposableStore();
this.bodyContainer.classList.add('welcome');
this.viewWelcomeContainer.innerText = '';
for (const { content, precondition } of contents) {
const lines = content.split('\n');
for (let line of lines) {
line = line.trim();
if (!line) {
continue;
}
const linkedText = parseLinkedText(line);
if (linkedText.nodes.length === 1 && typeof linkedText.nodes[0] !== 'string') {
const node = linkedText.nodes[0];
const button = new Button(this.viewWelcomeContainer, { title: node.title, supportCodicons: true });
button.label = node.label;
button.onDidClick(_ => {
this.telemetryService.publicLog2<{ viewId: string, uri: string }, WelcomeActionClassification>('views.welcomeAction', { viewId: this.id, uri: node.href });
this.openerService.open(node.href);
}, null, disposables);
disposables.add(button);
disposables.add(attachButtonStyler(button, this.themeService));
if (precondition) {
const updateEnablement = () => button.enabled = this.contextKeyService.contextMatchesRules(precondition);
updateEnablement();
const keys = new Set();
precondition.keys().forEach(key => keys.add(key));
const onDidChangeContext = Event.filter(this.contextKeyService.onDidChangeContext, e => e.affectsSome(keys));
onDidChangeContext(updateEnablement, null, disposables);
}
} else {
const p = append(this.viewWelcomeContainer, $('p'));
for (const node of linkedText.nodes) {
if (typeof node === 'string') {
append(p, document.createTextNode(node));
} else {
const link = this.instantiationService.createInstance(Link, node);
append(p, link.el);
disposables.add(link);
disposables.add(attachLinkStyler(link, this.themeService));
}
}
}
}
}
this.scrollableElement.scanDomNode();
this.viewWelcomeDisposable = disposables;
}
shouldShowWelcome(): boolean {
return false;
}
}
export interface IViewPaneContainerOptions extends IPaneViewOptions {
mergeViewWithContainerWhenSingleView: boolean;
}
@@ -860,6 +293,22 @@ class ViewPaneDropOverlay extends Themable {
}
}
class ViewContainerMenuActions extends CompositeMenuActions {
constructor(
viewContainer: ViewContainer,
@IViewDescriptorService viewDescriptorService: IViewDescriptorService,
@IContextKeyService contextKeyService: IContextKeyService,
@IMenuService menuService: IMenuService,
) {
const scopedContextKeyService = contextKeyService.createScoped();
scopedContextKeyService.createKey('viewContainer', viewContainer.id);
const viewContainerLocationKey = scopedContextKeyService.createKey('viewContainerLocation', ViewContainerLocationToString(viewDescriptorService.getViewContainerLocation(viewContainer)!));
super(MenuId.ViewContainerTitle, MenuId.ViewContainerTitleContext, { shouldForwardArgs: true }, scopedContextKeyService, menuService);
this._register(scopedContextKeyService);
this._register(Event.filter(viewDescriptorService.onDidChangeContainerLocation, e => e.viewContainer === viewContainer)(() => viewContainerLocationKey.set(ViewContainerLocationToString(viewDescriptorService.getViewContainerLocation(viewContainer)!))));
}
}
export class ViewPaneContainer extends Component implements IViewPaneContainer {
readonly viewContainer: ViewContainer;
@@ -910,6 +359,8 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
return this.paneItems.length;
}
private readonly menuActions: ViewContainerMenuActions;
constructor(
id: string,
private options: IViewPaneContainerOptions,
@@ -922,7 +373,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
@IThemeService protected themeService: IThemeService,
@IStorageService protected storageService: IStorageService,
@IWorkspaceContextService protected contextService: IWorkspaceContextService,
@IViewDescriptorService protected viewDescriptorService: IViewDescriptorService
@IViewDescriptorService protected viewDescriptorService: IViewDescriptorService,
) {
super(id, themeService, storageService);
@@ -938,6 +389,9 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
this.visibleViewsCountFromCache = this.storageService.getNumber(this.visibleViewsStorageId, StorageScope.WORKSPACE, undefined);
this._register(toDisposable(() => this.viewDisposables = dispose(this.viewDisposables)));
this.viewContainerModel = this.viewDescriptorService.getViewContainerModel(container);
this.menuActions = this._register(instantiationService.createInstance(ViewContainerMenuActions, container));
this._register(this.menuActions.onDidChange(() => this.updateTitleArea()));
}
create(parent: HTMLElement): void {
@@ -1110,57 +564,62 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
let anchor: { x: number, y: number; } = { x: event.posx, y: event.posy };
this.contextMenuService.showContextMenu({
getAnchor: () => anchor,
getActions: () => this.getContextMenuActions()
getActions: () => [...this.getContextMenuActions2()]
});
}
getContextMenuActions2(): ReadonlyArray<IAction> {
return this.menuActions.getContextMenuActions();
}
getContextMenuActions(viewDescriptor?: IViewDescriptor): IAction[] {
const result: IAction[] = [];
return [];
}
let showHide = true;
if (!viewDescriptor && this.isViewMergedWithContainer()) {
viewDescriptor = this.viewDescriptorService.getViewDescriptorById(this.panes[0].id) || undefined;
showHide = false;
getActions2(): IAction[] {
const result = [];
result.push(...this.menuActions.getPrimaryActions());
if (this.isViewMergedWithContainer()) {
result.push(...this.paneItems[0].pane.getActions());
}
if (viewDescriptor) {
if (showHide) {
result.push(<IAction>{
id: `${viewDescriptor.id}.removeView`,
label: nls.localize('hideView', "Hide"),
enabled: viewDescriptor.canToggleVisibility,
run: () => this.toggleViewVisibility(viewDescriptor!.id)
});
}
const view = this.getView(viewDescriptor.id);
if (view) {
result.push(...view.getContextMenuActions());
}
}
const viewToggleActions = this.getViewsVisibilityActions();
if (result.length && viewToggleActions.length) {
result.push(new Separator());
}
result.push(...viewToggleActions);
return result;
}
getActions(): IAction[] {
if (this.isViewMergedWithContainer()) {
return this.paneItems[0].pane.getActions();
}
return [];
}
getSecondaryActions(): IAction[] {
if (this.isViewMergedWithContainer()) {
return this.paneItems[0].pane.getSecondaryActions();
getSecondaryActions2(): IAction[] {
const viewPaneActions = this.isViewMergedWithContainer() ? this.paneItems[0].pane.getSecondaryActions() : [];
let menuActions = this.menuActions.getSecondaryActions();
const viewsSubmenuActionIndex = menuActions.findIndex(action => action instanceof SubmenuItemAction && action.item.submenu === ViewsSubMenu);
if (viewsSubmenuActionIndex !== -1) {
const viewsSubmenuAction = <SubmenuItemAction>menuActions[viewsSubmenuActionIndex];
if (viewsSubmenuAction.actions.some(({ enabled }) => enabled)) {
if (menuActions.length === 1 && viewPaneActions.length === 0) {
menuActions = viewsSubmenuAction.actions.slice();
} else if (viewsSubmenuActionIndex !== 0) {
menuActions = [viewsSubmenuAction, ...menuActions.slice(0, viewsSubmenuActionIndex), ...menuActions.slice(viewsSubmenuActionIndex + 1)];
}
} else {
// Remove views submenu if none of the actions are enabled
menuActions.splice(viewsSubmenuActionIndex, 1);
}
}
if (menuActions.length && viewPaneActions.length) {
return [
...menuActions,
new Separator(),
...viewPaneActions
];
}
return menuActions.length ? menuActions : viewPaneActions;
}
getSecondaryActions(): IAction[] {
return [];
}
@@ -1168,22 +627,11 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
return undefined;
}
getViewsVisibilityActions(): IAction[] {
return this.viewContainerModel.activeViewDescriptors.map(viewDescriptor => (<IAction>{
id: `${viewDescriptor.id}.toggleVisibility`,
label: viewDescriptor.name,
checked: this.viewContainerModel.isVisible(viewDescriptor.id),
enabled: viewDescriptor.canToggleVisibility && (!this.viewContainerModel.isVisible(viewDescriptor.id) || this.viewContainerModel.visibleViewDescriptors.length > 1),
run: () => this.toggleViewVisibility(viewDescriptor.id)
}));
}
getActionViewItem(action: IAction): IActionViewItem | undefined {
if (this.isViewMergedWithContainer()) {
return this.paneItems[0].pane.getActionViewItem(action);
}
return undefined;
return createActionViewItem(this.instantiationService, action);
}
focus(): void {
@@ -1321,11 +769,11 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
this.storageService.store(this.visibleViewsStorageId, this.length, StorageScope.WORKSPACE, StorageTarget.USER);
}
private onContextMenu(event: StandardMouseEvent, viewDescriptor: IViewDescriptor): void {
private onContextMenu(event: StandardMouseEvent, viewPane: ViewPane): void {
event.stopPropagation();
event.preventDefault();
const actions: IAction[] = this.getContextMenuActions(viewDescriptor);
const actions: IAction[] = viewPane.getContextMenuActions();
let anchor: { x: number, y: number } = { x: event.posx, y: event.posy };
this.contextMenuService.showContextMenu({
@@ -1364,7 +812,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
const contextMenuDisposable = addDisposableListener(pane.draggableElement, 'contextmenu', e => {
e.stopPropagation();
e.preventDefault();
this.onContextMenu(new StandardMouseEvent(e), viewDescriptor);
this.onContextMenu(new StandardMouseEvent(e), pane);
});
const collapseDisposable = Event.latch(Event.map(pane.onDidChange, () => !pane.isExpanded()))(collapsed => {
@@ -1401,7 +849,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
}
}
protected toggleViewVisibility(viewId: string): void {
toggleViewVisibility(viewId: string): void {
// Check if view is active
if (this.viewContainerModel.activeViewDescriptors.some(viewDescriptor => viewDescriptor.id === viewId)) {
const visible = !this.viewContainerModel.isVisible(viewId);
@@ -1641,7 +1089,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
}
}
private isViewMergedWithContainer(): boolean {
isViewMergedWithContainer(): boolean {
if (!(this.options.mergeViewWithContainerWhenSingleView && this.paneItems.length === 1)) {
return false;
}
@@ -1665,6 +1113,21 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
}
}
export abstract class ViewPaneContainerAction<T extends IViewPaneContainer> extends Action2 {
constructor(readonly desc: Readonly<IAction2Options> & { viewPaneContainerId: string }) {
super(desc);
}
run(accessor: ServicesAccessor, ...args: any[]) {
const viewPaneContainer = accessor.get(IViewsService).getActiveViewPaneContainerWithId(this.desc.viewPaneContainerId);
if (viewPaneContainer) {
return this.runInViewPaneContainer(accessor, <T>viewPaneContainer, ...args);
}
}
abstract runInViewPaneContainer(accessor: ServicesAccessor, viewPaneContainer: T, ...args: any[]): any;
}
class MoveViewPosition extends Action2 {
constructor(desc: Readonly<IAction2Options>, private readonly offset: number) {
super(desc);

View File

@@ -196,7 +196,9 @@ export class ViewsService extends Disposable implements IViewsService {
ContextKeyExpr.equals('view', viewDescriptor.id),
ContextKeyExpr.equals(`${viewDescriptor.id}.defaultViewLocation`, false)
)
)
),
group: '1_hide',
order: 2
}],
});
}
@@ -392,12 +394,29 @@ export class ViewsService extends Disposable implements IViewsService {
getViewProgressIndicator(viewId: string): IProgressIndicator | undefined {
const viewContainer = this.viewDescriptorService.getViewContainerByViewId(viewId);
if (viewContainer === null) {
if (!viewContainer) {
return undefined;
}
const view = this.viewPaneContainers.get(viewContainer.id)?.viewPaneContainer?.getView(viewId);
return view?.getProgressIndicator();
const viewPaneContainer = this.viewPaneContainers.get(viewContainer.id)?.viewPaneContainer;
if (!viewPaneContainer) {
return undefined;
}
const view = viewPaneContainer.getView(viewId);
if (!view) {
return undefined;
}
if (viewPaneContainer.isViewMergedWithContainer()) {
return this.getViewContainerProgressIndicator(viewContainer);
}
return view.getProgressIndicator();
}
private getViewContainerProgressIndicator(viewContainer: ViewContainer): IProgressIndicator | undefined {
return this.viewDescriptorService.getViewContainerLocation(viewContainer) === ViewContainerLocation.Sidebar ? this.viewletService.getProgressIndicator(viewContainer.id) : this.panelService.getProgressIndicator(viewContainer.id);
}
private registerViewletOrPanel(viewContainer: ViewContainer, viewContainerLocation: ViewContainerLocation): void {
@@ -451,7 +470,6 @@ export class ViewsService extends Disposable implements IViewsService {
undefined,
viewContainer.order,
viewContainer.requestedIndex,
viewContainer.focusCommand?.id,
));
}

View File

@@ -3,7 +3,6 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IAction } from 'vs/base/common/actions';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IViewDescriptor, IViewDescriptorService, IAddedViewDescriptorRef } from 'vs/workbench/common/views';
@@ -12,7 +11,8 @@ import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { ViewPaneContainer, ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer';
import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer';
import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPane';
import { Event } from 'vs/base/common/event';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
@@ -81,18 +81,6 @@ export abstract class FilterViewPaneContainer extends ViewPaneContainer {
this.getViewsForTarget(newFilterValue).forEach(item => this.viewContainerModel.setVisible(item.id, true));
}
getContextMenuActions(): IAction[] {
const result: IAction[] = Array.from(this.constantViewDescriptors.values()).map(viewDescriptor => (<IAction>{
id: `${viewDescriptor.id}.toggleVisibility`,
label: viewDescriptor.name,
checked: this.viewContainerModel.isVisible(viewDescriptor.id),
enabled: viewDescriptor.canToggleVisibility,
run: () => this.toggleViewVisibility(viewDescriptor.id)
}));
return result;
}
private getViewsForTarget(target: string[]): IViewDescriptor[] {
const views: IViewDescriptor[] = [];
for (let i = 0; i < target.length; i++) {
@@ -140,7 +128,4 @@ export abstract class FilterViewPaneContainer extends ViewPaneContainer {
abstract getTitle(): string;
getViewsVisibilityActions(): IAction[] {
return [];
}
}

View File

@@ -5,7 +5,7 @@
import 'vs/css!./media/style';
import { registerThemingParticipant, IColorTheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService';
import { registerThemingParticipant } from 'vs/platform/theme/common/themeService';
import { iconForeground, foreground, selectionBackground, focusBorder, scrollbarShadow, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground, listHighlightForeground, inputPlaceholderForeground } from 'vs/platform/theme/common/colorRegistry';
import { WORKBENCH_BACKGROUND, TITLE_BAR_ACTIVE_BACKGROUND } from 'vs/workbench/common/theme';
import { isWeb, isIOS, isMacintosh, isWindows } from 'vs/base/common/platform';
@@ -13,7 +13,7 @@ import { createMetaElement } from 'vs/base/browser/dom';
import { isSafari, isStandalone } from 'vs/base/browser/browser';
import { ColorScheme } from 'vs/platform/theme/common/theme';
registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => {
registerThemingParticipant((theme, collector) => {
// Foreground
const windowForeground = theme.getColor(foreground);

View File

@@ -3,23 +3,19 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
import * as DOM from 'vs/base/browser/dom';
import { Registry } from 'vs/platform/registry/common/platform';
import { Action, IAction, Separator, SubmenuAction } from 'vs/base/common/actions';
import { Action } from 'vs/base/common/actions';
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
import { IViewlet } from 'vs/workbench/common/viewlet';
import { CompositeDescriptor, CompositeRegistry } from 'vs/workbench/browser/composite';
import { IConstructorSignature0, IInstantiationService, BrandedService } from 'vs/platform/instantiation/common/instantiation';
import { ToggleSidebarVisibilityAction, ToggleSidebarPositionAction } from 'vs/workbench/browser/actions/layoutActions';
import { IConstructorSignature0, IInstantiationService, BrandedService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { URI } from 'vs/base/common/uri';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { AsyncDataTree } from 'vs/base/browser/ui/tree/asyncDataTree';
import { AbstractTree } from 'vs/base/browser/ui/tree/abstractTree';
import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
@@ -28,6 +24,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
import { PaneComposite } from 'vs/workbench/browser/panecomposite';
import { Event } from 'vs/base/common/event';
import { FilterViewPaneContainer } from 'vs/workbench/browser/parts/views/viewsViewlet';
import { Action2 } from 'vs/platform/actions/common/actions';
export abstract class Viewlet extends PaneComposite implements IViewlet {
@@ -52,40 +49,6 @@ export abstract class Viewlet extends PaneComposite implements IViewlet {
}));
}
}
getContextMenuActions(): IAction[] {
const parentActions = [...super.getContextMenuActions()];
if (parentActions.length) {
parentActions.push(new Separator());
}
const toggleSidebarPositionAction = new ToggleSidebarPositionAction(ToggleSidebarPositionAction.ID, ToggleSidebarPositionAction.getLabel(this.layoutService), this.layoutService, this.configurationService);
return [...parentActions, toggleSidebarPositionAction,
<IAction>{
id: ToggleSidebarVisibilityAction.ID,
label: nls.localize('compositePart.hideSideBarLabel', "Hide Side Bar"),
enabled: true,
run: () => this.layoutService.setSideBarHidden(true)
}];
}
getSecondaryActions(): IAction[] {
const viewVisibilityActions = this.viewPaneContainer.getViewsVisibilityActions();
const secondaryActions = this.viewPaneContainer.getSecondaryActions();
if (viewVisibilityActions.length <= 1 || viewVisibilityActions.every(({ enabled }) => !enabled)) {
return secondaryActions;
}
if (secondaryActions.length === 0) {
return viewVisibilityActions;
}
return [
new SubmenuAction('workbench.views', nls.localize('views', "Views"), viewVisibilityActions),
new Separator(),
...secondaryActions
];
}
}
/**
@@ -115,7 +78,7 @@ export class ViewletDescriptor extends CompositeDescriptor<Viewlet> {
requestedIndex?: number,
readonly iconUrl?: URI
) {
super(ctor, id, name, cssClass, order, requestedIndex, id);
super(ctor, id, name, cssClass, order, requestedIndex);
}
}
@@ -152,7 +115,6 @@ export class ViewletRegistry extends CompositeRegistry<Viewlet> {
getViewlets(): ViewletDescriptor[] {
return this.getComposites() as ViewletDescriptor[];
}
}
Registry.add(Extensions.Viewlets, new ViewletRegistry());
@@ -176,7 +138,7 @@ export class ShowViewletAction extends Action {
async run(): Promise<void> {
// Pass focus to viewlet if not open or focused
if (this.otherViewletShowing() || !this.sidebarHasFocus()) {
if (otherViewletShowing(this.viewletService, this.viewletId) || !sidebarHasFocus(this.viewletService, this.layoutService)) {
await this.viewletService.openViewlet(this.viewletId, true);
return;
}
@@ -184,28 +146,42 @@ export class ShowViewletAction extends Action {
// Otherwise pass focus to editor group
this.editorGroupService.activeGroup.focus();
}
}
private otherViewletShowing(): boolean {
const activeViewlet = this.viewletService.getActiveViewlet();
/**
* A reusable action to show a viewlet with a specific id.
*/
export abstract class ShowViewletAction2 extends Action2 {
/**
* Gets the viewlet ID to show.
*/
protected abstract viewletId(): string;
return !activeViewlet || activeViewlet.getId() !== this.viewletId;
}
public async run(accessor: ServicesAccessor): Promise<void> {
const viewletService = accessor.get(IViewletService);
const editorGroupService = accessor.get(IEditorGroupsService);
const layoutService = accessor.get(IWorkbenchLayoutService);
private sidebarHasFocus(): boolean {
const activeViewlet = this.viewletService.getActiveViewlet();
const activeElement = document.activeElement;
const sidebarPart = this.layoutService.getContainer(Parts.SIDEBAR_PART);
// Pass focus to viewlet if not open or focused
if (otherViewletShowing(viewletService, this.viewletId()) || !sidebarHasFocus(viewletService, layoutService)) {
await viewletService.openViewlet(this.viewletId(), true);
return;
}
return !!(activeViewlet && activeElement && sidebarPart && DOM.isAncestor(activeElement, sidebarPart));
// Otherwise pass focus to editor group
editorGroupService.activeGroup.focus();
}
}
export class CollapseAction extends Action {
// We need a tree getter because the action is sometimes instantiated too early
constructor(treeGetter: () => AsyncDataTree<any, any, any> | AbstractTree<any, any, any>, enabled: boolean, clazz?: string) {
super('workbench.action.collapse', nls.localize('collapse', "Collapse All"), clazz, enabled, async () => {
const tree = treeGetter();
tree.collapseAll();
});
}
}
const otherViewletShowing = (viewletService: IViewletService, viewletId: string): boolean => {
const activeViewlet = viewletService.getActiveViewlet();
return !activeViewlet || activeViewlet.getId() !== viewletId;
};
const sidebarHasFocus = (viewletService: IViewletService, layoutService: IWorkbenchLayoutService): boolean => {
const activeViewlet = viewletService.getActiveViewlet();
const activeElement = document.activeElement;
const sidebarPart = layoutService.getContainer(Parts.SIDEBAR_PART);
return !!(activeViewlet && activeElement && sidebarPart && DOM.isAncestor(activeElement, sidebarPart));
};

View File

@@ -4,8 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { mark } from 'vs/base/common/performance';
import { hash } from 'vs/base/common/hash';
import { domContentLoaded, addDisposableListener, EventType, EventHelper, detectFullscreen, addDisposableThrottledListener } from 'vs/base/browser/dom';
import { domContentLoaded, detectFullscreen, getCookieValue } from 'vs/base/browser/dom';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { ILogService, ConsoleLogService, MultiplexLogService, getLogLevel } from 'vs/platform/log/common/log';
import { ConsoleLogInAutomationService } from 'vs/platform/log/browser/log';
@@ -24,10 +23,9 @@ import { IFileService, IFileSystemProvider } from 'vs/platform/files/common/file
import { FileService } from 'vs/platform/files/common/fileService';
import { Schemas } from 'vs/base/common/network';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IWorkbenchConfigurationService } from 'vs/workbench/services/configuration/common/configuration';
import { onUnexpectedError } from 'vs/base/common/errors';
import { setFullscreen } from 'vs/base/browser/browser';
import { isIOS, isMacintosh } from 'vs/base/common/platform';
import { URI } from 'vs/base/common/uri';
import { IWorkspaceInitializationPayload } from 'vs/platform/workspaces/common/workspaces';
import { WorkspaceService } from 'vs/workbench/services/configuration/browser/configurationService';
@@ -37,13 +35,16 @@ import { SignService } from 'vs/platform/sign/browser/signService';
import type { IWorkbenchConstructionOptions, IWorkspace, IWorkbench } from 'vs/workbench/workbench.web.api';
import { BrowserStorageService } from 'vs/platform/storage/browser/storageService';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { registerWindowDriver } from 'vs/platform/driver/browser/driver';
import { BufferLogService } from 'vs/platform/log/common/bufferLog';
import { FileLogService } from 'vs/platform/log/common/fileLogService';
import { toLocalISOString } from 'vs/base/common/date';
import { isWorkspaceToOpen, isFolderToOpen } from 'vs/platform/windows/common/windows';
<<<<<<< HEAD
import { getWorkspaceIdentifier } from 'vs/workbench/services/workspaces/browser/workspaces';
import { initialize } from 'vs/server/browser/client';
=======
import { getSingleFolderWorkspaceIdentifier, getWorkspaceIdentifier } from 'vs/workbench/services/workspaces/browser/workspaces';
>>>>>>> 89b6e0164fa770333755b11504e19a4232b1a2d4
import { coalesce } from 'vs/base/common/arrays';
import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFilesystemProvider';
import { ICommandService } from 'vs/platform/commands/common/commands';
@@ -62,6 +63,8 @@ import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { IHostService } from 'vs/workbench/services/host/browser/host';
import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity';
import { UriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentityService';
import { BrowserWindow } from 'vs/workbench/browser/window';
import { ITimerService } from 'vs/workbench/services/timer/browser/timerService';
class BrowserMain extends Disposable {
@@ -84,37 +87,44 @@ class BrowserMain extends Disposable {
const services = await this.initServices();
await domContentLoaded();
mark('willStartWorkbench');
mark('code/willStartWorkbench');
// Create Workbench
const workbench = new Workbench(
this.domElement,
services.serviceCollection,
services.logService
);
const workbench = new Workbench(this.domElement, services.serviceCollection, services.logService);
// Listeners
this.registerListeners(workbench, services.storageService, services.logService);
// Driver
if (this.configuration.driver) {
(async () => this._register(await registerWindowDriver()))();
}
// Startup
const instantiationService = workbench.startup();
<<<<<<< HEAD
await initialize(services.serviceCollection);
=======
// Window
this._register(instantiationService.createInstance(BrowserWindow));
// Logging
services.logService.trace('workbench configuration', JSON.stringify(this.configuration));
>>>>>>> 89b6e0164fa770333755b11504e19a4232b1a2d4
// Return API Facade
return instantiationService.invokeFunction(accessor => {
const commandService = accessor.get(ICommandService);
const lifecycleService = accessor.get(ILifecycleService);
const timerService = accessor.get(ITimerService);
return {
commands: {
executeCommand: (command, ...args) => commandService.executeCommand(command, ...args)
},
env: {
async retrievePerformanceMarks() {
await timerService.whenReady();
return timerService.getPerformanceMarks();
}
},
shutdown: () => lifecycleService.shutdown()
};
});
@@ -122,46 +132,17 @@ class BrowserMain extends Disposable {
private registerListeners(workbench: Workbench, storageService: BrowserStorageService, logService: ILogService): void {
// Layout
const viewport = isIOS && window.visualViewport ? window.visualViewport /** Visual viewport */ : window /** Layout viewport */;
this._register(addDisposableListener(viewport, EventType.RESIZE, () => {
logService.trace(`web.main#${isIOS && window.visualViewport ? 'visualViewport' : 'window'}Resize`);
workbench.layout();
}));
// Prevent the back/forward gestures in macOS
this._register(addDisposableListener(this.domElement, EventType.WHEEL, e => e.preventDefault(), { passive: false }));
// Prevent native context menus in web
this._register(addDisposableListener(this.domElement, EventType.CONTEXT_MENU, e => EventHelper.stop(e, true)));
// Prevent default navigation on drop
this._register(addDisposableListener(this.domElement, EventType.DROP, e => EventHelper.stop(e, true)));
// Workbench Lifecycle
this._register(workbench.onBeforeShutdown(event => {
if (storageService.hasPendingUpdate) {
logService.warn('Unload veto: pending storage update');
event.veto(true); // prevent data loss from pending storage update
event.veto(true, 'veto.pendingStorageUpdate'); // prevent data loss from pending storage update
}
}));
this._register(workbench.onWillShutdown(() => {
storageService.close();
}));
this._register(workbench.onWillShutdown(() => storageService.close()));
this._register(workbench.onShutdown(() => this.dispose()));
// Fullscreen (Browser)
[EventType.FULLSCREEN_CHANGE, EventType.WK_FULLSCREEN_CHANGE].forEach(event => {
this._register(addDisposableListener(document, event, () => setFullscreen(!!detectFullscreen())));
});
// Fullscreen (Native)
this._register(addDisposableThrottledListener(viewport, EventType.RESIZE, () => {
setFullscreen(!!detectFullscreen());
}, undefined, isMacintosh ? 2000 /* adjust for macOS animation */ : 800 /* can be throttled */));
}
private async initServices(): Promise<{ serviceCollection: ServiceCollection, configurationService: IConfigurationService, logService: ILogService, storageService: BrowserStorageService }> {
private async initServices(): Promise<{ serviceCollection: ServiceCollection, configurationService: IWorkbenchConfigurationService, logService: ILogService, storageService: BrowserStorageService }> {
const serviceCollection = new ServiceCollection();
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
@@ -169,7 +150,7 @@ class BrowserMain extends Disposable {
// CONTRIBUTE IT VIA WORKBENCH.WEB.MAIN.TS AND registerSingleton().
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
const payload = await this.resolveWorkspaceInitializationPayload();
const payload = this.resolveWorkspaceInitializationPayload();
// Product
const productService: IProductService = { _serviceBrand: undefined, ...product, ...this.configuration.productConfiguration };
@@ -184,9 +165,8 @@ class BrowserMain extends Disposable {
const logService = new BufferLogService(getLogLevel(environmentService));
serviceCollection.set(ILogService, logService);
const connectionToken = environmentService.options.connectionToken || this.getCookieValue('vscode-tkn');
// Remote
const connectionToken = environmentService.options.connectionToken || getCookieValue('vscode-tkn');
const remoteAuthorityResolverService = new RemoteAuthorityResolverService(connectionToken, this.configuration.resourceUriProvider);
serviceCollection.set(IRemoteAuthorityResolverService, remoteAuthorityResolverService);
@@ -215,7 +195,7 @@ class BrowserMain extends Disposable {
serviceCollection.set(IWorkspaceContextService, service);
// Configuration
serviceCollection.set(IConfigurationService, service);
serviceCollection.set(IWorkbenchConfigurationService, service);
return service;
}),
@@ -242,14 +222,16 @@ class BrowserMain extends Disposable {
serviceCollection.set(IUserDataInitializationService, userDataInitializationService);
if (await userDataInitializationService.requiresInitialization()) {
mark('willInitRequiredUserData');
mark('code/willInitRequiredUserData');
// Initialize required resources - settings & global state
await userDataInitializationService.initializeRequiredResources();
// Important: Reload only local user configuration after initializing
// Reloading complete configuraiton blocks workbench until remote configuration is loaded.
await configurationService.reloadLocalUserConfiguration();
mark('didInitRequiredUserData');
mark('code/didInitRequiredUserData');
}
return { serviceCollection, configurationService, logService, storageService };
@@ -264,8 +246,9 @@ class BrowserMain extends Disposable {
try {
indexedDBLogProvider = await indexedDB.createFileSystemProvider(logsPath.scheme, INDEXEDDB_LOGS_OBJECT_STORE);
} catch (error) {
console.error(error);
onUnexpectedError(error);
}
if (indexedDBLogProvider) {
fileService.registerProvider(logsPath.scheme, indexedDBLogProvider);
} else {
@@ -282,6 +265,7 @@ class BrowserMain extends Disposable {
const connection = remoteAgentService.getConnection();
if (connection) {
// Remote file system
const remoteFileSystemProvider = this._register(new RemoteFileSystemProvider(remoteAgentService));
fileService.registerProvider(Schemas.vscodeRemote, remoteFileSystemProvider);
@@ -292,8 +276,9 @@ class BrowserMain extends Disposable {
try {
indexedDBUserDataProvider = await indexedDB.createFileSystemProvider(Schemas.userData, INDEXEDDB_USERDATA_OBJECT_STORE);
} catch (error) {
console.error(error);
onUnexpectedError(error);
}
fileService.registerProvider(Schemas.userData, indexedDBUserDataProvider || new InMemoryFileSystemProvider());
if (indexedDBUserDataProvider) {
registerAction2(class ResetUserDataAction extends Action2 {
@@ -307,21 +292,22 @@ class BrowserMain extends Disposable {
}
});
}
async run(accessor: ServicesAccessor): Promise<void> {
const dialogService = accessor.get(IDialogService);
const hostService = accessor.get(IHostService);
const result = await dialogService.confirm({
message: localize('reset user data message', "Would you like to reset your data (settings, keybindings, extensions, snippets and UI State) and reload?")
});
if (result.confirmed) {
await indexedDBUserDataProvider!.reset();
await indexedDBUserDataProvider?.reset();
}
hostService.reload();
}
});
}
fileService.registerProvider(Schemas.userData, indexedDBUserDataProvider || new InMemoryFileSystemProvider());
}
private async createStorageService(payload: IWorkspaceInitializationPayload, environmentService: IWorkbenchEnvironmentService, fileService: IFileService, logService: ILogService): Promise<BrowserStorageService> {
@@ -354,7 +340,7 @@ class BrowserMain extends Disposable {
}
}
private async resolveWorkspaceInitializationPayload(): Promise<IWorkspaceInitializationPayload> {
private resolveWorkspaceInitializationPayload(): IWorkspaceInitializationPayload {
let workspace: IWorkspace | undefined = undefined;
if (this.configuration.workspaceProvider) {
workspace = this.configuration.workspaceProvider.workspace;
@@ -367,18 +353,11 @@ class BrowserMain extends Disposable {
// Single-folder workspace
if (workspace && isFolderToOpen(workspace)) {
const id = hash(workspace.folderUri.toString()).toString(16);
return { id, folder: workspace.folderUri };
return getSingleFolderWorkspaceIdentifier(workspace.folderUri);
}
return { id: 'empty-window' };
}
private getCookieValue(name: string): string | undefined {
const match = document.cookie.match('(^|[^;]+)\\s*' + name + '\\s*=\\s*([^;]+)'); // See https://stackoverflow.com/a/25490531
return match ? match.pop() : undefined;
}
}
export function main(domElement: HTMLElement, options: IWorkbenchConstructionOptions): Promise<IWorkbench> {

View File

@@ -0,0 +1,161 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { setFullscreen } from 'vs/base/browser/browser';
import { addDisposableListener, addDisposableThrottledListener, detectFullscreen, EventHelper, EventType, windowOpenNoOpener } from 'vs/base/browser/dom';
import { domEvent } from 'vs/base/browser/event';
import { timeout } from 'vs/base/common/async';
import { Event } from 'vs/base/common/event';
import { Disposable } from 'vs/base/common/lifecycle';
import { Schemas } from 'vs/base/common/network';
import { isIOS, isMacintosh } from 'vs/base/common/platform';
import Severity from 'vs/base/common/severity';
import { localize } from 'vs/nls';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { registerWindowDriver } from 'vs/platform/driver/browser/driver';
import { ILabelService } from 'vs/platform/label/common/label';
import { ILogService } from 'vs/platform/log/common/log';
import { IOpenerService, matchesScheme } from 'vs/platform/opener/common/opener';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { IHostService } from 'vs/workbench/services/host/browser/host';
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
import { BrowserLifecycleService } from 'vs/workbench/services/lifecycle/browser/lifecycleService';
import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle';
export class BrowserWindow extends Disposable {
constructor(
@IOpenerService private readonly openerService: IOpenerService,
@ILifecycleService private readonly lifecycleService: BrowserLifecycleService,
@IDialogService private readonly dialogService: IDialogService,
@IHostService private readonly hostService: IHostService,
@ILabelService private readonly labelService: ILabelService,
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService,
@ILogService private readonly logService: ILogService,
@IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService
) {
super();
this.registerListeners();
this.create();
}
private registerListeners(): void {
// Lifecycle
this._register(this.lifecycleService.onWillShutdown(() => this.onWillShutdown()));
// Layout
const viewport = isIOS && window.visualViewport ? window.visualViewport /** Visual viewport */ : window /** Layout viewport */;
this._register(addDisposableListener(viewport, EventType.RESIZE, () => this.onWindowResize()));
// Prevent the back/forward gestures in macOS
this._register(addDisposableListener(this.layoutService.getWorkbenchContainer(), EventType.WHEEL, e => e.preventDefault(), { passive: false }));
// Prevent native context menus in web
this._register(addDisposableListener(this.layoutService.getWorkbenchContainer(), EventType.CONTEXT_MENU, e => EventHelper.stop(e, true)));
// Prevent default navigation on drop
this._register(addDisposableListener(this.layoutService.getWorkbenchContainer(), EventType.DROP, e => EventHelper.stop(e, true)));
// Fullscreen (Browser)
[EventType.FULLSCREEN_CHANGE, EventType.WK_FULLSCREEN_CHANGE].forEach(event => {
this._register(addDisposableListener(document, event, () => setFullscreen(!!detectFullscreen())));
});
// Fullscreen (Native)
this._register(addDisposableThrottledListener(viewport, EventType.RESIZE, () => {
setFullscreen(!!detectFullscreen());
}, undefined, isMacintosh ? 2000 /* adjust for macOS animation */ : 800 /* can be throttled */));
}
private onWindowResize(): void {
this.logService.trace(`web.main#${isIOS && window.visualViewport ? 'visualViewport' : 'window'}Resize`);
this.layoutService.layout();
}
private onWillShutdown(): void {
// Try to detect some user interaction with the workbench
// when shutdown has happened to not show the dialog e.g.
// when navigation takes a longer time.
Event.toPromise(Event.any(
Event.once(domEvent(document.body, EventType.KEY_DOWN, true)),
Event.once(domEvent(document.body, EventType.MOUSE_DOWN, true))
)).then(async () => {
// Delay the dialog in case the user interacted
// with the page before it transitioned away
await timeout(3000);
// This should normally not happen, but if for some reason
// the workbench was shutdown while the page is still there,
// inform the user that only a reload can bring back a working
// state.
const res = await this.dialogService.show(
Severity.Error,
localize('shutdownError', "An unexpected error occurred that requires a reload of this page."),
[
localize('reload', "Reload")
],
{
detail: localize('shutdownErrorDetail', "The workbench was unexpectedly disposed while running.")
}
);
if (res.choice === 0) {
this.hostService.reload();
}
});
}
private create(): void {
// Driver
if (this.environmentService.options?.driver) {
(async () => this._register(await registerWindowDriver()))();
}
// Handle open calls
this.setupOpenHandlers();
// Label formatting
this.registerLabelFormatters();
}
private setupOpenHandlers(): void {
// We need to ignore the `beforeunload` event while
// we handle external links to open specifically for
// the case of application protocols that e.g. invoke
// vscode itself. We do not want to open these links
// in a new window because that would leave a blank
// window to the user, but using `window.location.href`
// will trigger the `beforeunload`.
this.openerService.setDefaultExternalOpener({
openExternal: async (href: string) => {
if (matchesScheme(href, Schemas.http) || matchesScheme(href, Schemas.https)) {
windowOpenNoOpener(href);
} else {
this.lifecycleService.withExpectedUnload(() => window.location.href = href);
}
return true;
}
});
}
private registerLabelFormatters() {
this.labelService.registerFormatter({
scheme: Schemas.userData,
priority: true,
formatting: {
label: '${scheme}:${path}',
separator: '/',
}
});
}
}

View File

@@ -33,14 +33,29 @@ import { isStandalone } from 'vs/base/browser/browser';
'description': nls.localize('showEditorTabs', "Controls whether opened editors should show in tabs or not."),
'default': true
},
'workbench.editor.wrapTabs': {
'type': 'boolean',
'markdownDescription': nls.localize('wrapTabs', "Controls whether tabs should be wrapped over multiple lines when exceeding available space or whether a scrollbar should appear instead. This value is ignored when `#workbench.editor.showTabs#` is disabled."),
'default': false
},
'workbench.editor.scrollToSwitchTabs': {
'type': 'boolean',
'markdownDescription': nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'scrollToSwitchTabs' }, "Controls whether scrolling over tabs will open them or not. By default tabs will only reveal upon scrolling, but not open. You can press and hold the Shift-key while scrolling to change this behaviour for that duration. This value is ignored when `#workbench.editor.showTabs#` is `false`."),
'markdownDescription': nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'scrollToSwitchTabs' }, "Controls whether scrolling over tabs will open them or not. By default tabs will only reveal upon scrolling, but not open. You can press and hold the Shift-key while scrolling to change this behaviour for that duration. This value is ignored when `#workbench.editor.showTabs#` is disabled."),
'default': false
},
'workbench.editor.highlightModifiedTabs': {
'type': 'boolean',
'markdownDescription': nls.localize('highlightModifiedTabs', "Controls whether a top border is drawn on modified (dirty) editor tabs or not. This value is ignored when `#workbench.editor.showTabs#` is `false`."),
'markdownDescription': nls.localize('highlightModifiedTabs', "Controls whether a top border is drawn on modified (dirty) editor tabs or not. This value is ignored when `#workbench.editor.showTabs#` is disabled."),
'default': false
},
'workbench.editor.decorations.badges': {
'type': 'boolean',
'markdownDescription': nls.localize('decorations.badges', "Controls whether editor file decorations should use badges."),
'default': false
},
'workbench.editor.decorations.colors': {
'type': 'boolean',
'markdownDescription': nls.localize('decorations.colors', "Controls whether editor file decorations should use colors."),
'default': false
},
'workbench.editor.labelFormat': {
@@ -75,7 +90,7 @@ import { isStandalone } from 'vs/base/browser/browser';
'type': 'string',
'enum': ['left', 'right', 'off'],
'default': 'right',
'markdownDescription': nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'editorTabCloseButton' }, "Controls the position of the editor's tabs close buttons, or disables them when set to 'off'. This value is ignored when `#workbench.editor.showTabs#` is `false`.")
'markdownDescription': nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'editorTabCloseButton' }, "Controls the position of the editor's tabs close buttons, or disables them when set to 'off'. This value is ignored when `#workbench.editor.showTabs#` is disabled.")
},
'workbench.editor.tabSizing': {
'type': 'string',
@@ -85,7 +100,7 @@ import { isStandalone } from 'vs/base/browser/browser';
nls.localize('workbench.editor.tabSizing.fit', "Always keep tabs large enough to show the full editor label."),
nls.localize('workbench.editor.tabSizing.shrink', "Allow tabs to get smaller when the available space is not enough to show all tabs at once.")
],
'markdownDescription': nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'tabSizing' }, "Controls the sizing of editor tabs. This value is ignored when `#workbench.editor.showTabs#` is `false`.")
'markdownDescription': nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'tabSizing' }, "Controls the sizing of editor tabs. This value is ignored when `#workbench.editor.showTabs#` is disabled.")
},
'workbench.editor.pinnedTabSizing': {
'type': 'string',
@@ -96,7 +111,7 @@ import { isStandalone } from 'vs/base/browser/browser';
nls.localize('workbench.editor.pinnedTabSizing.compact', "A pinned tab will show in a compact form with only icon or first letter of the editor name."),
nls.localize('workbench.editor.pinnedTabSizing.shrink', "A pinned tab shrinks to a compact fixed size showing parts of the editor name.")
],
'markdownDescription': nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'pinnedTabSizing' }, "Controls the sizing of pinned editor tabs. Pinned tabs are sorted to the beginning of all opened tabs and typically do not close until unpinned. This value is ignored when `#workbench.editor.showTabs#` is `false`.")
'markdownDescription': nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'pinnedTabSizing' }, "Controls the sizing of pinned editor tabs. Pinned tabs are sorted to the beginning of all opened tabs and typically do not close until unpinned. This value is ignored when `#workbench.editor.showTabs#` is disabled.")
},
'workbench.editor.splitSizing': {
'type': 'string',
@@ -130,7 +145,12 @@ import { isStandalone } from 'vs/base/browser/browser';
},
'workbench.editor.enablePreviewFromQuickOpen': {
'type': 'boolean',
'description': nls.localize('enablePreviewFromQuickOpen', "Controls whether editors opened from Quick Open show as preview. Preview editors do not keep open and are reused until explicitly set to be kept open (e.g. via double click or editing)."),
'markdownDescription': nls.localize('enablePreviewFromQuickOpen', "Controls whether editors opened from Quick Open show as preview. Preview editors do not keep open and are reused until explicitly set to be kept open (e.g. via double click or editing). This value is ignored when `#workbench.editor.enablePreview#` is disabled."),
'default': false
},
'workbench.editor.enablePreviewFromCodeNavigation': {
'type': 'boolean',
'markdownDescription': nls.localize('enablePreviewFromCodeNavigation', "Controls whether editors remain in preview when a code navigation is started from them. Preview editors do not keep open and are reused until explicitly set to be kept open (e.g. via double click or editing). This value is ignored when `#workbench.editor.enablePreview#` is disabled."),
'default': false
},
'workbench.editor.closeOnFileDelete': {
@@ -315,8 +335,8 @@ import { isStandalone } from 'vs/base/browser/browser';
nls.localize('activeFolderLong', "`\${activeFolderLong}`: the full path of the folder the file is contained in (e.g. /Users/Development/myFolder/myFileFolder)."),
nls.localize('folderName', "`\${folderName}`: name of the workspace folder the file is contained in (e.g. myFolder)."),
nls.localize('folderPath', "`\${folderPath}`: file path of the workspace folder the file is contained in (e.g. /Users/Development/myFolder)."),
nls.localize('rootName', "`\${rootName}`: name of the workspace (e.g. myFolder or myWorkspace)."),
nls.localize('rootPath', "`\${rootPath}`: file path of the workspace (e.g. /Users/Development/myWorkspace)."),
nls.localize('rootName', "`\${rootName}`: name of the opened workspace or folder (e.g. myFolder or myWorkspace)."),
nls.localize('rootPath', "`\${rootPath}`: file path of the opened workspace or folder (e.g. /Users/Development/myWorkspace)."),
nls.localize('appName', "`\${appName}`: e.g. VS Code."),
nls.localize('remoteName', "`\${remoteName}`: e.g. SSH"),
nls.localize('dirty', "`\${dirty}`: a dirty indicator if the active editor is dirty."),
@@ -353,7 +373,7 @@ import { isStandalone } from 'vs/base/browser/browser';
'window.menuBarVisibility': {
'type': 'string',
'enum': ['default', 'visible', 'toggle', 'hidden', 'compact'],
'enumDescriptions': [
'markdownEnumDescriptions': [
nls.localize('window.menuBarVisibility.default', "Menu is only hidden in full screen mode."),
nls.localize('window.menuBarVisibility.visible', "Menu is always visible even in full screen mode."),
nls.localize('window.menuBarVisibility.toggle', "Menu is hidden but can be displayed via Alt key."),
@@ -463,7 +483,7 @@ import { isStandalone } from 'vs/base/browser/browser';
},
'zenMode.restore': {
'type': 'boolean',
'default': false,
'default': true,
'description': nls.localize('zenMode.restore', "Controls whether a window should restore to zen mode if it was exited in zen mode.")
},
'zenMode.silentNotifications': {

View File

@@ -8,7 +8,7 @@ import 'vs/workbench/browser/style';
import { localize } from 'vs/nls';
import { Emitter, setGlobalLeakWarningThreshold } from 'vs/base/common/event';
import { runWhenIdle } from 'vs/base/common/async';
import { getZoomLevel, isFirefox, isSafari, isChrome } from 'vs/base/browser/browser';
import { getZoomLevel, isFirefox, isSafari, isChrome, getPixelRatio } from 'vs/base/browser/browser';
import { mark } from 'vs/base/common/performance';
import { onUnexpectedError, setUnexpectedErrorHandler } from 'vs/base/common/errors';
import { Registry } from 'vs/platform/registry/common/platform';
@@ -177,10 +177,17 @@ export class Workbench extends Layout {
// Layout Service
serviceCollection.set(IWorkbenchLayoutService, this);
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// NOTE: DO NOT ADD ANY OTHER SERVICE INTO THE COLLECTION HERE.
// CONTRIBUTE IT VIA WORKBENCH.DESKTOP.MAIN.TS AND registerSingleton().
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//
// NOTE: Please do NOT register services here. Use `registerSingleton()`
// from `workbench.common.main.ts` if the service is shared between
// native and web or `workbench.sandbox.main.ts` if the service
// is native only.
//
// DO NOT add services to `workbench.desktop.main.ts`, always add
// to `workbench.sandbox.main.ts` to support our Electron sandbox
//
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// All Contributed Services
const contributedServices = getSingletonServiceDescriptors();
@@ -284,7 +291,7 @@ export class Workbench extends Layout {
}
}
readFontInfo(BareFontInfo.createFromRawSettings(configurationService.getValue('editor'), getZoomLevel()));
readFontInfo(BareFontInfo.createFromRawSettings(configurationService.getValue('editor'), getZoomLevel(), getPixelRatio()));
}
private storeFontInfo(storageService: IStorageService): void {
@@ -412,10 +419,10 @@ export class Workbench extends Layout {
}, 2500);
// Telemetry: startup metrics
mark('didStartWorkbench');
mark('code/didStartWorkbench');
// Perf reporting (devtools)
performance.measure('perf: workbench create & restore', 'didLoadWorkbenchMain', 'didStartWorkbench');
performance.measure('perf: workbench create & restore', 'code/didLoadWorkbenchMain', 'code/didStartWorkbench');
}
}
}