Merge from vscode c58aaab8a1cc22a7139b761166a0d4f37d41e998 (#7880)

* Merge from vscode c58aaab8a1cc22a7139b761166a0d4f37d41e998

* fix pipelines

* fix strict-null-checks

* add missing files
This commit is contained in:
Anthony Dresser
2019-10-21 22:12:22 -07:00
committed by GitHub
parent 7c9be74970
commit 1e22f47304
913 changed files with 18898 additions and 16536 deletions

View File

@@ -57,13 +57,7 @@ export class ContributableActionProvider implements IActionProvider {
const context = this.toContext(tree, element);
const contributors = this.registry.getActionBarContributors(Scope.VIEWER);
for (const contributor of contributors) {
if (contributor.hasActions(context)) {
return true;
}
}
return false;
return contributors.some(contributor => contributor.hasActions(context));
}
getActions(tree: ITree, element: unknown): ReadonlyArray<IAction> {
@@ -156,7 +150,7 @@ export interface IActionBarRegistry {
class ActionBarRegistry implements IActionBarRegistry {
private readonly actionBarContributorConstructors: { scope: string; ctor: IConstructorSignature0<ActionBarContributor>; }[] = [];
private readonly actionBarContributorInstances: Map<string, ActionBarContributor[]> = new Map();
private instantiationService!: IInstantiationService;
private instantiationService: IInstantiationService | undefined;
start(accessor: ServicesAccessor): void {
this.instantiationService = accessor.get(IInstantiationService);
@@ -168,13 +162,15 @@ class ActionBarRegistry implements IActionBarRegistry {
}
private createActionBarContributor(scope: string, ctor: IConstructorSignature0<ActionBarContributor>): void {
const instance = this.instantiationService.createInstance(ctor);
let target = this.actionBarContributorInstances.get(scope);
if (!target) {
target = [];
this.actionBarContributorInstances.set(scope, target);
if (this.instantiationService) {
const instance = this.instantiationService.createInstance(ctor);
let target = this.actionBarContributorInstances.get(scope);
if (!target) {
target = [];
this.actionBarContributorInstances.set(scope, target);
}
target.push(instance);
}
target.push(instance);
}
private getContributors(scope: string): ActionBarContributor[] {

View File

@@ -29,7 +29,7 @@ import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'v
class InspectContextKeysAction extends Action {
static readonly ID = 'workbench.action.inspectContextKeys';
static LABEL = nls.localize('inspect context keys', "Inspect Context Keys");
static readonly LABEL = nls.localize('inspect context keys', "Inspect Context Keys");
constructor(
id: string,
@@ -91,7 +91,7 @@ class InspectContextKeysAction extends Action {
class ToggleScreencastModeAction extends Action {
static readonly ID = 'workbench.action.toggleScreencastMode';
static LABEL = nls.localize('toggle screencast mode', "Toggle Screencast Mode");
static readonly LABEL = nls.localize('toggle screencast mode', "Toggle Screencast Mode");
static disposable: IDisposable | undefined;
@@ -195,7 +195,7 @@ class ToggleScreencastModeAction extends Action {
class LogStorageAction extends Action {
static readonly ID = 'workbench.action.logStorage';
static LABEL = nls.localize({ key: 'logStorage', comment: ['A developer only action to log the contents of the storage for the current window.'] }, "Log Storage Database Contents");
static readonly LABEL = nls.localize({ key: 'logStorage', comment: ['A developer only action to log the contents of the storage for the current window.'] }, "Log Storage Database Contents");
constructor(
id: string,

View File

@@ -16,13 +16,14 @@ import { IEditorGroupsService, GroupOrientation } from 'vs/workbench/services/ed
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { KeyMod, KeyCode, KeyChord } from 'vs/base/common/keyCodes';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { MenuBarVisibility } from 'vs/platform/windows/common/windows';
import { getMenuBarVisibility } from 'vs/platform/windows/common/windows';
import { isWindows, isLinux, isWeb } from 'vs/base/common/platform';
import { IsMacNativeContext } from 'vs/workbench/browser/contextkeys';
import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { InEditorZenModeContext, IsCenteredLayoutContext } from 'vs/workbench/common/editor';
import { InEditorZenModeContext, IsCenteredLayoutContext, EditorAreaVisibleContext } from 'vs/workbench/common/editor';
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { SideBarVisibleContext } from 'vs/workbench/common/viewlet';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
const registry = Registry.as<IWorkbenchActionRegistry>(Extensions.WorkbenchActions);
const viewCategory = nls.localize('view', "View");
@@ -232,6 +233,16 @@ export class ToggleEditorVisibilityAction extends Action {
registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleEditorVisibilityAction, ToggleEditorVisibilityAction.ID, ToggleEditorVisibilityAction.LABEL), 'View: Toggle Editor Area Visibility', viewCategory);
MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, {
group: '2_workbench_layout',
command: {
id: ToggleEditorVisibilityAction.ID,
title: nls.localize({ key: 'miShowEditorArea', comment: ['&& denotes a mnemonic'] }, "Show &&Editor Area"),
toggled: EditorAreaVisibleContext
},
order: 5
});
export class ToggleSidebarVisibilityAction extends Action {
static readonly ID = 'workbench.action.toggleSidebarVisibility';
@@ -340,7 +351,7 @@ class ToggleTabsVisibilityAction extends Action {
}
registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleTabsVisibilityAction, ToggleTabsVisibilityAction.ID, ToggleTabsVisibilityAction.LABEL, {
primary: undefined!,
primary: undefined,
mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.KEY_W, },
linux: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.KEY_W, }
}), 'View: Toggle Tab Visibility', viewCategory);
@@ -396,20 +407,21 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
export class ToggleMenuBarAction extends Action {
static readonly ID = 'workbench.action.toggleMenuBar';
static LABEL = nls.localize('toggleMenuBar', "Toggle Menu Bar");
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
@IConfigurationService private readonly configurationService: IConfigurationService,
@IEnvironmentService private readonly environmentService: IEnvironmentService
) {
super(id, label);
}
run(): Promise<void> {
let currentVisibilityValue = this.configurationService.getValue<MenuBarVisibility>(ToggleMenuBarAction.menuBarVisibilityKey);
let currentVisibilityValue = getMenuBarVisibility(this.configurationService, this.environmentService);
if (typeof currentVisibilityValue !== 'string') {
currentVisibilityValue = 'default';
}
@@ -417,8 +429,10 @@ export class ToggleMenuBarAction extends Action {
let newVisibilityValue: string;
if (currentVisibilityValue === 'visible' || currentVisibilityValue === 'default') {
newVisibilityValue = 'toggle';
} else if (currentVisibilityValue === 'compact') {
newVisibilityValue = 'hidden';
} else {
newVisibilityValue = 'default';
newVisibilityValue = (isWeb && currentVisibilityValue === 'hidden') ? 'compact' : 'default';
}
this.configurationService.updateValue(ToggleMenuBarAction.menuBarVisibilityKey, newVisibilityValue, ConfigurationTarget.USER);
@@ -446,7 +460,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, {
export abstract class BaseResizeViewAction extends Action {
protected static RESIZE_INCREMENT = 6.5; // This is a media-size percentage
protected static readonly RESIZE_INCREMENT = 6.5; // This is a media-size percentage
constructor(
id: string,

View File

@@ -17,6 +17,11 @@ import { AsyncDataTree } from 'vs/base/browser/ui/tree/asyncDataTree';
import { DataTree } from 'vs/base/browser/ui/tree/dataTree';
import { ITreeNode } from 'vs/base/browser/ui/tree/tree';
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
import { Tree } from 'vs/base/parts/tree/browser/treeImpl';
function isLegacyTree(widget: ListWidget): widget is ITree {
return widget instanceof Tree;
}
function ensureDOMFocus(widget: ListWidget | undefined): void {
// it can happen that one of the commands is executed while
@@ -552,7 +557,7 @@ function listFocusFirst(accessor: ServicesAccessor, options?: { fromFocused: boo
else if (focused) {
const tree = focused;
tree.focusFirst({ origin: 'keyboard' }, options && options.fromFocused ? tree.getFocus() : undefined);
tree.focusFirst({ origin: 'keyboard' }, options?.fromFocused ? tree.getFocus() : undefined);
tree.reveal(tree.getFocus());
}
}
@@ -604,7 +609,7 @@ function listFocusLast(accessor: ServicesAccessor, options?: { fromFocused: bool
else if (focused) {
const tree = focused;
tree.focusLast({ origin: 'keyboard' }, options && options.fromFocused ? tree.getFocus() : undefined);
tree.focusLast({ origin: 'keyboard' }, options?.fromFocused ? tree.getFocus() : undefined);
tree.reveal(tree.getFocus());
}
}
@@ -833,3 +838,67 @@ CommandsRegistry.registerCommand({
}
}
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'list.scrollUp',
weight: KeybindingWeight.WorkbenchContrib,
when: WorkbenchListFocusContextKey,
primary: KeyMod.CtrlCmd | KeyCode.UpArrow,
handler: accessor => {
const focused = accessor.get(IListService).lastFocusedList;
if (!focused || isLegacyTree(focused)) {
return;
}
focused.scrollTop -= 10;
}
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'list.scrollDown',
weight: KeybindingWeight.WorkbenchContrib,
when: WorkbenchListFocusContextKey,
primary: KeyMod.CtrlCmd | KeyCode.DownArrow,
handler: accessor => {
const focused = accessor.get(IListService).lastFocusedList;
if (!focused || isLegacyTree(focused)) {
return;
}
focused.scrollTop += 10;
}
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'list.scrollLeft',
weight: KeybindingWeight.WorkbenchContrib,
when: WorkbenchListFocusContextKey,
primary: KeyMod.CtrlCmd | KeyCode.LeftArrow,
handler: accessor => {
const focused = accessor.get(IListService).lastFocusedList;
if (!focused || isLegacyTree(focused)) {
return;
}
focused.scrollLeft -= 10;
}
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'list.scrollRight',
weight: KeybindingWeight.WorkbenchContrib,
when: WorkbenchListFocusContextKey,
primary: KeyMod.CtrlCmd | KeyCode.RightArrow,
handler: accessor => {
const focused = accessor.get(IListService).lastFocusedList;
if (!focused || isLegacyTree(focused)) {
return;
}
focused.scrollLeft += 10;
}
});

View File

@@ -68,9 +68,19 @@ abstract class BaseNavigationAction extends Action {
return false;
}
const activePanelId = this.panelService.getActivePanel()!.getId();
const activePanel = this.panelService.getActivePanel();
if (!activePanel) {
return false;
}
return this.panelService.openPanel(activePanelId, true)!;
const activePanelId = activePanel.getId();
const res = this.panelService.openPanel(activePanelId, true);
if (!res) {
return false;
}
return res;
}
protected async navigateToSidebar(): Promise<IViewlet | boolean> {
@@ -84,8 +94,8 @@ abstract class BaseNavigationAction extends Action {
}
const activeViewletId = activeViewlet.getId();
const value = await this.viewletService.openViewlet(activeViewletId, true);
return value === null ? false : value;
const viewlet = await this.viewletService.openViewlet(activeViewletId, true);
return !!viewlet;
}
protected navigateAcrossEditorGroup(direction: GroupDirection): boolean {

View File

@@ -37,7 +37,7 @@ export const inRecentFilesPickerContextKey = 'inRecentFilesPicker';
abstract class BaseOpenRecentAction extends Action {
private removeFromRecentlyOpened: IQuickInputButton = {
iconClass: 'action-remove-from-recently-opened',
iconClass: 'codicon-close',
tooltip: nls.localize('remove', "Remove from Recently Opened")
};
@@ -135,7 +135,7 @@ abstract class BaseOpenRecentAction extends Action {
});
if (pick) {
return this.hostService.openWindow([pick.openable], { forceNewWindow: keyMods && keyMods.ctrlCmd });
return this.hostService.openWindow([pick.openable], { forceNewWindow: keyMods?.ctrlCmd });
}
}
}
@@ -193,7 +193,7 @@ class QuickOpenRecentAction extends BaseOpenRecentAction {
class ToggleFullScreenAction extends Action {
static readonly ID = 'workbench.action.toggleFullScreen';
static LABEL = nls.localize('toggleFullScreen', "Toggle Full Screen");
static readonly LABEL = nls.localize('toggleFullScreen', "Toggle Full Screen");
constructor(
id: string,
@@ -211,7 +211,7 @@ class ToggleFullScreenAction extends Action {
export class ReloadWindowAction extends Action {
static readonly ID = 'workbench.action.reloadWindow';
static LABEL = nls.localize('reloadWindow', "Reload Window");
static readonly LABEL = nls.localize('reloadWindow', "Reload Window");
constructor(
id: string,
@@ -249,7 +249,7 @@ class ShowAboutDialogAction extends Action {
export class NewWindowAction extends Action {
static readonly ID = 'workbench.action.newWindow';
static LABEL = nls.localize('newWindow', "New Window");
static readonly LABEL = nls.localize('newWindow', "New Window");
constructor(
id: string,

View File

@@ -21,11 +21,12 @@ import { INotificationService } from 'vs/platform/notification/common/notificati
import { IHostService } from 'vs/workbench/services/host/browser/host';
import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
export class OpenFileAction extends Action {
static readonly ID = 'workbench.action.files.openFile';
static LABEL = nls.localize('openFile', "Open File...");
static readonly LABEL = nls.localize('openFile', "Open File...");
constructor(
id: string,
@@ -43,7 +44,7 @@ export class OpenFileAction extends Action {
export class OpenFolderAction extends Action {
static readonly ID = 'workbench.action.files.openFolder';
static LABEL = nls.localize('openFolder', "Open Folder...");
static readonly LABEL = nls.localize('openFolder', "Open Folder...");
constructor(
id: string,
@@ -61,7 +62,7 @@ export class OpenFolderAction extends Action {
export class OpenFileFolderAction extends Action {
static readonly ID = 'workbench.action.files.openFileFolder';
static LABEL = nls.localize('openFileFolder', "Open...");
static readonly LABEL = nls.localize('openFileFolder', "Open...");
constructor(
id: string,
@@ -79,7 +80,7 @@ export class OpenFileFolderAction extends Action {
export class OpenWorkspaceAction extends Action {
static readonly ID = 'workbench.action.openWorkspace';
static LABEL = nls.localize('openWorkspaceAction', "Open Workspace...");
static readonly LABEL = nls.localize('openWorkspaceAction', "Open Workspace...");
constructor(
id: string,
@@ -97,14 +98,15 @@ export class OpenWorkspaceAction extends Action {
export class CloseWorkspaceAction extends Action {
static readonly ID = 'workbench.action.closeFolder';
static LABEL = nls.localize('closeWorkspace', "Close Workspace");
static readonly LABEL = nls.localize('closeWorkspace', "Close Workspace");
constructor(
id: string,
label: string,
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
@INotificationService private readonly notificationService: INotificationService,
@IHostService private readonly hostService: IHostService
@IHostService private readonly hostService: IHostService,
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService
) {
super(id, label);
}
@@ -116,7 +118,7 @@ export class CloseWorkspaceAction extends Action {
return Promise.resolve(undefined);
}
return this.hostService.closeWorkspace();
return this.hostService.openWindow({ forceReuseWindow: true, remoteAuthority: this.environmentService.configuration.remoteAuthority });
}
}
@@ -148,7 +150,7 @@ export class OpenWorkspaceConfigFileAction extends Action {
export class AddRootFolderAction extends Action {
static readonly ID = 'workbench.action.addRootFolder';
static LABEL = ADD_ROOT_FOLDER_LABEL;
static readonly LABEL = ADD_ROOT_FOLDER_LABEL;
constructor(
id: string,
@@ -166,7 +168,7 @@ export class AddRootFolderAction extends Action {
export class GlobalRemoveRootFolderAction extends Action {
static readonly ID = 'workbench.action.removeRootFolder';
static LABEL = nls.localize('globalRemoveFolderFromWorkspace', "Remove Folder from Workspace...");
static readonly LABEL = nls.localize('globalRemoveFolderFromWorkspace', "Remove Folder from Workspace...");
constructor(
id: string,

View File

@@ -14,6 +14,8 @@ import { IConstructorSignature0, IInstantiationService } from 'vs/platform/insta
import { trackFocus, Dimension } from 'vs/base/browser/dom';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { Disposable } from 'vs/base/common/lifecycle';
import { assertIsDefined } from 'vs/base/common/types';
import { find } from 'vs/base/common/arrays';
/**
* Composites are layed out in the sidebar and panel part of the workbench. At a time only one composite
@@ -35,10 +37,10 @@ export abstract class Composite extends Component implements IComposite {
private readonly _onDidChangeVisibility: Emitter<boolean> = this._register(new Emitter<boolean>());
readonly onDidChangeVisibility: Event<boolean> = this._onDidChangeVisibility.event;
private _onDidFocus!: Emitter<void>;
private _onDidFocus: Emitter<void> | undefined;
get onDidFocus(): Event<void> {
if (!this._onDidFocus) {
this.registerFocusTrackEvents();
this._onDidFocus = this.registerFocusTrackEvents().onDidFocus;
}
return this._onDidFocus.event;
@@ -50,28 +52,32 @@ export abstract class Composite extends Component implements IComposite {
}
}
private _onDidBlur!: Emitter<void>;
private _onDidBlur: Emitter<void> | undefined;
get onDidBlur(): Event<void> {
if (!this._onDidBlur) {
this.registerFocusTrackEvents();
this._onDidBlur = this.registerFocusTrackEvents().onDidBlur;
}
return this._onDidBlur.event;
}
private registerFocusTrackEvents(): void {
this._onDidFocus = this._register(new Emitter<void>());
this._onDidBlur = this._register(new Emitter<void>());
private registerFocusTrackEvents(): { onDidFocus: Emitter<void>, onDidBlur: Emitter<void> } {
const container = assertIsDefined(this.getContainer());
const focusTracker = this._register(trackFocus(container));
const focusTracker = this._register(trackFocus(this.getContainer()));
this._register(focusTracker.onDidFocus(() => this._onDidFocus.fire()));
this._register(focusTracker.onDidBlur(() => this._onDidBlur.fire()));
const onDidFocus = this._onDidFocus = this._register(new Emitter<void>());
this._register(focusTracker.onDidFocus(() => onDidFocus.fire()));
const onDidBlur = this._onDidBlur = this._register(new Emitter<void>());
this._register(focusTracker.onDidBlur(() => onDidBlur.fire()));
return { onDidFocus, onDidBlur };
}
protected actionRunner: IActionRunner | undefined;
private visible: boolean;
private parent!: HTMLElement;
private parent: HTMLElement | undefined;
constructor(
id: string,
@@ -112,7 +118,7 @@ export abstract class Composite extends Component implements IComposite {
/**
* Returns the container this composite is being build in.
*/
getContainer(): HTMLElement {
getContainer(): HTMLElement | undefined {
return this.parent;
}
@@ -253,7 +259,7 @@ export abstract class CompositeRegistry<T extends Composite> extends Disposable
private composites: CompositeDescriptor<T>[] = [];
protected registerComposite(descriptor: CompositeDescriptor<T>): void {
if (this.compositeById(descriptor.id) !== null) {
if (this.compositeById(descriptor.id)) {
return;
}
@@ -271,7 +277,7 @@ export abstract class CompositeRegistry<T extends Composite> extends Disposable
this._onDidDeregister.fire(descriptor);
}
getComposite(id: string): CompositeDescriptor<T> | null {
getComposite(id: string): CompositeDescriptor<T> | undefined {
return this.compositeById(id);
}
@@ -279,13 +285,7 @@ export abstract class CompositeRegistry<T extends Composite> extends Disposable
return this.composites.slice(0);
}
private compositeById(id: string): CompositeDescriptor<T> | null {
for (const composite of this.composites) {
if (composite.id === id) {
return composite;
}
}
return null;
private compositeById(id: string): CompositeDescriptor<T> | undefined {
return find(this.composites, composite => composite.id === id);
}
}

View File

@@ -8,7 +8,7 @@ import { Disposable } from 'vs/base/common/lifecycle';
import { IContextKeyService, IContextKey, RawContextKey } from 'vs/platform/contextkey/common/contextkey';
import { InputFocusedContext } from 'vs/platform/contextkey/common/contextkeys';
import { IWindowsConfiguration } from 'vs/platform/windows/common/windows';
import { ActiveEditorContext, EditorsVisibleContext, TextCompareEditorVisibleContext, TextCompareEditorActiveContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, TEXT_DIFF_EDITOR_ID, SplitEditorsVertically, InEditorZenModeContext, IsCenteredLayoutContext, ActiveEditorGroupIndexContext, ActiveEditorGroupLastContext, ActiveEditorIsSaveableContext, toResource, SideBySideEditor } from 'vs/workbench/common/editor';
import { ActiveEditorContext, EditorsVisibleContext, TextCompareEditorVisibleContext, TextCompareEditorActiveContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, TEXT_DIFF_EDITOR_ID, SplitEditorsVertically, InEditorZenModeContext, IsCenteredLayoutContext, ActiveEditorGroupIndexContext, ActiveEditorGroupLastContext, ActiveEditorIsSaveableContext, toResource, SideBySideEditor, EditorAreaVisibleContext } from 'vs/workbench/common/editor';
import { trackFocus, addDisposableListener, EventType } from 'vs/base/browser/dom';
import { preferredSideBySideGroupDirection, GroupDirection, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
@@ -73,6 +73,7 @@ export class WorkbenchContextKeysHandler extends Disposable {
private isFullscreenContext: IContextKey<boolean>;
private isCenteredLayoutContext: IContextKey<boolean>;
private sideBarVisibleContext: IContextKey<boolean>;
private editorAreaVisibleContext: IContextKey<boolean>;
private panelPositionContext: IContextKey<string>;
constructor(
@@ -88,41 +89,6 @@ export class WorkbenchContextKeysHandler extends Disposable {
) {
super();
this.initContextKeys();
this.registerListeners();
}
private registerListeners(): void {
this.editorGroupService.whenRestored.then(() => this.updateEditorContextKeys());
this._register(this.editorService.onDidActiveEditorChange(() => this.updateEditorContextKeys()));
this._register(this.editorService.onDidVisibleEditorsChange(() => this.updateEditorContextKeys()));
this._register(this.editorGroupService.onDidAddGroup(() => this.updateEditorContextKeys()));
this._register(this.editorGroupService.onDidRemoveGroup(() => this.updateEditorContextKeys()));
this._register(this.editorGroupService.onDidGroupIndexChange(() => this.updateEditorContextKeys()));
this._register(addDisposableListener(window, EventType.FOCUS_IN, () => this.updateInputContextKeys(), true));
this._register(this.contextService.onDidChangeWorkbenchState(() => this.updateWorkbenchStateContextKey()));
this._register(this.contextService.onDidChangeWorkspaceFolders(() => this.updateWorkspaceFolderCountContextKey()));
this._register(this.configurationService.onDidChangeConfiguration(e => {
if (e.affectsConfiguration('workbench.editor.openSideBySideDirection')) {
this.updateSplitEditorsVerticallyContext();
}
}));
this._register(this.layoutService.onZenModeChange(enabled => this.inZenModeContext.set(enabled)));
this._register(this.layoutService.onFullscreenChange(fullscreen => this.isFullscreenContext.set(fullscreen)));
this._register(this.layoutService.onCenteredLayoutChange(centered => this.isCenteredLayoutContext.set(centered)));
this._register(this.layoutService.onPanelPositionChange(position => this.panelPositionContext.set(position)));
this._register(this.viewletService.onDidViewletClose(() => this.updateSideBarContextKeys()));
this._register(this.viewletService.onDidViewletOpen(() => this.updateSideBarContextKeys()));
}
private initContextKeys(): void {
// Platform
IsMacContext.bindTo(this.contextKeyService);
@@ -136,7 +102,7 @@ export class WorkbenchContextKeysHandler extends Disposable {
// macOS Native Tabs
const windowConfig = this.configurationService.getValue<IWindowsConfiguration>();
HasMacNativeTabsContext.bindTo(this.contextKeyService).set(windowConfig && windowConfig.window && windowConfig.window.nativeTabs);
HasMacNativeTabsContext.bindTo(this.contextKeyService).set(windowConfig?.window?.nativeTabs);
// Development
IsDevelopmentContext.bindTo(this.contextKeyService).set(!this.environmentService.isBuilt || this.environmentService.isExtensionDevelopment);
@@ -181,12 +147,49 @@ export class WorkbenchContextKeysHandler extends Disposable {
// Centered Layout
this.isCenteredLayoutContext = IsCenteredLayoutContext.bindTo(this.contextKeyService);
// Editor Area
this.editorAreaVisibleContext = EditorAreaVisibleContext.bindTo(this.contextKeyService);
// Sidebar
this.sideBarVisibleContext = SideBarVisibleContext.bindTo(this.contextKeyService);
// Panel Position
this.panelPositionContext = PanelPositionContext.bindTo(this.contextKeyService);
this.panelPositionContext.set(this.layoutService.getPanelPosition() === Position.RIGHT ? 'right' : 'bottom');
this.registerListeners();
}
private registerListeners(): void {
this.editorGroupService.whenRestored.then(() => this.updateEditorContextKeys());
this._register(this.editorService.onDidActiveEditorChange(() => this.updateEditorContextKeys()));
this._register(this.editorService.onDidVisibleEditorsChange(() => this.updateEditorContextKeys()));
this._register(this.editorGroupService.onDidAddGroup(() => this.updateEditorContextKeys()));
this._register(this.editorGroupService.onDidRemoveGroup(() => this.updateEditorContextKeys()));
this._register(this.editorGroupService.onDidGroupIndexChange(() => this.updateEditorContextKeys()));
this._register(addDisposableListener(window, EventType.FOCUS_IN, () => this.updateInputContextKeys(), true));
this._register(this.contextService.onDidChangeWorkbenchState(() => this.updateWorkbenchStateContextKey()));
this._register(this.contextService.onDidChangeWorkspaceFolders(() => this.updateWorkspaceFolderCountContextKey()));
this._register(this.configurationService.onDidChangeConfiguration(e => {
if (e.affectsConfiguration('workbench.editor.openSideBySideDirection')) {
this.updateSplitEditorsVerticallyContext();
}
}));
this._register(this.layoutService.onZenModeChange(enabled => this.inZenModeContext.set(enabled)));
this._register(this.layoutService.onFullscreenChange(fullscreen => this.isFullscreenContext.set(fullscreen)));
this._register(this.layoutService.onCenteredLayoutChange(centered => this.isCenteredLayoutContext.set(centered)));
this._register(this.layoutService.onPanelPositionChange(position => this.panelPositionContext.set(position)));
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))));
}
private updateEditorContextKeys(): void {
@@ -194,7 +197,7 @@ export class WorkbenchContextKeysHandler extends Disposable {
const activeControl = this.editorService.activeControl;
const visibleEditors = this.editorService.visibleControls;
this.textCompareEditorActiveContext.set(!!activeControl && activeControl.getId() === TEXT_DIFF_EDITOR_ID);
this.textCompareEditorActiveContext.set(activeControl?.getId() === TEXT_DIFF_EDITOR_ID);
this.textCompareEditorVisibleContext.set(visibleEditors.some(control => control.getId() === TEXT_DIFF_EDITOR_ID));
if (visibleEditors.length > 0) {

View File

@@ -113,7 +113,7 @@ export function extractResources(e: DragEvent, externalOnly?: boolean): Array<ID
if (e.dataTransfer && e.dataTransfer.files) {
for (let i = 0; i < e.dataTransfer.files.length; i++) {
const file = e.dataTransfer.files[i];
if (file && file.path /* Electron only */ && !resources.some(r => r.resource.fsPath === file.path) /* prevent duplicates */) {
if (file?.path /* Electron only */ && !resources.some(r => r.resource.fsPath === file.path) /* prevent duplicates */) {
try {
resources.push({ resource: URI.file(file.path), isExternal: true });
} catch (error) {
@@ -243,11 +243,13 @@ export class ResourcesDropHandler {
}
// Resolve the contents of the dropped dirty resource from source
try {
const content = await this.backupFileService.resolveBackupContent((droppedDirtyEditor.backupResource!));
await this.backupFileService.backupResource(droppedDirtyEditor.resource, content.value.create(this.getDefaultEOL()).createSnapshot(true));
} catch (e) {
// Ignore error
if (droppedDirtyEditor.backupResource) {
try {
const content = await this.backupFileService.resolveBackupContent((droppedDirtyEditor.backupResource));
await this.backupFileService.backupResource(droppedDirtyEditor.resource, content.value.create(this.getDefaultEOL()).createSnapshot(true));
} catch (e) {
// Ignore error
}
}
return false;
@@ -358,7 +360,7 @@ export function fillResourceDataTransfers(accessor: ServicesAccessor, resources:
for (const textEditorWidget of textEditorWidgets) {
if (isCodeEditor(textEditorWidget)) {
const model = textEditorWidget.getModel();
if (model && model.uri && model.uri.toString() === file.resource.toString()) {
if (model?.uri?.toString() === file.resource.toString()) {
viewState = textEditorWidget.saveViewState();
break;
}

View File

@@ -8,6 +8,7 @@ import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { Registry } from 'vs/platform/registry/common/platform';
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
import { IConstructorSignature0, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { find } from 'vs/base/common/arrays';
export interface IEditorDescriptor {
instantiate(instantiationService: IInstantiationService): BaseEditor;
@@ -140,13 +141,7 @@ class EditorRegistry implements IEditorRegistry {
}
getEditorById(editorId: string): EditorDescriptor | undefined {
for (const editor of this.editors) {
if (editor.getId() === editorId) {
return editor;
}
}
return undefined;
return find(this.editors, editor => editor.getId() === editorId);
}
getEditors(): readonly EditorDescriptor[] {

View File

@@ -211,7 +211,7 @@ export class ResourceLabel extends ResourceLabels {
constructor(
container: HTMLElement,
options: IIconLabelCreationOptions,
options: IIconLabelCreationOptions | undefined,
@IInstantiationService instantiationService: IInstantiationService,
@IExtensionService extensionService: IExtensionService,
@IConfigurationService configurationService: IConfigurationService,
@@ -252,7 +252,7 @@ class ResourceLabelWidget extends IconLabel {
constructor(
container: HTMLElement,
options: IIconLabelCreationOptions,
options: IIconLabelCreationOptions | undefined,
@IModeService private readonly modeService: IModeService,
@IModelService private readonly modelService: IModelService,
@IDecorationsService private readonly decorationsService: IDecorationsService,
@@ -434,7 +434,7 @@ class ResourceLabelWidget extends IconLabel {
return;
}
const iconLabelOptions: IIconLabelValueOptions = {
const iconLabelOptions: IIconLabelValueOptions & { extraClasses: string[] } = {
title: '',
italic: this.options && this.options.italic,
matches: this.options && this.options.matches,
@@ -462,7 +462,7 @@ class ResourceLabelWidget extends IconLabel {
}
if (this.options && this.options.extraClasses) {
iconLabelOptions.extraClasses!.push(...this.options.extraClasses);
iconLabelOptions.extraClasses.push(...this.options.extraClasses);
}
if (this.options && this.options.fileDecorations && resource) {
@@ -477,11 +477,11 @@ class ResourceLabelWidget extends IconLabel {
}
if (this.options.fileDecorations.colors) {
iconLabelOptions.extraClasses!.push(deco.labelClassName);
iconLabelOptions.extraClasses.push(deco.labelClassName);
}
if (this.options.fileDecorations.badges) {
iconLabelOptions.extraClasses!.push(deco.badgeClassName);
iconLabelOptions.extraClasses.push(deco.badgeClassName);
}
}
}

View File

@@ -21,9 +21,9 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
import { ITitleService } from 'vs/workbench/services/title/common/titleService';
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { LifecyclePhase, StartupKind, ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
import { MenuBarVisibility, getTitleBarStyle } from 'vs/platform/windows/common/windows';
import { MenuBarVisibility, getTitleBarStyle, getMenuBarVisibility } from 'vs/platform/windows/common/windows';
import { IHostService } from 'vs/workbench/services/host/browser/host';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { IEditorService, IResourceEditor } from 'vs/workbench/services/editor/common/editorService';
@@ -36,9 +36,10 @@ import { IActivityBarService } from 'vs/workbench/services/activityBar/browser/a
import { IFileService } from 'vs/platform/files/common/files';
import { isCodeEditor } from 'vs/editor/browser/editorBrowser';
import { coalesce } from 'vs/base/common/arrays';
import { assertIsDefined } from 'vs/base/common/types';
import { INotificationService, NotificationsFilter } from 'vs/platform/notification/common/notification';
enum Settings {
MENUBAR_VISIBLE = 'window.menuBarVisibility',
ACTIVITYBAR_VISIBLE = 'workbench.activityBar.visible',
STATUSBAR_VISIBLE = 'workbench.statusBar.visible',
@@ -46,7 +47,6 @@ enum Settings {
PANEL_POSITION = 'workbench.panel.defaultLocation',
ZEN_MODE_RESTORE = 'zenMode.restore',
}
enum Storage {
@@ -83,9 +83,6 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
//#region Events
private readonly _onTitleBarVisibilityChange: Emitter<void> = this._register(new Emitter<void>());
readonly onTitleBarVisibilityChange: Event<void> = this._onTitleBarVisibilityChange.event;
private readonly _onZenModeChange: Emitter<boolean> = this._register(new Emitter<boolean>());
readonly onZenModeChange: Event<boolean> = this._onZenModeChange.event;
@@ -98,12 +95,15 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
private readonly _onPanelPositionChange: Emitter<string> = this._register(new Emitter<string>());
readonly onPanelPositionChange: Event<string> = this._onPanelPositionChange.event;
private readonly _onPartVisibilityChange: Emitter<void> = this._register(new Emitter<void>());
readonly onPartVisibilityChange: Event<void> = this._onPartVisibilityChange.event;
private readonly _onLayout = this._register(new Emitter<IDimension>());
readonly onLayout: Event<IDimension> = this._onLayout.event;
//#endregion
private _dimension: IDimension;
private _dimension!: IDimension;
get dimension(): IDimension { return this._dimension; }
private _container: HTMLElement = document.createElement('div');
@@ -111,29 +111,30 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
private parts: Map<string, Part> = new Map<string, Part>();
private workbenchGrid: SerializableGrid<ISerializableView>;
private workbenchGrid!: SerializableGrid<ISerializableView>;
private disposed: boolean;
private disposed: boolean | undefined;
private titleBarPartView: ISerializableView;
private activityBarPartView: ISerializableView;
private sideBarPartView: ISerializableView;
private panelPartView: ISerializableView;
private editorPartView: ISerializableView;
private statusBarPartView: ISerializableView;
private titleBarPartView!: ISerializableView;
private activityBarPartView!: ISerializableView;
private sideBarPartView!: ISerializableView;
private panelPartView!: ISerializableView;
private editorPartView!: ISerializableView;
private statusBarPartView!: ISerializableView;
private environmentService: IWorkbenchEnvironmentService;
private configurationService: IConfigurationService;
private lifecycleService: ILifecycleService;
private storageService: IStorageService;
private hostService: IHostService;
private editorService: IEditorService;
private editorGroupService: IEditorGroupsService;
private panelService: IPanelService;
private titleService: ITitleService;
private viewletService: IViewletService;
private contextService: IWorkspaceContextService;
private backupFileService: IBackupFileService;
private environmentService!: IWorkbenchEnvironmentService;
private configurationService!: IConfigurationService;
private lifecycleService!: ILifecycleService;
private storageService!: IStorageService;
private hostService!: IHostService;
private editorService!: IEditorService;
private editorGroupService!: IEditorGroupsService;
private panelService!: IPanelService;
private titleService!: ITitleService;
private viewletService!: IViewletService;
private contextService!: IWorkspaceContextService;
private backupFileService!: IBackupFileService;
private notificationService!: INotificationService;
protected readonly state = {
fullscreen: false,
@@ -181,7 +182,8 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
transitionedToCenteredEditorLayout: false,
wasSideBarVisible: false,
wasPanelVisible: false,
transitionDisposables: new DisposableStore()
transitionDisposables: new DisposableStore(),
setNotificationsFilter: false
},
};
@@ -209,6 +211,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
this.panelService = accessor.get(IPanelService);
this.viewletService = accessor.get(IViewletService);
this.titleService = accessor.get(ITitleService);
this.notificationService = accessor.get(INotificationService);
accessor.get(IStatusbarService); // not used, but called to ensure instantiated
accessor.get(IActivityBarService); // not used, but called to ensure instantiated
@@ -261,8 +264,6 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
this.state.menuBar.toggled = visible;
if (this.state.fullscreen && (this.state.menuBar.visibility === 'toggle' || this.state.menuBar.visibility === 'default')) {
this._onTitleBarVisibilityChange.fire();
// Propagate to grid
this.workbenchGrid.setViewVisible(this.titleBarPartView, this.isVisible(Parts.TITLEBAR_PART));
@@ -287,8 +288,6 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
// Changing fullscreen state of the window has an impact on custom title bar visibility, so we need to update
if (getTitleBarStyle(this.configurationService, this.environmentService) === 'custom') {
this._onTitleBarVisibilityChange.fire();
// Propagate to grid
this.workbenchGrid.setViewVisible(this.titleBarPartView, this.isVisible(Parts.TITLEBAR_PART));
@@ -326,7 +325,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
}
// Menubar visibility
const newMenubarVisibility = this.configurationService.getValue<MenuBarVisibility>(Settings.MENUBAR_VISIBLE);
const newMenubarVisibility = getMenuBarVisibility(this.configurationService, this.environmentService);
this.setMenubarVisibility(newMenubarVisibility, !!skipLayout);
}
@@ -340,10 +339,12 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
this.state.sideBar.position = position;
// Adjust CSS
removeClass(activityBar.getContainer(), oldPositionValue);
removeClass(sideBar.getContainer(), oldPositionValue);
addClass(activityBar.getContainer(), newPositionValue);
addClass(sideBar.getContainer(), newPositionValue);
const activityBarContainer = assertIsDefined(activityBar.getContainer());
const sideBarContainer = assertIsDefined(sideBar.getContainer());
removeClass(activityBarContainer, oldPositionValue);
removeClass(sideBarContainer, oldPositionValue);
addClass(activityBarContainer, newPositionValue);
addClass(sideBarContainer, newPositionValue);
// Update Styles
activityBar.updateStyles();
@@ -371,7 +372,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
this.state.fullscreen = isFullscreen();
// Menubar visibility
this.state.menuBar.visibility = this.configurationService.getValue<MenuBarVisibility>(Settings.MENUBAR_VISIBLE);
this.state.menuBar.visibility = getMenuBarVisibility(this.configurationService, this.environmentService);
// Activity bar visibility
this.state.activityBar.hidden = !this.configurationService.getValue<string>(Settings.ACTIVITYBAR_VISIBLE);
@@ -455,7 +456,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
// Files to diff is exclusive
return pathsToEditors(configuration.filesToDiff, fileService).then(filesToDiff => {
if (filesToDiff && filesToDiff.length === 2) {
if (filesToDiff?.length === 2) {
return [{
leftResource: filesToDiff[0].resource,
rightResource: filesToDiff[1].resource,
@@ -528,10 +529,10 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
const container = this.getContainer(part);
return isAncestor(activeElement, container);
return !!container && isAncestor(activeElement, container);
}
getContainer(part: Parts): HTMLElement {
getContainer(part: Parts): HTMLElement | undefined {
switch (part) {
case Parts.TITLEBAR_PART:
return this.getPart(Parts.TITLEBAR_PART).getContainer();
@@ -579,7 +580,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
return true; // any other part cannot be hidden
}
getDimension(part: Parts): Dimension {
getDimension(part: Parts): Dimension | undefined {
return this.getPart(part).dimension;
}
@@ -647,6 +648,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
hideActivityBar: boolean;
hideStatusBar: boolean;
hideLineNumbers: boolean;
silentNotifications: boolean;
} = this.configurationService.getValue('zenMode');
toggleFullScreen = !this.state.fullscreen && config.fullScreen;
@@ -676,6 +678,11 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
this.state.zenMode.transitionDisposables.add(this.editorGroupService.enforcePartOptions({ showTabs: false }));
}
this.state.zenMode.setNotificationsFilter = config.silentNotifications;
if (config.silentNotifications) {
this.notificationService.setFilter(NotificationsFilter.ERROR);
}
if (config.centerLayout) {
this.centerEditorLayout(true, true);
}
@@ -701,6 +708,9 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
this.doUpdateLayoutConfiguration(true);
this.editorGroupService.activeGroup.focus();
if (this.state.zenMode.setNotificationsFilter) {
this.notificationService.setFilter(NotificationsFilter.OFF);
}
toggleFullScreen = this.state.zenMode.transitionedToFullScreen && this.state.fullscreen;
}
@@ -747,7 +757,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
this.workbenchGrid.setViewVisible(this.statusBarPartView, !hidden);
}
protected createWorkbenchLayout(instantiationService: IInstantiationService): void {
protected createWorkbenchLayout(): void {
const titleBar = this.getPart(Parts.TITLEBAR_PART);
const editorPart = this.getPart(Parts.EDITOR_PART);
const activityBar = this.getPart(Parts.ACTIVITYBAR_PART);
@@ -782,17 +792,18 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
this.container.prepend(workbenchGrid.element);
this.workbenchGrid = workbenchGrid;
this._register((this.sideBarPartView as SidebarPart).onDidVisibilityChange((visible) => {
this.setSideBarHidden(!visible, true);
}));
this._register((this.panelPartView as PanelPart).onDidVisibilityChange((visible) => {
this.setPanelHidden(!visible, true);
}));
this._register((this.editorPartView as PanelPart).onDidVisibilityChange((visible) => {
this.setEditorHidden(!visible, true);
}));
[titleBar, editorPart, activityBar, panelPart, sideBar, statusBar].forEach((part: Part) => {
this._register(part.onDidVisibilityChange((visible) => {
this._onPartVisibilityChange.fire();
if (part === sideBar) {
this.setSideBarHidden(!visible, true);
} else if (part === panelPart) {
this.setPanelHidden(!visible, true);
} else if (part === editorPart) {
this.setEditorHidden(!visible, true);
}
}));
});
this._register(this.storageService.onWillSaveState(() => {
const grid = this.workbenchGrid as SerializableGrid<ISerializableView>;
@@ -1121,8 +1132,9 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
this.storageService.store(Storage.PANEL_POSITION, positionToString(this.state.panel.position), StorageScope.WORKSPACE);
// Adjust CSS
removeClass(panelPart.getContainer(), oldPositionValue);
addClass(panelPart.getContainer(), newPositionValue);
const panelContainer = assertIsDefined(panelPart.getContainer());
removeClass(panelContainer, oldPositionValue);
addClass(panelContainer, newPositionValue);
// Update Styles
panelPart.updateStyles();
@@ -1161,7 +1173,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
const width = this.storageService.getNumber(Storage.GRID_WIDTH, StorageScope.GLOBAL, workbenchDimensions.width);
const height = this.storageService.getNumber(Storage.GRID_HEIGHT, StorageScope.GLOBAL, workbenchDimensions.height);
// At some point, we will not fall back to old keys from legacy layout, but for now, let's migrate the keys
const sideBarSize = this.storageService.getNumber(Storage.SIDEBAR_SIZE, StorageScope.GLOBAL, this.storageService.getNumber('workbench.sidebar.width', StorageScope.GLOBAL, Math.min(workbenchDimensions.width / 4, 300))!);
const sideBarSize = this.storageService.getNumber(Storage.SIDEBAR_SIZE, StorageScope.GLOBAL, this.storageService.getNumber('workbench.sidebar.width', StorageScope.GLOBAL, Math.min(workbenchDimensions.width / 4, 300)));
const panelSize = this.storageService.getNumber(Storage.PANEL_SIZE, StorageScope.GLOBAL, this.storageService.getNumber(this.state.panel.position === Position.BOTTOM ? 'workbench.panel.height' : 'workbench.panel.width', StorageScope.GLOBAL, workbenchDimensions.height / 3));
const titleBarHeight = this.titleBarPartView.minimumHeight;

View File

@@ -11,6 +11,7 @@ import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService';
import { IConstructorSignature0 } from 'vs/platform/instantiation/common/instantiation';
import { isAncestor } from 'vs/base/browser/dom';
import { assertIsDefined } from 'vs/base/common/types';
export abstract class Panel extends Composite implements IPanel { }
@@ -25,7 +26,7 @@ export class PanelDescriptor extends CompositeDescriptor<Panel> {
}
export class PanelRegistry extends CompositeRegistry<Panel> {
private defaultPanelId!: string;
private defaultPanelId: string | undefined;
/**
* Registers a panel to the platform.
@@ -44,7 +45,7 @@ export class PanelRegistry extends CompositeRegistry<Panel> {
/**
* Returns a panel by id.
*/
getPanel(id: string): PanelDescriptor | null {
getPanel(id: string): PanelDescriptor | undefined {
return this.getComposite(id);
}
@@ -66,7 +67,7 @@ export class PanelRegistry extends CompositeRegistry<Panel> {
* Gets the id of the panel that should open on startup by default.
*/
getDefaultPanelId(): string {
return this.defaultPanelId;
return assertIsDefined(this.defaultPanelId);
}
/**
@@ -106,13 +107,14 @@ export abstract class TogglePanelAction extends Action {
private isPanelActive(): boolean {
const activePanel = this.panelService.getActivePanel();
return !!activePanel && activePanel.getId() === this.panelId;
return activePanel?.getId() === this.panelId;
}
private isPanelFocused(): boolean {
const activeElement = document.activeElement;
const panelPart = this.layoutService.getContainer(Parts.PANEL_PART);
return !!(this.isPanelActive() && activeElement && isAncestor(activeElement, this.layoutService.getContainer(Parts.PANEL_PART)));
return !!(this.isPanelActive() && activeElement && panelPart && isAncestor(activeElement, panelPart));
}
}

View File

@@ -12,6 +12,7 @@ import { IDimension } from 'vs/platform/layout/browser/layoutService';
import { ISerializableView, IViewSize } from 'vs/base/browser/ui/grid/grid';
import { Event, Emitter } from 'vs/base/common/event';
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
import { assertIsDefined } from 'vs/base/common/types';
export interface IPartOptions {
hasTitle?: boolean;
@@ -29,13 +30,16 @@ export interface ILayoutContentResult {
*/
export abstract class Part extends Component implements ISerializableView {
private _dimension: Dimension;
get dimension(): Dimension { return this._dimension; }
private _dimension: Dimension | undefined;
get dimension(): Dimension | undefined { return this._dimension; }
private parent: HTMLElement;
private titleArea: HTMLElement | null = null;
private contentArea: HTMLElement | null = null;
private partLayout: PartLayout;
protected _onDidVisibilityChange = this._register(new Emitter<boolean>());
readonly onDidVisibilityChange: Event<boolean> = this._onDidVisibilityChange.event;
private parent: HTMLElement | undefined;
private titleArea: HTMLElement | undefined;
private contentArea: HTMLElement | undefined;
private partLayout: PartLayout | undefined;
constructor(
id: string,
@@ -80,35 +84,35 @@ export abstract class Part extends Component implements ISerializableView {
/**
* Returns the overall part container.
*/
getContainer(): HTMLElement {
getContainer(): HTMLElement | undefined {
return this.parent;
}
/**
* Subclasses override to provide a title area implementation.
*/
protected createTitleArea(parent: HTMLElement, options?: object): HTMLElement | null {
return null;
protected createTitleArea(parent: HTMLElement, options?: object): HTMLElement | undefined {
return undefined;
}
/**
* Returns the title area container.
*/
protected getTitleArea(): HTMLElement | null {
protected getTitleArea(): HTMLElement | undefined {
return this.titleArea;
}
/**
* Subclasses override to provide a content area implementation.
*/
protected createContentArea(parent: HTMLElement, options?: object): HTMLElement | null {
return null;
protected createContentArea(parent: HTMLElement, options?: object): HTMLElement | undefined {
return undefined;
}
/**
* Returns the content area container.
*/
protected getContentArea(): HTMLElement | null {
protected getContentArea(): HTMLElement | undefined {
return this.contentArea;
}
@@ -116,7 +120,9 @@ export abstract class Part extends Component implements ISerializableView {
* Layout title and content area in the given dimension.
*/
protected layoutContents(width: number, height: number): ILayoutContentResult {
return this.partLayout.layout(width, height);
const partLayout = assertIsDefined(this.partLayout);
return partLayout.layout(width, height);
}
//#region ISerializableView
@@ -124,7 +130,7 @@ export abstract class Part extends Component implements ISerializableView {
private _onDidChange = this._register(new Emitter<IViewSize | undefined>());
get onDidChange(): Event<IViewSize | undefined> { return this._onDidChange.event; }
element: HTMLElement;
element!: HTMLElement;
abstract minimumWidth: number;
abstract maximumWidth: number;
@@ -135,6 +141,10 @@ export abstract class Part extends Component implements ISerializableView {
this._dimension = new Dimension(width, height);
}
setVisible(visible: boolean) {
this._onDidVisibilityChange.fire(visible);
}
abstract toJSON(): object;
//#endregion
@@ -144,7 +154,7 @@ class PartLayout {
private static readonly TITLE_HEIGHT = 35;
constructor(private options: IPartOptions, private contentArea: HTMLElement | null) { }
constructor(private options: IPartOptions, private contentArea: HTMLElement | undefined) { }
layout(width: number, height: number): ILayoutContentResult {

View File

@@ -22,7 +22,7 @@ import { ActivityAction, ActivityActionViewItem, ICompositeBar, ICompositeBarCol
import { ViewletDescriptor } from 'vs/workbench/browser/viewlet';
import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions';
import { IActivity } from 'vs/workbench/common/activity';
import { ACTIVITY_BAR_FOREGROUND } from 'vs/workbench/common/theme';
import { ACTIVITY_BAR_FOREGROUND, ACTIVITY_BAR_ACTIVE_BORDER, ACTIVITY_BAR_ACTIVE_BACKGROUND } from 'vs/workbench/common/theme';
import { IActivityBarService } from 'vs/workbench/services/activityBar/browser/activityBarService';
import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService';
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
@@ -60,7 +60,7 @@ export class ViewletActivityAction extends ActivityAction {
const activeViewlet = this.viewletService.getActiveViewlet();
// Hide sidebar if selected viewlet already visible
if (sideBarVisible && activeViewlet && activeViewlet.getId() === this.activity.id) {
if (sideBarVisible && activeViewlet?.getId() === this.activity.id) {
this.logAction('hide');
this.layoutService.setSideBarHidden(true);
return true;
@@ -95,7 +95,7 @@ export class ToggleViewletAction extends Action {
const activeViewlet = this.viewletService.getActiveViewlet();
// Hide sidebar if selected viewlet already visible
if (sideBarVisible && activeViewlet && activeViewlet.getId() === this._viewlet.id) {
if (sideBarVisible && activeViewlet?.getId() === this._viewlet.id) {
this.layoutService.setSideBarHidden(true);
return Promise.resolve();
}
@@ -163,15 +163,19 @@ export class GlobalActivityActionViewItem extends ActivityActionViewItem {
export class PlaceHolderViewletActivityAction extends ViewletActivityAction {
constructor(
id: string, name: string, iconUrl: URI,
id: string,
name: string,
iconUrl: URI | undefined,
@IViewletService viewletService: IViewletService,
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,
@ITelemetryService telemetryService: ITelemetryService
) {
super({ id, name: id, cssClass: `extensionViewlet-placeholder-${id.replace(/\./g, '-')}` }, viewletService, layoutService, telemetryService);
const iconClass = `.monaco-workbench .activitybar .monaco-action-bar .action-label.${this.class}`; // Generate Placeholder CSS to show the icon in the activity bar
DOM.createCSSRule(iconClass, `-webkit-mask: ${DOM.asCSSUrl(iconUrl)} no-repeat 50% 50%; -webkit-mask-size: 24px;`);
if (iconUrl) {
const iconClass = `.monaco-workbench .activitybar .monaco-action-bar .action-label.${this.class}`; // Generate Placeholder CSS to show the icon in the activity bar
DOM.createCSSRule(iconClass, `-webkit-mask: ${DOM.asCSSUrl(iconUrl)} no-repeat 50% 50%; -webkit-mask-size: 24px;`);
}
}
setActivity(activity: IActivity): void {
@@ -222,7 +226,7 @@ class SwitchSideBarViewAction extends Action {
export class PreviousSideBarViewAction extends SwitchSideBarViewAction {
static readonly ID = 'workbench.action.previousSideBarView';
static LABEL = nls.localize('previousSideBarView', 'Previous Side Bar View');
static readonly LABEL = nls.localize('previousSideBarView', 'Previous Side Bar View');
constructor(
id: string,
@@ -241,7 +245,7 @@ export class PreviousSideBarViewAction extends SwitchSideBarViewAction {
export class NextSideBarViewAction extends SwitchSideBarViewAction {
static readonly ID = 'workbench.action.nextSideBarView';
static LABEL = nls.localize('nextSideBarView', 'Next Side Bar View');
static readonly LABEL = nls.localize('nextSideBarView', 'Next Side Bar View');
constructor(
id: string,
@@ -274,6 +278,25 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
`);
}
const activeBorderColor = theme.getColor(ACTIVITY_BAR_ACTIVE_BORDER);
if (activeBorderColor) {
collector.addRule(`
.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.checked .active-item-indicator:before {
border-left-color: ${activeBorderColor};
}
`);
}
const activeBackgroundColor = theme.getColor(ACTIVITY_BAR_ACTIVE_BACKGROUND);
if (activeBackgroundColor) {
collector.addRule(`
.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.checked .active-item-indicator {
z-index: 0;
background-color: ${activeBackgroundColor};
}
`);
}
// Styling with Outline color (e.g. high contrast theme)
const outline = theme.getColor(activeContrastBorder);
if (outline) {

View File

@@ -5,7 +5,6 @@
import 'vs/css!./media/activitybarpart';
import * as nls from 'vs/nls';
import { illegalArgument } from 'vs/base/common/errors';
import { ActionsOrientation, ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
import { GLOBAL_ACTIVITY_ID } from 'vs/workbench/common/activity';
import { Registry } from 'vs/platform/registry/common/platform';
@@ -15,10 +14,10 @@ import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
import { IBadge } from 'vs/workbench/services/activity/common/activity';
import { IWorkbenchLayoutService, Parts, Position as SideBarPosition } from 'vs/workbench/services/layout/browser/layoutService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IDisposable, toDisposable, DisposableStore } from 'vs/base/common/lifecycle';
import { ToggleActivityBarVisibilityAction } from 'vs/workbench/browser/actions/layoutActions';
import { IDisposable, toDisposable, DisposableStore, Disposable } from 'vs/base/common/lifecycle';
import { ToggleActivityBarVisibilityAction, ToggleMenuBarAction } from 'vs/workbench/browser/actions/layoutActions';
import { IThemeService, ITheme } from 'vs/platform/theme/common/themeService';
import { ACTIVITY_BAR_BACKGROUND, ACTIVITY_BAR_BORDER, ACTIVITY_BAR_FOREGROUND, ACTIVITY_BAR_BADGE_BACKGROUND, ACTIVITY_BAR_BADGE_FOREGROUND, ACTIVITY_BAR_DRAG_AND_DROP_BACKGROUND, ACTIVITY_BAR_INACTIVE_FOREGROUND } from 'vs/workbench/common/theme';
import { ACTIVITY_BAR_BACKGROUND, ACTIVITY_BAR_BORDER, ACTIVITY_BAR_FOREGROUND, ACTIVITY_BAR_ACTIVE_BORDER, ACTIVITY_BAR_BADGE_BACKGROUND, ACTIVITY_BAR_BADGE_FOREGROUND, ACTIVITY_BAR_DRAG_AND_DROP_BACKGROUND, ACTIVITY_BAR_INACTIVE_FOREGROUND, ACTIVITY_BAR_ACTIVE_BACKGROUND } from 'vs/workbench/common/theme';
import { contrastBorder } from 'vs/platform/theme/common/colorRegistry';
import { CompositeBar, ICompositeBarItem } from 'vs/workbench/browser/parts/compositeBar';
import { Dimension, addClass, removeNode } from 'vs/base/browser/dom';
@@ -30,14 +29,16 @@ import { ViewletDescriptor } from 'vs/workbench/browser/viewlet';
import { IViewsService, IViewContainersRegistry, Extensions as ViewContainerExtensions, ViewContainer, TEST_VIEW_CONTAINER_ID, IViewDescriptorCollection } from 'vs/workbench/common/views';
import { IContextKeyService, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { IViewlet } from 'vs/workbench/common/viewlet';
import { isUndefinedOrNull } from 'vs/base/common/types';
import { isUndefinedOrNull, assertIsDefined } 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 { MenuBarVisibility } from 'vs/platform/windows/common/windows';
import { getMenuBarVisibility } from 'vs/platform/windows/common/windows';
import { isWeb } from 'vs/base/common/platform';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
interface ICachedViewlet {
id: string;
@@ -65,17 +66,17 @@ export class ActivitybarPart extends Part implements IActivityBarService {
//#endregion
private globalActivityAction: ActivityAction;
private globalActivityActionBar: ActionBar;
private globalActivityAction: ActivityAction | undefined;
private globalActivityActionBar: ActionBar | undefined;
private customMenubar: CustomMenubarControl | undefined;
private menubar: HTMLElement | undefined;
private content: HTMLElement;
private content: HTMLElement | undefined;
private cachedViewlets: ICachedViewlet[] = [];
private compositeBar: CompositeBar;
private compositeActions: Map<string, { activityAction: ViewletActivityAction, pinnedAction: ToggleCompositePinnedAction }> = new Map();
private readonly compositeActions: Map<string, { activityAction: ViewletActivityAction, pinnedAction: ToggleCompositePinnedAction }> = new Map();
private readonly viewletDisposables: Map<string, IDisposable> = new Map<string, IDisposable>();
@@ -89,7 +90,8 @@ export class ActivitybarPart extends Part implements IActivityBarService {
@IViewsService private readonly viewsService: IViewsService,
@IContextKeyService private readonly contextKeyService: IContextKeyService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IWorkbenchEnvironmentService workbenchEnvironmentService: IWorkbenchEnvironmentService
@IWorkbenchEnvironmentService workbenchEnvironmentService: IWorkbenchEnvironmentService,
@IEnvironmentService private readonly environmentService: IEnvironmentService
) {
super(Parts.ACTIVITYBAR_PART, { hasTitle: false }, themeService, storageService, layoutService);
@@ -110,8 +112,18 @@ export class ActivitybarPart extends Part implements IActivityBarService {
openComposite: (compositeId: string) => this.viewletService.openViewlet(compositeId, true),
getActivityAction: (compositeId: string) => this.getCompositeActions(compositeId).activityAction,
getCompositePinnedAction: (compositeId: string) => this.getCompositeActions(compositeId).pinnedAction,
getOnCompositeClickAction: (compositeId: string) => this.instantiationService.createInstance(ToggleViewletAction, this.viewletService.getViewlet(compositeId)),
getContextMenuActions: () => [this.instantiationService.createInstance(ToggleActivityBarVisibilityAction, ToggleActivityBarVisibilityAction.ID, nls.localize('hideActivitBar', "Hide Activity Bar"))],
getOnCompositeClickAction: (compositeId: string) => this.instantiationService.createInstance(ToggleViewletAction, assertIsDefined(this.viewletService.getViewlet(compositeId))),
getContextMenuActions: () => {
const menuBarVisibility = getMenuBarVisibility(this.configurationService, this.environmentService);
const actions = [];
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")));
}
actions.push(this.instantiationService.createInstance(ToggleActivityBarVisibilityAction, ToggleActivityBarVisibilityAction.ID, nls.localize('hideActivitBar', "Hide Activity Bar")));
return actions;
},
getDefaultCompositeId: () => this.viewletService.getDefaultViewletId(),
hidePart: () => this.layoutService.setSideBarHidden(true),
compositeSize: 50,
@@ -147,7 +159,7 @@ export class ActivitybarPart extends Part implements IActivityBarService {
// Register for configuration changes
this._register(this.configurationService.onDidChangeConfiguration(e => {
if (e.affectsConfiguration('window.menuBarVisibility')) {
if (this.configurationService.getValue<MenuBarVisibility>('window.menuBarVisibility') === 'compact') {
if (getMenuBarVisibility(this.configurationService, this.environmentService) === 'compact') {
this.installMenubar();
} else {
this.uninstallMenubar();
@@ -169,13 +181,15 @@ export class ActivitybarPart extends Part implements IActivityBarService {
if (foundViewlet) {
this.compositeBar.addComposite(foundViewlet);
}
this.compositeBar.activateComposite(viewlet.getId());
const viewletDescriptor = this.viewletService.getViewlet(viewlet.getId());
if (viewletDescriptor) {
const viewContainer = this.getViewContainer(viewletDescriptor.id);
if (viewContainer && viewContainer.hideIfEmpty) {
if (viewContainer?.hideIfEmpty) {
const viewDescriptors = this.viewsService.getViewDescriptors(viewContainer);
if (viewDescriptors && viewDescriptors.activeViewDescriptors.length === 0) {
if (viewDescriptors?.activeViewDescriptors.length === 0) {
this.hideComposite(viewletDescriptor.id); // Update the composite bar by hiding
}
}
@@ -191,13 +205,15 @@ export class ActivitybarPart extends Part implements IActivityBarService {
return this.showGlobalActivity(badge, clazz);
}
throw illegalArgument('globalActivityId');
return Disposable.None;
}
private showGlobalActivity(badge: IBadge, clazz?: string): IDisposable {
this.globalActivityAction.setBadge(badge, clazz);
const globalActivityAction = assertIsDefined(this.globalActivityAction);
return toDisposable(() => this.globalActivityAction.setBadge(undefined));
globalActivityAction.setBadge(badge, clazz);
return toDisposable(() => globalActivityAction.setBadge(undefined));
}
private uninstallMenubar() {
@@ -213,12 +229,13 @@ export class ActivitybarPart extends Part implements IActivityBarService {
private installMenubar() {
this.menubar = document.createElement('div');
addClass(this.menubar, 'menubar');
this.content.prepend(this.menubar);
const content = assertIsDefined(this.content);
content.prepend(this.menubar);
// Menubar: install a custom menu bar depending on configuration
this.customMenubar = this._register(this.instantiationService.createInstance(CustomMenubarControl));
this.customMenubar.create(this.menubar);
}
createContentArea(parent: HTMLElement): HTMLElement {
@@ -229,7 +246,7 @@ export class ActivitybarPart extends Part implements IActivityBarService {
parent.appendChild(this.content);
// Install menubar if compact
if (this.configurationService.getValue<MenuBarVisibility>('window.menuBarVisibility') === 'compact') {
if (getMenuBarVisibility(this.configurationService, this.environmentService) === 'compact') {
this.installMenubar();
}
@@ -243,7 +260,7 @@ export class ActivitybarPart extends Part implements IActivityBarService {
this.createGlobalActivityActionBar(globalActivities);
this.element.style.display = this.layoutService.isVisible(Parts.ACTIVITYBAR_PART) ? null : 'none';
this.element.style.display = this.layoutService.isVisible(Parts.ACTIVITYBAR_PART) ? '' : 'none';
return this.content;
}
@@ -252,25 +269,27 @@ export class ActivitybarPart extends Part implements IActivityBarService {
super.updateStyles();
// Part container
const container = this.getContainer();
const background = this.getColor(ACTIVITY_BAR_BACKGROUND);
const container = assertIsDefined(this.getContainer());
const background = this.getColor(ACTIVITY_BAR_BACKGROUND) || '';
container.style.backgroundColor = background;
const borderColor = this.getColor(ACTIVITY_BAR_BORDER) || this.getColor(contrastBorder);
const borderColor = this.getColor(ACTIVITY_BAR_BORDER) || this.getColor(contrastBorder) || '';
const isPositionLeft = this.layoutService.getSideBarPosition() === SideBarPosition.LEFT;
container.style.boxSizing = borderColor && isPositionLeft ? 'border-box' : '';
container.style.borderRightWidth = borderColor && isPositionLeft ? '1px' : null;
container.style.borderRightStyle = borderColor && isPositionLeft ? 'solid' : null;
container.style.borderRightColor = isPositionLeft ? borderColor : null;
container.style.borderLeftWidth = borderColor && !isPositionLeft ? '1px' : null;
container.style.borderLeftStyle = borderColor && !isPositionLeft ? 'solid' : null;
container.style.borderLeftColor = !isPositionLeft ? borderColor : null;
container.style.borderRightWidth = borderColor && isPositionLeft ? '1px' : '';
container.style.borderRightStyle = borderColor && isPositionLeft ? 'solid' : '';
container.style.borderRightColor = isPositionLeft ? borderColor : '';
container.style.borderLeftWidth = borderColor && !isPositionLeft ? '1px' : '';
container.style.borderLeftStyle = borderColor && !isPositionLeft ? 'solid' : '';
container.style.borderLeftColor = !isPositionLeft ? borderColor : '';
}
private getActivitybarItemColors(theme: ITheme): ICompositeBarColors {
return {
activeForegroundColor: theme.getColor(ACTIVITY_BAR_FOREGROUND),
inactiveForegroundColor: theme.getColor(ACTIVITY_BAR_INACTIVE_FOREGROUND),
activeBorderColor: theme.getColor(ACTIVITY_BAR_ACTIVE_BORDER),
activeBackground: theme.getColor(ACTIVITY_BAR_ACTIVE_BACKGROUND),
badgeBackground: theme.getColor(ACTIVITY_BAR_BADGE_BACKGROUND),
badgeForeground: theme.getColor(ACTIVITY_BAR_BADGE_FOREGROUND),
dragAndDropBackground: theme.getColor(ACTIVITY_BAR_DRAG_AND_DROP_BACKGROUND),
@@ -280,7 +299,7 @@ export class ActivitybarPart extends Part implements IActivityBarService {
private createGlobalActivityActionBar(container: HTMLElement): void {
this.globalActivityActionBar = this._register(new ActionBar(container, {
actionViewItemProvider: a => this.instantiationService.createInstance(GlobalActivityActionViewItem, a, (theme: ITheme) => this.getActivitybarItemColors(theme)),
actionViewItemProvider: action => this.instantiationService.createInstance(GlobalActivityActionViewItem, action as ActivityAction, (theme: ITheme) => this.getActivitybarItemColors(theme)),
orientation: ActionsOrientation.VERTICAL,
ariaLabel: nls.localize('manage', "Manage"),
animated: false
@@ -291,6 +310,7 @@ export class ActivitybarPart extends Part implements IActivityBarService {
name: nls.localize('manage', "Manage"),
cssClass: 'update-activity'
});
this.globalActivityActionBar.push(this.globalActivityAction);
}
@@ -306,7 +326,7 @@ export class ActivitybarPart extends Part implements IActivityBarService {
} else {
const cachedComposite = this.cachedViewlets.filter(c => c.id === compositeId)[0];
compositeActions = {
activityAction: this.instantiationService.createInstance(PlaceHolderViewletActivityAction, compositeId, cachedComposite && cachedComposite.name ? cachedComposite.name : compositeId, cachedComposite && cachedComposite.iconUrl ? URI.revive(cachedComposite.iconUrl) : undefined),
activityAction: this.instantiationService.createInstance(PlaceHolderViewletActivityAction, compositeId, cachedComposite?.name || compositeId, cachedComposite?.iconUrl ? URI.revive(cachedComposite.iconUrl) : undefined),
pinnedAction: new PlaceHolderToggleCompositePinnedAction(compositeId, this.compositeBar)
};
}
@@ -321,7 +341,7 @@ export class ActivitybarPart extends Part implements IActivityBarService {
for (const viewlet of viewlets) {
const cachedViewlet = this.cachedViewlets.filter(({ id }) => id === viewlet.id)[0];
const activeViewlet = this.viewletService.getActiveViewlet();
const isActive = activeViewlet && activeViewlet.getId() === viewlet.id;
const isActive = activeViewlet?.getId() === viewlet.id;
if (isActive || !this.shouldBeHidden(viewlet.id, cachedViewlet)) {
this.compositeBar.addComposite(viewlet);
@@ -336,10 +356,11 @@ export class ActivitybarPart extends Part implements IActivityBarService {
}
}
}
for (const viewlet of viewlets) {
this.enableCompositeActions(viewlet);
const viewContainer = this.getViewContainer(viewlet.id);
if (viewContainer && viewContainer.hideIfEmpty) {
if (viewContainer?.hideIfEmpty) {
const viewDescriptors = this.viewsService.getViewDescriptors(viewContainer);
if (viewDescriptors) {
this.onDidChangeActiveViews(viewlet, viewDescriptors);
@@ -354,6 +375,7 @@ export class ActivitybarPart extends Part implements IActivityBarService {
if (disposable) {
disposable.dispose();
}
this.viewletDisposables.delete(viewletId);
this.hideComposite(viewletId);
}
@@ -371,7 +393,8 @@ export class ActivitybarPart extends Part implements IActivityBarService {
if (!viewContainer || !viewContainer.hideIfEmpty) {
return false;
}
return cachedViewlet && cachedViewlet.views && cachedViewlet.views.length
return cachedViewlet?.views && cachedViewlet.views.length
? cachedViewlet.views.every(({ when }) => !!when && !this.contextKeyService.contextMatchesRules(ContextKeyExpr.deserialize(when)))
: viewletId === TEST_VIEW_CONTAINER_ID /* Hide Test viewlet for the first time or it had no views registered before */;
}
@@ -387,6 +410,7 @@ export class ActivitybarPart extends Part implements IActivityBarService {
private hideComposite(compositeId: string): void {
this.compositeBar.hideComposite(compositeId);
const compositeActions = this.compositeActions.get(compositeId);
if (compositeActions) {
compositeActions.activityAction.dispose();
@@ -415,12 +439,6 @@ export class ActivitybarPart extends Part implements IActivityBarService {
.map(v => v.id);
}
setVisible(visible: boolean): void {
if (this.element) {
this.element.style.display = visible ? null : 'none';
}
}
layout(width: number, height: number): void {
if (!this.layoutService.isVisible(Parts.ACTIVITYBAR_PART)) {
return;
@@ -440,7 +458,7 @@ export class ActivitybarPart extends Part implements IActivityBarService {
private onDidStorageChange(e: IWorkspaceStorageChangeEvent): void {
if (e.key === ActivitybarPart.PINNED_VIEWLETS && e.scope === StorageScope.GLOBAL
&& this.cachedViewletsValue !== this.getStoredCachedViewletsValue() /* This checks if current window changed the value or not */) {
this._cachedViewletsValue = null;
this._cachedViewletsValue = undefined;
const newCompositeItems: ICompositeBarItem[] = [];
const compositeItems = this.compositeBar.getCompositeBarItems();
const cachedViewlets = this.getCachedViewlets();
@@ -525,7 +543,7 @@ export class ActivitybarPart extends Part implements IActivityBarService {
return result;
}
private _cachedViewletsValue: string | null;
private _cachedViewletsValue: string | undefined;
private get cachedViewletsValue(): string {
if (!this._cachedViewletsValue) {
this._cachedViewletsValue = this.getStoredCachedViewletsValue();

View File

@@ -10,6 +10,8 @@
}
.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-label {
position: relative;
z-index: 1;
display: flex;
overflow: hidden;
height: 40px;
@@ -20,19 +22,41 @@
font-size: 15px;
}
.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.checked .active-item-indicator:before,
.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item:focus:before {
content: "";
position: absolute;
top: 9px;
height: 32px;
z-index: 1;
top: 5px;
height: 40px;
width: 0;
border-left: 2px solid;
}
.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.checked .active-item-indicator:before {
top: 0;
height: 100%;
}
.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.checked:focus .active-item-indicator:before {
border-left: none; /* don't show active border + focus at the same time, focus takes priority */
}
/* Hides active elements in high contrast mode */
.hc-black .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.checked .active-item-indicator {
display: none;
}
.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.clicked:focus:before {
border-left: none !important; /* no focus feedback when using mouse */
}
.monaco-workbench .activitybar.left > .content :not(.monaco-menu) > .monaco-action-bar .action-item.checked .active-item-indicator:before{
left: 0;
}
.monaco-workbench .activitybar.left > .content :not(.monaco-menu) > .monaco-action-bar .action-item:focus:before {
left: 1px;
}
@@ -41,8 +65,20 @@
right: 1px;
}
.monaco-workbench .activitybar.right > .content :not(.monaco-menu) > .monaco-action-bar .action-item.checked .active-item-indicator:before {
right: 2px;
}
/* Hides outline on HC as focus is handled by border */
.hc-black .monaco-workbench .activitybar.left > .content :not(.monaco-menu) > .monaco-action-bar .action-item:focus:before,
.hc-black .monaco-workbench .activitybar.right > .content :not(.monaco-menu) > .monaco-action-bar .action-item:focus:before {
outline: none;
}
.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .active-item-indicator,
.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .badge {
position: absolute;
z-index: 1;
top: 5px;
left: 0;
overflow: hidden;

View File

@@ -30,11 +30,12 @@ export interface ICompositeBarItem {
}
export interface ICompositeBarOptions {
icon: boolean;
orientation: ActionsOrientation;
colors: (theme: ITheme) => ICompositeBarColors;
compositeSize: number;
overflowActionSize: number;
readonly icon: boolean;
readonly orientation: ActionsOrientation;
readonly colors: (theme: ITheme) => ICompositeBarColors;
readonly compositeSize: number;
readonly overflowActionSize: number;
getActivityAction: (compositeId: string) => ActivityAction;
getCompositePinnedAction: (compositeId: string) => Action;
getOnCompositeClickAction: (compositeId: string) => Action;
@@ -48,7 +49,7 @@ export class CompositeBar extends Widget implements ICompositeBar {
private dimension: Dimension | undefined;
private compositeSwitcherBar!: ActionBar;
private compositeSwitcherBar: ActionBar | undefined;
private compositeOverflowAction: CompositeOverflowActivityAction | undefined;
private compositeOverflowActionViewItem: CompositeOverflowActivityActionViewItem | undefined;
@@ -92,13 +93,14 @@ 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: Action) => {
if (action instanceof CompositeOverflowActivityAction) {
return this.compositeOverflowActionViewItem;
}
const item = this.model.findItem(action.id);
return item && this.instantiationService.createInstance(CompositeActionViewItem, action, item.pinnedAction, () => this.getContextMenuActions(), this.options.colors, this.options.icon, this);
return item && this.instantiationService.createInstance(CompositeActionViewItem, action as ActivityAction, item.pinnedAction, () => this.getContextMenuActions() as Action[], this.options.colors, this.options.icon, this);
},
orientation: this.options.orientation,
ariaLabel: nls.localize('activityBarAriaLabel', "Active View Switcher"),
@@ -113,12 +115,15 @@ export class CompositeBar extends Widget implements ICompositeBar {
if (this.compositeTransfer.hasData(DraggedCompositeIdentifier.prototype)) {
EventHelper.stop(e, true);
const draggedCompositeId = this.compositeTransfer.getData(DraggedCompositeIdentifier.prototype)![0].id;
this.compositeTransfer.clearData(DraggedCompositeIdentifier.prototype);
const data = this.compositeTransfer.getData(DraggedCompositeIdentifier.prototype);
if (Array.isArray(data)) {
const draggedCompositeId = data[0].id;
this.compositeTransfer.clearData(DraggedCompositeIdentifier.prototype);
const targetItem = this.model.visibleItems[this.model.visibleItems.length - 1];
if (targetItem && targetItem.id !== draggedCompositeId) {
this.move(draggedCompositeId, targetItem.id);
const targetItem = this.model.visibleItems[this.model.visibleItems.length - 1];
if (targetItem && targetItem.id !== draggedCompositeId) {
this.move(draggedCompositeId, targetItem.id);
}
}
}
}));
@@ -258,7 +263,7 @@ export class CompositeBar extends Widget implements ICompositeBar {
isPinned(compositeId: string): boolean {
const item = this.model.findItem(compositeId);
return item && item.pinned;
return item?.pinned;
}
move(compositeId: string, toCompositeId: string): void {
@@ -270,7 +275,7 @@ export class CompositeBar extends Widget implements ICompositeBar {
getAction(compositeId: string): ActivityAction {
const item = this.model.findItem(compositeId);
return item && item.activityAction;
return item?.activityAction;
}
private computeSizes(items: ICompositeBarModelItem[]): void {
@@ -278,21 +283,23 @@ export class CompositeBar extends Widget implements ICompositeBar {
if (size) {
items.forEach(composite => this.compositeSizeInBar.set(composite.id, size));
} else {
if (this.dimension && this.dimension.height !== 0 && this.dimension.width !== 0) {
const compositeSwitcherBar = this.compositeSwitcherBar;
if (compositeSwitcherBar && this.dimension && this.dimension.height !== 0 && this.dimension.width !== 0) {
// Compute sizes only if visible. Otherwise the size measurment would be computed wrongly.
const currentItemsLength = this.compositeSwitcherBar.viewItems.length;
this.compositeSwitcherBar.push(items.map(composite => composite.activityAction));
const currentItemsLength = compositeSwitcherBar.viewItems.length;
compositeSwitcherBar.push(items.map(composite => composite.activityAction));
items.map((composite, index) => this.compositeSizeInBar.set(composite.id, this.options.orientation === ActionsOrientation.VERTICAL
? this.compositeSwitcherBar.getHeight(currentItemsLength + index)
: this.compositeSwitcherBar.getWidth(currentItemsLength + index)
? compositeSwitcherBar.getHeight(currentItemsLength + index)
: compositeSwitcherBar.getWidth(currentItemsLength + index)
));
items.forEach(() => this.compositeSwitcherBar.pull(this.compositeSwitcherBar.viewItems.length - 1));
items.forEach(() => compositeSwitcherBar.pull(compositeSwitcherBar.viewItems.length - 1));
}
}
}
private updateCompositeSwitcher(): void {
if (!this.compositeSwitcherBar || !this.dimension) {
const compositeSwitcherBar = this.compositeSwitcherBar;
if (!compositeSwitcherBar || !this.dimension) {
return; // We have not been rendered yet so there is nothing to update.
}
@@ -340,7 +347,7 @@ export class CompositeBar extends Widget implements ICompositeBar {
// Pull out overflow action if there is a composite change so that we can add it to the end later
if (this.compositeOverflowAction && visibleCompositesChange) {
this.compositeSwitcherBar.pull(this.compositeSwitcherBar.length() - 1);
compositeSwitcherBar.pull(compositeSwitcherBar.length() - 1);
this.compositeOverflowAction.dispose();
this.compositeOverflowAction = undefined;
@@ -359,8 +366,8 @@ export class CompositeBar extends Widget implements ICompositeBar {
}
});
compositesToRemove.reverse().forEach(index => {
const actionViewItem = this.compositeSwitcherBar.viewItems[index];
this.compositeSwitcherBar.pull(index);
const actionViewItem = compositeSwitcherBar.viewItems[index];
compositeSwitcherBar.pull(index);
actionViewItem.dispose();
this.visibleComposites.splice(index, 1);
});
@@ -370,19 +377,19 @@ export class CompositeBar extends Widget implements ICompositeBar {
const currentIndex = this.visibleComposites.indexOf(compositeId);
if (newIndex !== currentIndex) {
if (currentIndex !== -1) {
const actionViewItem = this.compositeSwitcherBar.viewItems[currentIndex];
this.compositeSwitcherBar.pull(currentIndex);
const actionViewItem = compositeSwitcherBar.viewItems[currentIndex];
compositeSwitcherBar.pull(currentIndex);
actionViewItem.dispose();
this.visibleComposites.splice(currentIndex, 1);
}
this.compositeSwitcherBar.push(this.model.findItem(compositeId).activityAction, { label: true, icon: this.options.icon, index: newIndex });
compositeSwitcherBar.push(this.model.findItem(compositeId).activityAction, { label: true, icon: this.options.icon, index: newIndex });
this.visibleComposites.splice(newIndex, 0, compositeId);
}
});
// Add overflow action as needed
if ((visibleCompositesChange && overflows) || this.compositeSwitcherBar.length() === 0) {
if ((visibleCompositesChange && overflows) || compositeSwitcherBar.length() === 0) {
this.compositeOverflowAction = this.instantiationService.createInstance(CompositeOverflowActivityAction, () => {
if (this.compositeOverflowActionViewItem) {
this.compositeOverflowActionViewItem.showMenu();
@@ -395,13 +402,13 @@ export class CompositeBar extends Widget implements ICompositeBar {
() => this.model.activeItem ? this.model.activeItem.id : undefined,
(compositeId: string) => {
const item = this.model.findItem(compositeId);
return item && item.activity[0] && item.activity[0].badge;
return item?.activity?.[0].badge;
},
this.options.getOnCompositeClickAction,
this.options.colors
);
this.compositeSwitcherBar.push(this.compositeOverflowAction, { label: false, icon: true });
compositeSwitcherBar.push(this.compositeOverflowAction, { label: false, icon: true });
}
this._onDidChange.fire();

View File

@@ -57,7 +57,7 @@ export class ActivityAction extends Action {
private readonly _onDidChangeBadge = new Emitter<this>();
readonly onDidChangeBadge: Event<this> = this._onDidChangeBadge.event;
private badge?: IBadge;
private badge: IBadge | undefined;
private clazz: string | undefined;
constructor(private _activity: IActivity) {
@@ -110,6 +110,8 @@ export class ActivityAction extends Action {
export interface ICompositeBarColors {
activeBackgroundColor?: Color;
inactiveBackgroundColor?: Color;
activeBorderColor?: Color;
activeBackground?: Color;
activeBorderBottomColor?: Color;
activeForegroundColor?: Color;
inactiveForegroundColor?: Color;
@@ -156,12 +158,12 @@ export class ActivityActionViewItem extends BaseActionViewItem {
if (this.label) {
if (this.options.icon) {
const foreground = this._action.checked ? colors.activeBackgroundColor || colors.activeForegroundColor : colors.inactiveBackgroundColor || colors.inactiveForegroundColor;
this.label.style.backgroundColor = foreground ? foreground.toString() : null;
this.label.style.backgroundColor = foreground ? foreground.toString() : '';
} else {
const foreground = this._action.checked ? colors.activeForegroundColor : colors.inactiveForegroundColor;
const borderBottomColor = this._action.checked ? colors.activeBorderBottomColor : null;
this.label.style.color = foreground ? foreground.toString() : null;
this.label.style.borderBottomColor = borderBottomColor ? borderBottomColor.toString() : null;
this.label.style.borderBottomColor = borderBottomColor ? borderBottomColor.toString() : '';
}
}
@@ -172,11 +174,11 @@ export class ActivityActionViewItem extends BaseActionViewItem {
const contrastBorderColor = theme.getColor(contrastBorder);
this.badgeContent.style.color = badgeForeground ? badgeForeground.toString() : null;
this.badgeContent.style.backgroundColor = badgeBackground ? badgeBackground.toString() : null;
this.badgeContent.style.backgroundColor = badgeBackground ? badgeBackground.toString() : '';
this.badgeContent.style.borderStyle = contrastBorderColor ? 'solid' : null;
this.badgeContent.style.borderWidth = contrastBorderColor ? '1px' : null;
this.badgeContent.style.borderColor = contrastBorderColor ? contrastBorderColor.toString() : null;
this.badgeContent.style.borderStyle = contrastBorderColor ? 'solid' : '';
this.badgeContent.style.borderWidth = contrastBorderColor ? '1px' : '';
this.badgeContent.style.borderColor = contrastBorderColor ? contrastBorderColor.toString() : '';
}
}
@@ -205,12 +207,18 @@ export class ActivityActionViewItem extends BaseActionViewItem {
}));
// Label
this.label = dom.append(this.element!, dom.$('a'));
this.label = dom.append(container, dom.$('a'));
// Badge
this.badge = dom.append(this.element!, dom.$('.badge'));
this.badge = dom.append(container, dom.$('.badge'));
this.badgeContent = dom.append(this.badge, dom.$('.badge-content'));
// Activity bar active border + background
const isActivityBarItem = this.options.icon;
if (isActivityBarItem) {
dom.append(container, dom.$('.active-item-indicator'));
}
dom.hide(this.badge);
this.updateActivity();
@@ -285,7 +293,7 @@ export class ActivityActionViewItem extends BaseActionViewItem {
// Title
let title: string;
if (badge && badge.getDescription()) {
if (badge?.getDescription()) {
if (this.activity.name) {
title = nls.localize('badgeTitle', "{0} - {1}", this.activity.name, badge.getDescription());
} else {
@@ -294,14 +302,17 @@ export class ActivityActionViewItem extends BaseActionViewItem {
} else {
title = this.activity.name;
}
this.updateTitle(title);
}
protected updateLabel(): void {
this.label.className = 'action-label';
if (this.activity.cssClass) {
dom.addClass(this.label, this.activity.cssClass);
}
if (!this.options.icon) {
this.label.textContent = this.getAction().label;
}
@@ -347,12 +358,12 @@ export class CompositeOverflowActivityAction extends ActivityAction {
}
export class CompositeOverflowActivityActionViewItem extends ActivityActionViewItem {
private actions: Action[] | undefined;
private actions: Action[] = [];
constructor(
action: ActivityAction,
private getOverflowingComposites: () => { id: string, name: string }[],
private getActiveCompositeId: () => string,
private getOverflowingComposites: () => { id: string, name?: string }[],
private getActiveCompositeId: () => string | undefined,
private getBadge: (compositeId: string) => IBadge,
private getCompositeOpenAction: (compositeId: string) => Action,
colors: (theme: ITheme) => ICompositeBarColors,
@@ -370,16 +381,17 @@ export class CompositeOverflowActivityActionViewItem extends ActivityActionViewI
this.actions = this.getActions();
this.contextMenuService.showContextMenu({
getAnchor: () => this.element!,
getActions: () => this.actions!,
onHide: () => dispose(this.actions!)
getAnchor: () => this.container,
getActions: () => this.actions,
getCheckedActionsRepresentation: () => 'radio',
onHide: () => dispose(this.actions)
});
}
private getActions(): Action[] {
return this.getOverflowingComposites().map(composite => {
const action = this.getCompositeOpenAction(composite.id);
action.radio = this.getActiveCompositeId() === action.id;
action.checked = this.getActiveCompositeId() === action.id;
const badge = this.getBadge(composite.id);
let suffix: string | number | undefined;
@@ -392,7 +404,7 @@ export class CompositeOverflowActivityActionViewItem extends ActivityActionViewI
if (suffix) {
action.label = nls.localize('numberBadge', "{0} ({1})", composite.name, suffix);
} else {
action.label = composite.name;
action.label = composite.name || '';
}
return action;
@@ -439,7 +451,7 @@ export class CompositeActionViewItem extends ActivityActionViewItem {
constructor(
private compositeActivityAction: ActivityAction,
private toggleCompositePinnedAction: Action,
private contextMenuActionsProvider: () => Action[],
private contextMenuActionsProvider: () => ReadonlyArray<Action>,
colors: (theme: ITheme) => ICompositeBarColors,
icon: boolean,
private compositeBar: ICompositeBar,
@@ -502,7 +514,9 @@ export class CompositeActionViewItem extends ActivityActionViewItem {
// Allow to drag
this._register(dom.addDisposableListener(this.container, dom.EventType.DRAG_START, (e: DragEvent) => {
e.dataTransfer!.effectAllowed = 'move';
if (e.dataTransfer) {
e.dataTransfer.effectAllowed = 'move';
}
// Registe as dragged to local transfer
this.compositeTransfer.setData([new DraggedCompositeIdentifier(this.activity.id)], DraggedCompositeIdentifier.prototype);
@@ -515,8 +529,11 @@ export class CompositeActionViewItem extends ActivityActionViewItem {
this._register(new DragAndDropObserver(this.container, {
onDragEnter: e => {
if (this.compositeTransfer.hasData(DraggedCompositeIdentifier.prototype) && this.compositeTransfer.getData(DraggedCompositeIdentifier.prototype)![0].id !== this.activity.id) {
this.updateFromDragging(container, true);
if (this.compositeTransfer.hasData(DraggedCompositeIdentifier.prototype)) {
const data = this.compositeTransfer.getData(DraggedCompositeIdentifier.prototype);
if (Array.isArray(data) && data[0].id !== this.activity.id) {
this.updateFromDragging(container, true);
}
}
},
@@ -538,12 +555,15 @@ export class CompositeActionViewItem extends ActivityActionViewItem {
dom.EventHelper.stop(e, true);
if (this.compositeTransfer.hasData(DraggedCompositeIdentifier.prototype)) {
const draggedCompositeId = this.compositeTransfer.getData(DraggedCompositeIdentifier.prototype)![0].id;
if (draggedCompositeId !== this.activity.id) {
this.updateFromDragging(container, false);
this.compositeTransfer.clearData(DraggedCompositeIdentifier.prototype);
const data = this.compositeTransfer.getData(DraggedCompositeIdentifier.prototype);
if (Array.isArray(data)) {
const draggedCompositeId = data[0].id;
if (draggedCompositeId !== this.activity.id) {
this.updateFromDragging(container, false);
this.compositeTransfer.clearData(DraggedCompositeIdentifier.prototype);
this.compositeBar.move(draggedCompositeId, this.activity.id);
this.compositeBar.move(draggedCompositeId, this.activity.id);
}
}
}
}
@@ -563,7 +583,7 @@ export class CompositeActionViewItem extends ActivityActionViewItem {
const theme = this.themeService.getTheme();
const dragBackground = this.options.colors(theme).dragAndDropBackground;
element.style.backgroundColor = isDragging && dragBackground ? dragBackground.toString() : null;
element.style.backgroundColor = isDragging && dragBackground ? dragBackground.toString() : '';
}
private showContextMenu(container: HTMLElement): void {
@@ -595,8 +615,8 @@ export class CompositeActionViewItem extends ActivityActionViewItem {
this.contextMenuService.showContextMenu({
getAnchor: () => anchor,
getActionsContext: () => this.activity.id,
getActions: () => actions
getActions: () => actions,
getActionsContext: () => this.activity.id
});
}

View File

@@ -32,6 +32,7 @@ import { attachProgressBarStyler } from 'vs/platform/theme/common/styler';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { Dimension, append, $, addClass, hide, show, addClasses } from 'vs/base/browser/dom';
import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview';
import { assertIsDefined, withNullAsUndefined } from 'vs/base/common/types';
export interface ICompositeTitleLabel {
@@ -57,15 +58,15 @@ export abstract class CompositePart<T extends Composite> extends Part {
protected readonly onDidCompositeOpen = this._register(new Emitter<{ composite: IComposite, focus: boolean }>());
protected readonly onDidCompositeClose = this._register(new Emitter<IComposite>());
protected toolBar!: ToolBar;
protected toolBar: ToolBar | undefined;
private mapCompositeToCompositeContainer = new Map<string, HTMLElement>();
private mapActionsBindingToComposite = new Map<string, () => void>();
private activeComposite: Composite | null;
private activeComposite: Composite | undefined;
private lastActiveCompositeId: string;
private instantiatedCompositeItems: Map<string, CompositeItem>;
private titleLabel!: ICompositeTitleLabel;
private progressBar!: ProgressBar;
private titleLabel: ICompositeTitleLabel | undefined;
private progressBar: ProgressBar | undefined;
private contentAreaSize: Dimension | undefined;
private readonly telemetryActionsListener = this._register(new MutableDisposable());
private currentCompositeOpenToken: string | undefined;
@@ -90,7 +91,6 @@ export abstract class CompositePart<T extends Composite> extends Part {
) {
super(id, options, themeService, storageService, layoutService);
this.activeComposite = null;
this.instantiatedCompositeItems = new Map<string, CompositeItem>();
this.lastActiveCompositeId = storageService.get(activeCompositeSettingsKey, StorageScope.WORKSPACE, this.defaultCompositeId);
}
@@ -168,7 +168,7 @@ export abstract class CompositePart<T extends Composite> extends Part {
// Instantiate composite from registry otherwise
const compositeDescriptor = this.registry.getComposite(id);
if (compositeDescriptor) {
const compositeProgressIndicator = this.instantiationService.createInstance(CompositeProgressIndicator, this.progressBar, compositeDescriptor.id, isActive);
const compositeProgressIndicator = this.instantiationService.createInstance(CompositeProgressIndicator, this.progressBar, compositeDescriptor.id, !!isActive);
const compositeInstantiationService = this.instantiationService.createChild(new ServiceCollection(
[IEditorProgressService, compositeProgressIndicator] // provide the editor progress service for any editors instantiated within the composite
));
@@ -234,7 +234,8 @@ export abstract class CompositePart<T extends Composite> extends Part {
show(compositeContainer);
// Setup action runner
this.toolBar.actionRunner = composite.getActionRunner();
const toolBar = assertIsDefined(this.toolBar);
toolBar.actionRunner = composite.getActionRunner();
// Update title with composite title if it differs from descriptor
const descriptor = this.registry.getComposite(composite.getId());
@@ -251,7 +252,7 @@ export abstract class CompositePart<T extends Composite> extends Part {
actionsBinding();
// Action Run Handling
this.telemetryActionsListener.value = this.toolBar.actionRunner.onDidRun(e => {
this.telemetryActionsListener.value = toolBar.actionRunner.onDidRun(e => {
// Check for Error
if (e.error && !errors.isPromiseCanceledError(e.error)) {
@@ -310,9 +311,10 @@ export abstract class CompositePart<T extends Composite> extends Part {
const keybinding = this.keybindingService.lookupKeybinding(compositeId);
this.titleLabel.updateTitle(compositeId, compositeTitle, (keybinding && keybinding.getLabel()) || undefined);
this.titleLabel.updateTitle(compositeId, compositeTitle, withNullAsUndefined(keybinding?.getLabel()));
this.toolBar.setAriaLabel(nls.localize('ariaCompositeToolbarLabel', "{0} actions", compositeTitle));
const toolBar = assertIsDefined(this.toolBar);
toolBar.setAriaLabel(nls.localize('ariaCompositeToolbarLabel', "{0} actions", compositeTitle));
}
private collectCompositeActions(composite: Composite): () => void {
@@ -326,13 +328,14 @@ export abstract class CompositePart<T extends Composite> extends Part {
secondaryActions.push(...this.getSecondaryActions());
// Update context
this.toolBar.context = this.actionsContextProvider();
const toolBar = assertIsDefined(this.toolBar);
toolBar.context = this.actionsContextProvider();
// Return fn to set into toolbar
return this.toolBar.setActions(prepareActions(primaryActions), prepareActions(secondaryActions));
return toolBar.setActions(prepareActions(primaryActions), prepareActions(secondaryActions));
}
protected getActiveComposite(): IComposite | null {
protected getActiveComposite(): IComposite | undefined {
return this.activeComposite;
}
@@ -346,7 +349,7 @@ export abstract class CompositePart<T extends Composite> extends Part {
}
const composite = this.activeComposite;
this.activeComposite = null;
this.activeComposite = undefined;
const compositeContainer = this.mapCompositeToCompositeContainer.get(composite.getId());
@@ -360,10 +363,14 @@ export abstract class CompositePart<T extends Composite> extends Part {
}
// Clear any running Progress
this.progressBar.stop().hide();
if (this.progressBar) {
this.progressBar.stop().hide();
}
// Empty Actions
this.toolBar.setActions([])();
if (this.toolBar) {
this.toolBar.setActions([])();
}
this.onDidCompositeClose.fire(composite);
return composite;
@@ -413,7 +420,8 @@ export abstract class CompositePart<T extends Composite> extends Part {
super.updateStyles();
// Forward to title label
this.titleLabel.updateStyles();
const titleLabel = assertIsDefined(this.titleLabel);
titleLabel.updateStyles();
}
protected actionViewItemProvider(action: IAction): IActionViewItem | undefined {
@@ -446,10 +454,10 @@ export abstract class CompositePart<T extends Composite> extends Part {
return contentContainer;
}
getProgressIndicator(id: string): IProgressIndicator | null {
getProgressIndicator(id: string): IProgressIndicator | undefined {
const compositeItem = this.instantiatedCompositeItems.get(id);
return compositeItem ? compositeItem.progress : null;
return compositeItem ? compositeItem.progress : undefined;
}
protected getActions(): ReadonlyArray<IAction> {

View File

@@ -19,6 +19,7 @@ import { CancellationToken } from 'vs/base/common/cancellation';
import { dispose } from 'vs/base/common/lifecycle';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { assertIsDefined, assertAllDefined } from 'vs/base/common/types';
export interface IOpenCallbacks {
openInternal: (input: EditorInput, options: EditorOptions | undefined) => Promise<void>;
@@ -38,8 +39,8 @@ export abstract class BaseBinaryResourceEditor extends BaseEditor {
private callbacks: IOpenCallbacks;
private metadata: string | undefined;
private binaryContainer: HTMLElement;
private scrollbar: DomScrollableElement;
private binaryContainer: HTMLElement | undefined;
private scrollbar: DomScrollableElement | undefined;
private resourceViewerContext: ResourceViewerContext | undefined;
constructor(
@@ -91,7 +92,8 @@ export abstract class BaseBinaryResourceEditor extends BaseEditor {
this.resourceViewerContext.dispose();
}
this.resourceViewerContext = ResourceViewer.show({ name: model.getName(), resource: model.getResource(), size: model.getSize(), etag: model.getETag(), mime: model.getMime() }, this.binaryContainer, this.scrollbar, {
const [binaryContainer, scrollbar] = assertAllDefined(this.binaryContainer, this.scrollbar);
this.resourceViewerContext = ResourceViewer.show({ name: model.getName(), resource: model.getResource(), size: model.getSize(), etag: model.getETag(), mime: model.getMime() }, binaryContainer, scrollbar, {
openInternalClb: () => this.handleOpenInternalCallback(input, options),
openExternalClb: this.environmentService.configuration.remoteAuthority ? undefined : resource => this.callbacks.openExternal(resource),
metadataClb: meta => this.handleMetadataChanged(meta)
@@ -120,8 +122,10 @@ export abstract class BaseBinaryResourceEditor extends BaseEditor {
// Clear Meta
this.handleMetadataChanged(undefined);
// Clear Resource Viewer
clearNode(this.binaryContainer);
// Clear the rest
if (this.binaryContainer) {
clearNode(this.binaryContainer);
}
dispose(this.resourceViewerContext);
this.resourceViewerContext = undefined;
@@ -131,19 +135,24 @@ export abstract class BaseBinaryResourceEditor extends BaseEditor {
layout(dimension: Dimension): void {
// Pass on to Binary Container
size(this.binaryContainer, dimension.width, dimension.height);
this.scrollbar.scanDomNode();
const [binaryContainer, scrollbar] = assertAllDefined(this.binaryContainer, this.scrollbar);
size(binaryContainer, dimension.width, dimension.height);
scrollbar.scanDomNode();
if (this.resourceViewerContext && this.resourceViewerContext.layout) {
this.resourceViewerContext.layout(dimension);
}
}
focus(): void {
this.binaryContainer.focus();
const binaryContainer = assertIsDefined(this.binaryContainer);
binaryContainer.focus();
}
dispose(): void {
this.binaryContainer.remove();
if (this.binaryContainer) {
this.binaryContainer.remove();
}
dispose(this.resourceViewerContext);
this.resourceViewerContext = undefined;

View File

@@ -66,14 +66,14 @@ export abstract class BreadcrumbsConfig<T> {
// internal
}
static IsEnabled = BreadcrumbsConfig._stub<boolean>('breadcrumbs.enabled');
static UseQuickPick = BreadcrumbsConfig._stub<boolean>('breadcrumbs.useQuickPick');
static FilePath = BreadcrumbsConfig._stub<'on' | 'off' | 'last'>('breadcrumbs.filePath');
static SymbolPath = BreadcrumbsConfig._stub<'on' | 'off' | 'last'>('breadcrumbs.symbolPath');
static SymbolSortOrder = BreadcrumbsConfig._stub<'position' | 'name' | 'type'>('breadcrumbs.symbolSortOrder');
static Icons = BreadcrumbsConfig._stub<boolean>('breadcrumbs.icons');
static readonly IsEnabled = BreadcrumbsConfig._stub<boolean>('breadcrumbs.enabled');
static readonly UseQuickPick = BreadcrumbsConfig._stub<boolean>('breadcrumbs.useQuickPick');
static readonly FilePath = BreadcrumbsConfig._stub<'on' | 'off' | 'last'>('breadcrumbs.filePath');
static readonly SymbolPath = BreadcrumbsConfig._stub<'on' | 'off' | 'last'>('breadcrumbs.symbolPath');
static readonly SymbolSortOrder = BreadcrumbsConfig._stub<'position' | 'name' | 'type'>('breadcrumbs.symbolSortOrder');
static readonly Icons = BreadcrumbsConfig._stub<boolean>('breadcrumbs.icons');
static FileExcludes = BreadcrumbsConfig._stub<glob.IExpression>('files.exclude');
static readonly FileExcludes = BreadcrumbsConfig._stub<glob.IExpression>('files.exclude');
private static _stub<T>(name: string): { bindTo(service: IConfigurationService): BreadcrumbsConfig<T> } {
return {
@@ -166,6 +166,136 @@ Registry.as<IConfigurationRegistry>(Extensions.Configuration).registerConfigurat
description: localize('icons', "Render breadcrumb items with icons."),
type: 'boolean',
default: true
},
'breadcrumbs.filteredTypes.file': {
type: 'boolean',
default: true,
markdownDescription: localize('filteredTypes.file', "When set to `false` breadcrumbs never show `file`-symbols.")
},
'breadcrumbs.filteredTypes.module': {
type: 'boolean',
default: true,
markdownDescription: localize('filteredTypes.module', "When set to `false` breadcrumbs never show `module`-symbols.")
},
'breadcrumbs.filteredTypes.namespace': {
type: 'boolean',
default: true,
markdownDescription: localize('filteredTypes.namespace', "When set to `false` breadcrumbs never show `namespace`-symbols.")
},
'breadcrumbs.filteredTypes.package': {
type: 'boolean',
default: true,
markdownDescription: localize('filteredTypes.package', "When set to `false` breadcrumbs never show `package`-symbols.")
},
'breadcrumbs.filteredTypes.class': {
type: 'boolean',
default: true,
markdownDescription: localize('filteredTypes.class', "When set to `false` breadcrumbs never show `class`-symbols.")
},
'breadcrumbs.filteredTypes.method': {
type: 'boolean',
default: true,
markdownDescription: localize('filteredTypes.method', "When set to `false` breadcrumbs never show `method`-symbols.")
},
'breadcrumbs.filteredTypes.property': {
type: 'boolean',
default: true,
markdownDescription: localize('filteredTypes.property', "When set to `false` breadcrumbs never show `property`-symbols.")
},
'breadcrumbs.filteredTypes.field': {
type: 'boolean',
default: true,
markdownDescription: localize('filteredTypes.field', "When set to `false` breadcrumbs never show `field`-symbols.")
},
'breadcrumbs.filteredTypes.constructor': {
type: 'boolean',
default: true,
markdownDescription: localize('filteredTypes.constructor', "When set to `false` breadcrumbs never show `constructor`-symbols.")
},
'breadcrumbs.filteredTypes.enum': {
type: 'boolean',
default: true,
markdownDescription: localize('filteredTypes.enum', "When set to `false` breadcrumbs never show `enum`-symbols.")
},
'breadcrumbs.filteredTypes.interface': {
type: 'boolean',
default: true,
markdownDescription: localize('filteredTypes.interface', "When set to `false` breadcrumbs never show `interface`-symbols.")
},
'breadcrumbs.filteredTypes.function': {
type: 'boolean',
default: true,
markdownDescription: localize('filteredTypes.function', "When set to `false` breadcrumbs never show `function`-symbols.")
},
'breadcrumbs.filteredTypes.variable': {
type: 'boolean',
default: true,
markdownDescription: localize('filteredTypes.variable', "When set to `false` breadcrumbs never show `variable`-symbols.")
},
'breadcrumbs.filteredTypes.constant': {
type: 'boolean',
default: true,
markdownDescription: localize('filteredTypes.constant', "When set to `false` breadcrumbs never show `constant`-symbols.")
},
'breadcrumbs.filteredTypes.string': {
type: 'boolean',
default: true,
markdownDescription: localize('filteredTypes.string', "When set to `false` breadcrumbs never show `string`-symbols.")
},
'breadcrumbs.filteredTypes.number': {
type: 'boolean',
default: true,
markdownDescription: localize('filteredTypes.number', "When set to `false` breadcrumbs never show `number`-symbols.")
},
'breadcrumbs.filteredTypes.boolean': {
type: 'boolean',
default: true,
markdownDescription: localize('filteredTypes.boolean', "When set to `false` breadcrumbs never show `boolean`-symbols.")
},
'breadcrumbs.filteredTypes.array': {
type: 'boolean',
default: true,
markdownDescription: localize('filteredTypes.array', "When set to `false` breadcrumbs never show `array`-symbols.")
},
'breadcrumbs.filteredTypes.object': {
type: 'boolean',
default: true,
markdownDescription: localize('filteredTypes.object', "When set to `false` breadcrumbs never show `object`-symbols.")
},
'breadcrumbs.filteredTypes.key': {
type: 'boolean',
default: true,
markdownDescription: localize('filteredTypes.key', "When set to `false` breadcrumbs never show `key`-symbols.")
},
'breadcrumbs.filteredTypes.null': {
type: 'boolean',
default: true,
markdownDescription: localize('filteredTypes.null', "When set to `false` breadcrumbs never show `null`-symbols.")
},
'breadcrumbs.filteredTypes.enumMember': {
type: 'boolean',
default: true,
markdownDescription: localize('filteredTypes.enumMember', "When set to `false` breadcrumbs never show `enumMember`-symbols.")
},
'breadcrumbs.filteredTypes.struct': {
type: 'boolean',
default: true,
markdownDescription: localize('filteredTypes.struct', "When set to `false` breadcrumbs never show `struct`-symbols.")
},
'breadcrumbs.filteredTypes.event': {
type: 'boolean',
default: true,
markdownDescription: localize('filteredTypes.event', "When set to `false` breadcrumbs never show `event`-symbols.")
},
'breadcrumbs.filteredTypes.operator': {
type: 'boolean',
default: true,
markdownDescription: localize('filteredTypes.operator', "When set to `false` breadcrumbs never show `operator`-symbols.")
},
'breadcrumbs.filteredTypes.typeParameter': {
type: 'boolean',
default: true,
markdownDescription: localize('filteredTypes.typeParameter', "When set to `false` breadcrumbs never show `typeParameter`-symbols.")
}
}
});

View File

@@ -17,7 +17,7 @@ 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 { symbolKindToCssClass } from 'vs/editor/common/modes';
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';
@@ -45,7 +45,7 @@ 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 } from 'vs/base/common/types';
import { withNullAsUndefined, withUndefinedAsNull } from 'vs/base/common/types';
import { ILabelService } from 'vs/platform/label/common/label';
class Item extends BreadcrumbsItem {
@@ -109,7 +109,7 @@ class Item extends BreadcrumbsItem {
// symbol
if (this.options.showSymbolIcons) {
let icon = document.createElement('div');
icon.className = symbolKindToCssClass(this.element.symbol.kind);
icon.className = SymbolKinds.toCssClassName(this.element.symbol.kind);
container.appendChild(icon);
dom.addClass(container, 'shows-symbol-icon');
}
@@ -130,15 +130,15 @@ export interface IBreadcrumbsControlOptions {
export class BreadcrumbsControl {
static HEIGHT = 22;
static readonly HEIGHT = 22;
static readonly Payload_Reveal = {};
static readonly Payload_RevealAside = {};
static readonly Payload_Pick = {};
static CK_BreadcrumbsPossible = new RawContextKey('breadcrumbsPossible', false);
static CK_BreadcrumbsVisible = new RawContextKey('breadcrumbsVisible', false);
static CK_BreadcrumbsActive = new RawContextKey('breadcrumbsActive', false);
static readonly CK_BreadcrumbsPossible = new RawContextKey('breadcrumbsPossible', false);
static readonly CK_BreadcrumbsVisible = new RawContextKey('breadcrumbsVisible', false);
static readonly CK_BreadcrumbsActive = new RawContextKey('breadcrumbsActive', false);
private readonly _ckBreadcrumbsPossible: IContextKey<boolean>;
private readonly _ckBreadcrumbsVisible: IContextKey<boolean>;
@@ -246,7 +246,7 @@ export class BreadcrumbsControl {
const uri = input.getResource()!;
const editor = this._getActiveCodeEditor();
const model = new EditorBreadcrumbsModel(uri, editor, this._workspaceService, this._configurationService);
const model = new EditorBreadcrumbsModel(uri, editor, this._configurationService, this._workspaceService);
dom.toggleClass(this.domNode, 'relative-path', model.isRelative());
dom.toggleClass(this.domNode, 'backslash-path', this._labelService.getSeparator(uri.scheme, uri.authority) === '\\');
@@ -485,7 +485,7 @@ export class BreadcrumbsControl {
selection: Range.collapseToStart(element.symbol.selectionRange),
revealInCenterIfOutsideViewport: true
}
}, this._getActiveCodeEditor(), group === SIDE_GROUP);
}, withUndefinedAsNull(this._getActiveCodeEditor()), group === SIDE_GROUP);
}
}
}

View File

@@ -14,7 +14,7 @@ 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 { DocumentSymbolProviderRegistry, SymbolKinds } 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';
@@ -51,16 +51,15 @@ export class EditorBreadcrumbsModel {
constructor(
private readonly _uri: URI,
private readonly _editor: ICodeEditor | undefined,
@IConfigurationService private readonly _configurationService: IConfigurationService,
@IWorkspaceContextService workspaceService: IWorkspaceContextService,
@IConfigurationService configurationService: IConfigurationService,
) {
this._cfgFilePath = BreadcrumbsConfig.FilePath.bindTo(configurationService);
this._cfgSymbolPath = BreadcrumbsConfig.SymbolPath.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(this._uri, workspaceService);
this._bindToEditor();
this._onDidUpdate.fire(this);
@@ -138,6 +137,14 @@ export class EditorBreadcrumbsModel {
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 (e.affectsConfiguration('breadcrumbs.filteredTypes')) {
this._updateOutline(true);
}
}));
// update soon'ish as model content change
const updateSoon = new TimeoutTimer();
this._disposables.add(updateSoon);
@@ -221,7 +228,18 @@ export class EditorBreadcrumbsModel {
}
item = parent;
}
return chain.reverse();
let result: Array<OutlineGroup | OutlineElement> = [];
for (let i = chain.length - 1; i >= 0; i--) {
let element = chain[i];
if (
element instanceof OutlineElement
&& !this._configurationService.getValue<boolean>(`breadcrumbs.filteredTypes.${SymbolKinds.toString(element.symbol.kind)}`)
) {
break;
}
result.push(element);
}
return result;
}
private _updateOutlineElements(elements: Array<OutlineModel | OutlineGroup | OutlineElement>): void {

View File

@@ -26,7 +26,7 @@ import { BreadcrumbsConfig } from 'vs/workbench/browser/parts/editor/breadcrumbs
import { BreadcrumbElement, FileElement } from 'vs/workbench/browser/parts/editor/breadcrumbsModel';
import { IFileIconTheme, IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
import { IAsyncDataSource, ITreeRenderer, ITreeNode, ITreeFilter, TreeVisibility, ITreeSorter } from 'vs/base/browser/ui/tree/tree';
import { OutlineVirtualDelegate, OutlineGroupRenderer, OutlineElementRenderer, OutlineItemComparator, OutlineIdentityProvider, OutlineNavigationLabelProvider, OutlineDataSource, OutlineSortOrder } from 'vs/editor/contrib/documentSymbols/outlineTree';
import { OutlineVirtualDelegate, OutlineGroupRenderer, OutlineElementRenderer, OutlineItemComparator, OutlineIdentityProvider, OutlineNavigationLabelProvider, OutlineDataSource, OutlineSortOrder, OutlineFilter } from 'vs/editor/contrib/documentSymbols/outlineTree';
import { IIdentityProvider, IListVirtualDelegate, IKeyboardNavigationLabelProvider } from 'vs/base/browser/ui/list/list';
export function createBreadcrumbsPicker(instantiationService: IInstantiationService, parent: HTMLElement, element: BreadcrumbElement): BreadcrumbsPicker {
@@ -240,7 +240,7 @@ class FileRenderer implements ITreeRenderer<IFileStat | IWorkspaceFolder, FuzzyS
}
renderElement(node: ITreeNode<IWorkspaceFolder | IFileStat, [number, number, number]>, index: number, templateData: IResourceLabel): void {
const fileDecorations = this._configService.getValue<{ colors: boolean, badges: boolean }>('explorer.decorations');
const fileDecorations = this._configService.getValue<{ colors: boolean, badges: boolean; }>('explorer.decorations');
const { element } = node;
let resource: URI;
let fileKind: FileKind;
@@ -446,11 +446,13 @@ export class BreadcrumbsOutlinePicker extends BreadcrumbsPicker {
[new OutlineGroupRenderer(), this._instantiationService.createInstance(OutlineElementRenderer)],
new OutlineDataSource(),
{
collapseByDefault: true,
expandOnlyOnTwistieClick: true,
multipleSelectionSupport: false,
sorter: new OutlineItemComparator(this._getOutlineItemCompareType()),
identityProvider: new OutlineIdentityProvider(),
keyboardNavigationLabelProvider: new OutlineNavigationLabelProvider()
keyboardNavigationLabelProvider: new OutlineNavigationLabelProvider(),
filter: this._instantiationService.createInstance(OutlineFilter, 'breadcrumbs.filteredTypes')
}
);
}

View File

@@ -17,7 +17,7 @@ import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorIn
import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { TextDiffEditor } from 'vs/workbench/browser/parts/editor/textDiffEditor';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { ITextFileService, SUPPORTED_ENCODINGS } from 'vs/workbench/services/textfile/common/textfiles';
import { BinaryResourceDiffEditor } from 'vs/workbench/browser/parts/editor/binaryDiffEditor';
import { ChangeEncodingAction, ChangeEOLAction, ChangeModeAction, EditorStatus } from 'vs/workbench/browser/parts/editor/editorStatus';
import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions';
@@ -106,7 +106,7 @@ interface ISerializedUntitledEditorInput {
resource: string;
resourceJSON: object;
modeId: string | undefined;
encoding: string;
encoding: string | undefined;
}
// Register Editor Input Factory
@@ -223,7 +223,7 @@ class SideBySideEditorInputFactory implements IEditorInputFactory {
Registry.as<IEditorInputFactoryRegistry>(EditorInputExtensions.EditorInputFactories).registerEditorInputFactory(SideBySideEditorInput.ID, SideBySideEditorInputFactory);
// Register Editor Contributions
registerEditorContribution(OpenWorkspaceButtonContribution);
registerEditorContribution(OpenWorkspaceButtonContribution.ID, OpenWorkspaceButtonContribution);
// Register Editor Status
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(EditorStatus, LifecyclePhase.Ready);
@@ -232,7 +232,10 @@ Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).regi
const registry = Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions);
registry.registerWorkbenchAction(new SyncActionDescriptor(ChangeModeAction, ChangeModeAction.ID, ChangeModeAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_M) }), 'Change Language Mode');
registry.registerWorkbenchAction(new SyncActionDescriptor(ChangeEOLAction, ChangeEOLAction.ID, ChangeEOLAction.LABEL), 'Change End of Line Sequence');
registry.registerWorkbenchAction(new SyncActionDescriptor(ChangeEncodingAction, ChangeEncodingAction.ID, ChangeEncodingAction.LABEL), 'Change File Encoding');
if (Object.keys(SUPPORTED_ENCODINGS).length > 1) {
registry.registerWorkbenchAction(new SyncActionDescriptor(ChangeEncodingAction, ChangeEncodingAction.ID, ChangeEncodingAction.LABEL), 'Change File Encoding');
}
export class QuickOpenActionContributor extends ActionBarContributor {
private openToSideActionInstance: OpenToSideFromQuickOpenAction | undefined;
@@ -590,7 +593,7 @@ appendEditorToolItem(
appendEditorToolItem(
{
id: editorCommands.TOGGLE_DIFF_IGNORE_TRIM_WHITESPACE,
title: nls.localize('ignoreTrimWhitespace.label', "Ignore Trim Whitespace"),
title: nls.localize('ignoreTrimWhitespace.label', "Ignore Leading/Trailing Whitespace Differences"),
iconDark: URI.parse(registerAndGetAmdImageURL('vs/workbench/browser/parts/editor/media/paragraph-dark.svg')),
iconLight: URI.parse(registerAndGetAmdImageURL('vs/workbench/browser/parts/editor/media/paragraph-light.svg'))
},
@@ -602,7 +605,7 @@ appendEditorToolItem(
appendEditorToolItem(
{
id: editorCommands.TOGGLE_DIFF_IGNORE_TRIM_WHITESPACE,
title: nls.localize('showTrimWhitespace.label', "Show Trim Whitespace"),
title: nls.localize('showTrimWhitespace.label', "Show Leading/Trailing Whitespace Differences"),
iconDark: URI.parse(registerAndGetAmdImageURL('vs/workbench/browser/parts/editor/media/paragraph-disabled-dark.svg')),
iconLight: URI.parse(registerAndGetAmdImageURL('vs/workbench/browser/parts/editor/media/paragraph-disabled-light.svg'))
},

View File

@@ -37,7 +37,8 @@ export const DEFAULT_EDITOR_PART_OPTIONS: IEditorPartOptions = {
openSideBySideDirection: 'right',
closeEmptyGroups: true,
labelFormat: 'default',
iconTheme: 'vs-seti'
iconTheme: 'vs-seti',
splitSizing: 'distribute'
};
export function impactsEditorPartOptions(event: IConfigurationChangeEvent): boolean {

View File

@@ -495,7 +495,7 @@ export class CloseOneEditorAction extends Action {
group = this.editorGroupService.getGroup(context.groupId);
if (group) {
editorIndex = context.editorIndex!; // only allow editor at index if group is valid
editorIndex = context.editorIndex; // only allow editor at index if group is valid
}
}

View File

@@ -606,6 +606,10 @@ function registerCloseEditorCommands() {
.map(context => typeof context.editorIndex === 'number' ? group.getEditor(context.editorIndex) : group.activeEditor);
const editorsToClose = group.editors.filter(e => editors.indexOf(e) === -1);
if (group.activeEditor) {
group.pinEditor(group.activeEditor);
}
return group.closeEditors(editorsToClose);
}
@@ -624,6 +628,10 @@ function registerCloseEditorCommands() {
const { group, editor } = resolveCommandsContext(editorGroupService, getCommandsContext(resourceOrContext, context));
if (group && editor) {
if (group.activeEditor) {
group.pinEditor(group.activeEditor);
}
return group.closeEditors({ direction: CloseDirection.RIGHT, except: editor });
}
@@ -744,7 +752,7 @@ export function getMultiSelectedEditorContexts(editorContext: IEditorCommandsCon
const selection: Array<IEditorIdentifier | IEditorGroup> = list.getSelectedElements().filter(onlyEditorGroupAndEditor);
// Only respect selection if it contains focused element
if (selection && selection.some(s => {
if (selection?.some(s => {
if (isEditorGroup(s)) {
return s.id === focus.groupId;
}

View File

@@ -15,6 +15,7 @@ import { IEditorProgressService, LongRunningOperation } from 'vs/platform/progre
import { IEditorGroupView, DEFAULT_EDITOR_MIN_DIMENSIONS, DEFAULT_EDITOR_MAX_DIMENSIONS } from 'vs/workbench/browser/parts/editor/editor';
import { Event, Emitter } from 'vs/base/common/event';
import { IVisibleEditor } from 'vs/workbench/services/editor/common/editorService';
import { assertIsDefined } from 'vs/base/common/types';
export interface IOpenEditorResult {
readonly control: BaseEditor;
@@ -88,8 +89,9 @@ export class EditorControl extends Disposable {
this.doSetActiveControl(control);
// Show editor
this.parent.appendChild(control.getContainer());
show(control.getContainer());
const container = assertIsDefined(control.getContainer());
this.parent.appendChild(container);
show(container);
// Indicate to editor that it is now visible
control.setVisible(true, this.groupView);
@@ -154,7 +156,7 @@ export class EditorControl extends Disposable {
// If the input did not change, return early and only apply the options
// unless the options instruct us to force open it even if it is the same
const forceReload = options && options.forceReload;
const forceReload = options?.forceReload;
const inputMatches = control.input && control.input.matches(editor);
if (inputMatches && !forceReload) {
@@ -203,8 +205,10 @@ export class EditorControl extends Disposable {
// Remove control from parent and hide
const controlInstanceContainer = this._activeControl.getContainer();
this.parent.removeChild(controlInstanceContainer);
hide(controlInstanceContainer);
if (controlInstanceContainer) {
this.parent.removeChild(controlInstanceContainer);
hide(controlInstanceContainer);
}
// Indicate to editor control
this._activeControl.clearInput();

View File

@@ -16,6 +16,7 @@ import { GroupDirection, MergeGroupMode } from 'vs/workbench/services/editor/com
import { toDisposable } from 'vs/base/common/lifecycle';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { RunOnceScheduler } from 'vs/base/common/async';
import { find } from 'vs/base/common/arrays';
interface IDropOperation {
splitDirection?: GroupDirection;
@@ -23,7 +24,7 @@ interface IDropOperation {
class DropOverlay extends Themable {
private static OVERLAY_ID = 'monaco-workbench-editor-drop-overlay';
private static readonly OVERLAY_ID = 'monaco-workbench-editor-drop-overlay';
private container!: HTMLElement;
private overlay!: HTMLElement;
@@ -84,7 +85,7 @@ class DropOverlay extends Themable {
protected updateStyles(): void {
// Overlay drop background
this.overlay.style.backgroundColor = this.getColor(EDITOR_DRAG_AND_DROP_BACKGROUND);
this.overlay.style.backgroundColor = this.getColor(EDITOR_DRAG_AND_DROP_BACKGROUND) || '';
// Overlay contrast border (if any)
const activeContrastBorderColor = this.getColor(activeContrastBorder);
@@ -108,7 +109,16 @@ class DropOverlay extends Themable {
}
// Find out if operation is valid
const isCopy = isDraggingGroup ? this.isCopyOperation(e) : isDraggingEditor ? this.isCopyOperation(e, this.editorTransfer.getData(DraggedEditorIdentifier.prototype)![0].identifier) : true;
let isCopy = true;
if (isDraggingGroup) {
isCopy = this.isCopyOperation(e);
} else if (isDraggingEditor) {
const data = this.editorTransfer.getData(DraggedEditorIdentifier.prototype);
if (Array.isArray(data)) {
isCopy = this.isCopyOperation(e, data[0].identifier);
}
}
if (!isCopy) {
const sourceGroupView = this.findSourceGroupView();
if (sourceGroupView === this.groupView) {
@@ -162,12 +172,18 @@ class DropOverlay extends Themable {
// Check for group transfer
if (this.groupTransfer.hasData(DraggedEditorGroupIdentifier.prototype)) {
return this.accessor.getGroup(this.groupTransfer.getData(DraggedEditorGroupIdentifier.prototype)![0].identifier);
const data = this.groupTransfer.getData(DraggedEditorGroupIdentifier.prototype);
if (Array.isArray(data)) {
return this.accessor.getGroup(data[0].identifier);
}
}
// Check for editor transfer
else if (this.editorTransfer.hasData(DraggedEditorIdentifier.prototype)) {
return this.accessor.getGroup(this.editorTransfer.getData(DraggedEditorIdentifier.prototype)![0].identifier.groupId);
const data = this.editorTransfer.getData(DraggedEditorIdentifier.prototype);
if (Array.isArray(data)) {
return this.accessor.getGroup(data[0].identifier.groupId);
}
}
return undefined;
@@ -189,69 +205,75 @@ class DropOverlay extends Themable {
// Check for group transfer
if (this.groupTransfer.hasData(DraggedEditorGroupIdentifier.prototype)) {
const draggedEditorGroup = this.groupTransfer.getData(DraggedEditorGroupIdentifier.prototype)![0].identifier;
const data = this.groupTransfer.getData(DraggedEditorGroupIdentifier.prototype);
if (Array.isArray(data)) {
const draggedEditorGroup = data[0].identifier;
// Return if the drop is a no-op
const sourceGroup = this.accessor.getGroup(draggedEditorGroup);
if (sourceGroup) {
if (typeof splitDirection !== 'number' && sourceGroup === this.groupView) {
return;
}
// Return if the drop is a no-op
const sourceGroup = this.accessor.getGroup(draggedEditorGroup);
if (sourceGroup) {
if (typeof splitDirection !== 'number' && sourceGroup === this.groupView) {
return;
}
// Split to new group
let targetGroup: IEditorGroupView | undefined;
if (typeof splitDirection === 'number') {
if (this.isCopyOperation(event)) {
targetGroup = this.accessor.copyGroup(sourceGroup, this.groupView, splitDirection);
} else {
targetGroup = this.accessor.moveGroup(sourceGroup, this.groupView, splitDirection);
// Split to new group
let targetGroup: IEditorGroupView | undefined;
if (typeof splitDirection === 'number') {
if (this.isCopyOperation(event)) {
targetGroup = this.accessor.copyGroup(sourceGroup, this.groupView, splitDirection);
} else {
targetGroup = this.accessor.moveGroup(sourceGroup, this.groupView, splitDirection);
}
}
// Merge into existing group
else {
if (this.isCopyOperation(event)) {
targetGroup = this.accessor.mergeGroup(sourceGroup, this.groupView, { mode: MergeGroupMode.COPY_EDITORS });
} else {
targetGroup = this.accessor.mergeGroup(sourceGroup, this.groupView);
}
}
if (targetGroup) {
this.accessor.activateGroup(targetGroup);
}
}
// Merge into existing group
else {
if (this.isCopyOperation(event)) {
targetGroup = this.accessor.mergeGroup(sourceGroup, this.groupView, { mode: MergeGroupMode.COPY_EDITORS });
} else {
targetGroup = this.accessor.mergeGroup(sourceGroup, this.groupView);
}
}
if (targetGroup) {
this.accessor.activateGroup(targetGroup);
}
this.groupTransfer.clearData(DraggedEditorGroupIdentifier.prototype);
}
this.groupTransfer.clearData(DraggedEditorGroupIdentifier.prototype);
}
// Check for editor transfer
else if (this.editorTransfer.hasData(DraggedEditorIdentifier.prototype)) {
const draggedEditor = this.editorTransfer.getData(DraggedEditorIdentifier.prototype)![0].identifier;
const targetGroup = ensureTargetGroup();
const data = this.editorTransfer.getData(DraggedEditorIdentifier.prototype);
if (Array.isArray(data)) {
const draggedEditor = data[0].identifier;
const targetGroup = ensureTargetGroup();
// Return if the drop is a no-op
const sourceGroup = this.accessor.getGroup(draggedEditor.groupId);
if (sourceGroup) {
if (sourceGroup === targetGroup) {
return;
// Return if the drop is a no-op
const sourceGroup = this.accessor.getGroup(draggedEditor.groupId);
if (sourceGroup) {
if (sourceGroup === targetGroup) {
return;
}
// Open in target group
const options = getActiveTextEditorOptions(sourceGroup, draggedEditor.editor, EditorOptions.create({ pinned: true }));
targetGroup.openEditor(draggedEditor.editor, options);
// Ensure target has focus
targetGroup.focus();
// Close in source group unless we copy
const copyEditor = this.isCopyOperation(event, draggedEditor);
if (!copyEditor) {
sourceGroup.closeEditor(draggedEditor.editor);
}
}
// Open in target group
const options = getActiveTextEditorOptions(sourceGroup, draggedEditor.editor, EditorOptions.create({ pinned: true }));
targetGroup.openEditor(draggedEditor.editor, options);
// Ensure target has focus
targetGroup.focus();
// Close in source group unless we copy
const copyEditor = this.isCopyOperation(event, draggedEditor);
if (!copyEditor) {
sourceGroup.closeEditor(draggedEditor.editor);
}
this.editorTransfer.clearData(DraggedEditorIdentifier.prototype);
}
this.editorTransfer.clearData(DraggedEditorIdentifier.prototype);
}
// Check for URI transfer
@@ -266,7 +288,7 @@ class DropOverlay extends Themable {
}
private isCopyOperation(e: DragEvent, draggedEditor?: IEditorIdentifier): boolean {
if (draggedEditor && draggedEditor.editor instanceof EditorInput && !draggedEditor.editor.supportsSplitEditor()) {
if (draggedEditor?.editor instanceof EditorInput && !draggedEditor.editor.supportsSplitEditor()) {
return false;
}
@@ -438,6 +460,10 @@ class DropOverlay extends Themable {
}
}
export interface EditorDropTargetDelegate {
groupContainsPredicate?(groupView: IEditorGroupView): boolean;
}
export class EditorDropTarget extends Themable {
private _overlay?: DropOverlay;
@@ -450,6 +476,7 @@ export class EditorDropTarget extends Themable {
constructor(
private accessor: IEditorGroupsAccessor,
private container: HTMLElement,
private readonly delegate: EditorDropTargetDelegate,
@IThemeService themeService: IThemeService,
@IInstantiationService private readonly instantiationService: IInstantiationService
) {
@@ -523,13 +550,7 @@ export class EditorDropTarget extends Themable {
private findTargetGroupView(child: HTMLElement): IEditorGroupView | undefined {
const groups = this.accessor.groups;
for (const groupView of groups) {
if (isAncestor(child, groupView.element)) {
return groupView;
}
}
return undefined;
return find(groups, groupView => isAncestor(child, groupView.element) || this.delegate.groupContainsPredicate?.(groupView));
}
private updateContainer(isDraggedOver: boolean): void {

View File

@@ -53,7 +53,8 @@ import { hash } from 'vs/base/common/hash';
import { guessMimeTypes } from 'vs/base/common/mime';
import { extname } from 'vs/base/common/resources';
import { Schemas } from 'vs/base/common/network';
import { EditorActivation } from 'vs/platform/editor/common/editor';
import { EditorActivation, EditorOpenContext } from 'vs/platform/editor/common/editor';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
export class EditorGroupView extends Themable implements IEditorGroupView {
@@ -99,13 +100,13 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
//#endregion
private _group: EditorGroup;
private _disposed: boolean;
private _disposed = false;
private active: boolean;
private dimension: Dimension;
private active: boolean | undefined;
private dimension: Dimension | undefined;
private _whenRestored: Promise<void>;
private isRestored: boolean;
private isRestored = false;
private scopedInstantiationService: IInstantiationService;
@@ -123,12 +124,13 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
constructor(
private accessor: IEditorGroupsAccessor,
from: IEditorGroupView | ISerializedEditorGroup,
from: IEditorGroupView | ISerializedEditorGroup | null,
private _index: number,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IContextKeyService private readonly contextKeyService: IContextKeyService,
@IThemeService themeService: IThemeService,
@INotificationService private readonly notificationService: INotificationService,
@IDialogService private readonly dialogService: IDialogService,
@ITelemetryService private readonly telemetryService: ITelemetryService,
@IUntitledEditorService private readonly untitledEditorService: IUntitledEditorService,
@IKeybindingService private readonly keybindingService: IKeybindingService,
@@ -149,7 +151,68 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
this.disposedEditorsWorker = this._register(new RunOnceWorker(editors => this.handleDisposedEditors(editors), 0));
this.create();
//#region create()
{
// Container
addClasses(this.element, 'editor-group-container');
// Container listeners
this.registerContainerListeners();
// Container toolbar
this.createContainerToolbar();
// Container context menu
this.createContainerContextMenu();
// Letterpress container
const letterpressContainer = document.createElement('div');
addClass(letterpressContainer, 'editor-group-letterpress');
this.element.appendChild(letterpressContainer);
// Progress bar
this.progressBar = this._register(new ProgressBar(this.element));
this._register(attachProgressBarStyler(this.progressBar, this.themeService));
this.progressBar.hide();
// Scoped services
const scopedContextKeyService = this._register(this.contextKeyService.createScoped(this.element));
this.scopedInstantiationService = this.instantiationService.createChild(new ServiceCollection(
[IContextKeyService, scopedContextKeyService],
[IEditorProgressService, new EditorProgressService(this.progressBar)]
));
// Context keys
this.handleGroupContextKeys(scopedContextKeyService);
// Title container
this.titleContainer = document.createElement('div');
addClass(this.titleContainer, 'title');
this.element.appendChild(this.titleContainer);
// Title control
this.titleAreaControl = this.createTitleAreaControl();
// Editor container
this.editorContainer = document.createElement('div');
addClass(this.editorContainer, 'editor-container');
this.element.appendChild(this.editorContainer);
// Editor control
this.editorControl = this._register(this.scopedInstantiationService.createInstance(EditorControl, this.editorContainer, this));
this._onDidChange.input = this.editorControl.onDidSizeConstraintsChange;
// Track Focus
this.doTrackFocus();
// Update containers
this.updateTitleContainer();
this.updateContainer();
// Update styles
this.updateStyles();
}
//#endregion
this._whenRestored = this.restoreEditors(from);
this._whenRestored.then(() => this.isRestored = true);
@@ -157,68 +220,6 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
this.registerListeners();
}
private create(): void {
// Container
addClasses(this.element, 'editor-group-container');
// Container listeners
this.registerContainerListeners();
// Container toolbar
this.createContainerToolbar();
// Container context menu
this.createContainerContextMenu();
// Letterpress container
const letterpressContainer = document.createElement('div');
addClass(letterpressContainer, 'editor-group-letterpress');
this.element.appendChild(letterpressContainer);
// Progress bar
this.progressBar = this._register(new ProgressBar(this.element));
this._register(attachProgressBarStyler(this.progressBar, this.themeService));
this.progressBar.hide();
// Scoped services
const scopedContextKeyService = this._register(this.contextKeyService.createScoped(this.element));
this.scopedInstantiationService = this.instantiationService.createChild(new ServiceCollection(
[IContextKeyService, scopedContextKeyService],
[IEditorProgressService, new EditorProgressService(this.progressBar)]
));
// Context keys
this.handleGroupContextKeys(scopedContextKeyService);
// Title container
this.titleContainer = document.createElement('div');
addClass(this.titleContainer, 'title');
this.element.appendChild(this.titleContainer);
// Title control
this.createTitleAreaControl();
// Editor container
this.editorContainer = document.createElement('div');
addClass(this.editorContainer, 'editor-container');
this.element.appendChild(this.editorContainer);
// Editor control
this.editorControl = this._register(this.scopedInstantiationService.createInstance(EditorControl, this.editorContainer, this));
this._onDidChange.input = this.editorControl.onDidSizeConstraintsChange;
// Track Focus
this.doTrackFocus();
// Update containers
this.updateTitleContainer();
this.updateContainer();
// Update styles
this.updateStyles();
}
private handleGroupContextKeys(contextKeyService: IContextKeyService): void {
const groupActiveEditorDirtyContextKey = EditorGroupActiveEditorDirtyContext.bindTo(contextKeyService);
const groupEditorsCountContext = EditorGroupEditorsCountContext.bindTo(contextKeyService);
@@ -295,7 +296,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
const removeGroupAction = this._register(new Action(
CLOSE_EDITOR_GROUP_COMMAND_ID,
localize('closeGroupAction', "Close"),
'close-editor-group',
'codicon-close',
true,
() => {
this.accessor.removeGroup(this);
@@ -410,7 +411,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
toggleClass(this.titleContainer, 'show-file-icons', this.accessor.partOptions.showIcons);
}
private createTitleAreaControl(): void {
private createTitleAreaControl(): TitleControl {
// Clear old if existing
if (this.titleAreaControl) {
@@ -424,15 +425,17 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
} else {
this.titleAreaControl = this.scopedInstantiationService.createInstance(NoTabsTitleControl, this.titleContainer, this.accessor, this);
}
return this.titleAreaControl;
}
private async restoreEditors(from: IEditorGroupView | ISerializedEditorGroup): Promise<void> {
await this._group.removeNonExitingEditor(); // {{SQL CARBON EDIT}} @udeeshagautam perform async correction for non-existing files
private async restoreEditors(from: IEditorGroupView | ISerializedEditorGroup | null): Promise<void> {
if (this._group.count === 0) {
return; // nothing to show
}
await this._group.removeNonExitingEditor(); // {{SQL CARBON EDIT}} @udeeshagautam perform async correction for non-existing files
// Determine editor options
let options: EditorOptions;
if (from instanceof EditorGroupView) {
@@ -604,7 +607,9 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
// Recreate and layout control
this.createTitleAreaControl();
this.layoutTitleAreaControl();
if (this.dimension) {
this.layoutTitleAreaControl(this.dimension.width);
}
// Ensure to show active editor if any
if (this._group.activeEditor) {
@@ -829,7 +834,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
// Determine options
const openEditorOptions: IEditorOpenOptions = {
index: options ? options.index : undefined,
pinned: !this.accessor.partOptions.enablePreview || editor.isDirty() || (options && options.pinned) || (options && typeof options.index === 'number'),
pinned: !this.accessor.partOptions.enablePreview || editor.isDirty() || options?.pinned || typeof options?.index === 'number',
active: this._group.count === 0 || !options || !options.inactive
};
@@ -843,13 +848,13 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
let activateGroup = false;
let restoreGroup = false;
if (options && options.activation === EditorActivation.ACTIVATE) {
if (options?.activation === EditorActivation.ACTIVATE) {
// Respect option to force activate an editor group.
activateGroup = true;
} else if (options && options.activation === EditorActivation.RESTORE) {
} else if (options?.activation === EditorActivation.RESTORE) {
// Respect option to force restore an editor group.
restoreGroup = true;
} else if (options && options.activation === EditorActivation.PRESERVE) {
} else if (options?.activation === EditorActivation.PRESERVE) {
// Respect option to preserve active editor group.
activateGroup = false;
restoreGroup = false;
@@ -921,23 +926,67 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
return openEditorPromise;
}
private doHandleOpenEditorError(error: Error, editor: EditorInput, options?: EditorOptions): void {
private async doHandleOpenEditorError(error: Error, editor: EditorInput, options?: EditorOptions): Promise<void> {
// Report error only if this was not us restoring previous error state or
// we are told to ignore errors that occur from opening an editor
if (this.isRestored && !isPromiseCanceledError(error) && (!options || !options.ignoreError)) {
const actions: INotificationActions = { primary: [] };
// Extract possible error actions from the error
let errorActions: ReadonlyArray<IAction> | undefined = undefined;
if (isErrorWithActions(error)) {
actions.primary = (error as IErrorWithActions).actions;
errorActions = (error as IErrorWithActions).actions;
}
const handle = this.notificationService.notify({
severity: Severity.Error,
message: localize('editorOpenError', "Unable to open '{0}': {1}.", editor.getName(), toErrorMessage(error)),
actions
});
// If the context is USER, we try to show a modal dialog instead of a background notification
if (options?.context === EditorOpenContext.USER) {
const buttons: string[] = [];
if (Array.isArray(errorActions) && errorActions.length > 0) {
errorActions.forEach(action => buttons.push(action.label));
} else {
buttons.push(localize('ok', 'OK'));
}
Event.once(handle.onDidClose)(() => actions.primary && dispose(actions.primary));
let cancelId: number | undefined = undefined;
if (buttons.length === 1) {
buttons.push(localize('cancel', "Cancel"));
cancelId = 1;
}
const result = await this.dialogService.show(
Severity.Error,
localize('editorOpenErrorDialog', "Unable to open '{0}'", editor.getName()),
buttons,
{
detail: toErrorMessage(error),
cancelId
}
);
// Make sure to run any error action if present
if (result.choice !== cancelId && Array.isArray(errorActions)) {
const errorAction = errorActions[result.choice];
if (errorAction) {
errorAction.run();
}
}
}
// Otherwise, show a background notification.
else {
const actions: INotificationActions = { primary: [] };
if (Array.isArray(errorActions)) {
actions.primary = errorActions;
}
const handle = this.notificationService.notify({
severity: Severity.Error,
message: localize('editorOpenError', "Unable to open '{0}': {1}.", editor.getName(), toErrorMessage(error)),
actions
});
Event.once(handle.onDidClose)(() => actions.primary && dispose(actions.primary));
}
}
// Event
@@ -1073,7 +1122,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
}
// Do close
this.doCloseEditor(editor, options && options.preserveFocus ? false : undefined);
this.doCloseEditor(editor, options?.preserveFocus ? false : undefined);
}
private doCloseEditor(editor: EditorInput, focusNext = (this.accessor.activeGroup === this), fromError?: boolean): void {
@@ -1322,7 +1371,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
// Close active editor last if contained in editors list to close
if (closeActiveEditor) {
this.doCloseActiveEditor(options && options.preserveFocus ? false : undefined);
this.doCloseActiveEditor(options?.preserveFocus ? false : undefined);
}
// Forward to title control
@@ -1450,9 +1499,9 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
// Container
if (isEmpty) {
this.element.style.backgroundColor = this.getColor(EDITOR_GROUP_EMPTY_BACKGROUND);
this.element.style.backgroundColor = this.getColor(EDITOR_GROUP_EMPTY_BACKGROUND) || '';
} else {
this.element.style.backgroundColor = null;
this.element.style.backgroundColor = '';
}
// Title control
@@ -1467,10 +1516,10 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
this.titleContainer.style.removeProperty('--title-border-bottom-color');
}
this.titleContainer.style.backgroundColor = this.getColor(showTabs ? EDITOR_GROUP_HEADER_TABS_BACKGROUND : EDITOR_GROUP_HEADER_NO_TABS_BACKGROUND);
this.titleContainer.style.backgroundColor = this.getColor(showTabs ? EDITOR_GROUP_HEADER_TABS_BACKGROUND : EDITOR_GROUP_HEADER_NO_TABS_BACKGROUND) || '';
// Editor container
this.editorContainer.style.backgroundColor = this.getColor(editorBackground);
this.editorContainer.style.backgroundColor = this.getColor(editorBackground) || '';
}
//#endregion
@@ -1495,12 +1544,12 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
this.editorContainer.style.height = `calc(100% - ${this.titleAreaControl.getPreferredHeight()}px)`;
// Forward to controls
this.layoutTitleAreaControl();
this.layoutTitleAreaControl(width);
this.editorControl.layout(new Dimension(this.dimension.width, this.dimension.height - this.titleAreaControl.getPreferredHeight()));
}
private layoutTitleAreaControl(): void {
this.titleAreaControl.layout(new Dimension(this.dimension.width, this.titleAreaControl.getPreferredHeight()));
private layoutTitleAreaControl(width: number): void {
this.titleAreaControl.layout(new Dimension(width, this.titleAreaControl.getPreferredHeight()));
}
relayout(): void {
@@ -1528,7 +1577,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
}
class EditorOpeningEvent implements IEditorOpeningEvent {
private override: () => Promise<IEditor | undefined>;
private override: (() => Promise<IEditor | undefined>) | undefined = undefined;
constructor(
private _group: GroupIdentifier,
@@ -1553,7 +1602,7 @@ class EditorOpeningEvent implements IEditorOpeningEvent {
this.override = callback;
}
isPrevented(): () => Promise<IEditor | undefined> {
isPrevented(): (() => Promise<IEditor | undefined>) | undefined {
return this.override;
}
}

View File

@@ -23,13 +23,14 @@ import { IDisposable, dispose, toDisposable, DisposableStore } from 'vs/base/com
import { assign } from 'vs/base/common/objects';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { ISerializedEditorGroup, isSerializedEditorGroup } from 'vs/workbench/common/editor/editorGroup';
import { EditorDropTarget } from 'vs/workbench/browser/parts/editor/editorDropTarget';
import { EditorDropTarget, EditorDropTargetDelegate } from 'vs/workbench/browser/parts/editor/editorDropTarget';
import { Color } from 'vs/base/common/color';
import { CenteredViewLayout } from 'vs/base/browser/ui/centered/centeredViewLayout';
import { onUnexpectedError } from 'vs/base/common/errors';
import { Parts, IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { MementoObject } from 'vs/workbench/common/memento';
import { assertIsDefined } from 'vs/base/common/types';
interface IEditorPartUIState {
serializedGrid: ISerializedGrid;
@@ -49,13 +50,13 @@ class GridWidgetView<T extends IView> implements IView {
private _onDidChange = new Relay<{ width: number; height: number; } | undefined>();
readonly onDidChange: Event<{ width: number; height: number; } | undefined> = this._onDidChange.event;
private _gridWidget: Grid<T>;
private _gridWidget: Grid<T> | undefined;
get gridWidget(): Grid<T> {
get gridWidget(): Grid<T> | undefined {
return this._gridWidget;
}
set gridWidget(grid: Grid<T>) {
set gridWidget(grid: Grid<T> | undefined) {
this.element.innerHTML = '';
if (grid) {
@@ -113,9 +114,6 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro
private _onDidSizeConstraintsChange = this._register(new Relay<{ width: number; height: number; } | undefined>());
get onDidSizeConstraintsChange(): Event<{ width: number; height: number; } | undefined> { return Event.any(this.onDidSetGridWidget.event, this._onDidSizeConstraintsChange.event); }
private _onDidVisibilityChange = this._register(new Emitter<boolean>());
readonly onDidVisibilityChange: Event<boolean> = this._onDidVisibilityChange.event;
//#endregion
private readonly workspaceMemento: MementoObject;
@@ -123,17 +121,18 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro
private _partOptions: IEditorPartOptions;
private _activeGroup: IEditorGroupView;
private groupViews: Map<GroupIdentifier, IEditorGroupView> = new Map<GroupIdentifier, IEditorGroupView>();
private mostRecentActiveGroups: GroupIdentifier[] = [];
private container: HTMLElement;
private centeredLayoutWidget: CenteredViewLayout;
private gridWidget: SerializableGrid<IEditorGroupView>;
private container: HTMLElement | undefined;
private centeredLayoutWidget!: CenteredViewLayout;
private gridWidget!: SerializableGrid<IEditorGroupView>;
private gridWidgetView: GridWidgetView<IEditorGroupView>;
private _whenRestored: Promise<void>;
private whenRestoredResolve: () => void;
private whenRestoredResolve: (() => void) | undefined;
constructor(
@IInstantiationService private readonly instantiationService: IInstantiationService,
@@ -204,9 +203,10 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro
//#region IEditorGroupsService
private _contentDimension: Dimension;
private _contentDimension!: Dimension;
get contentDimension(): Dimension { return this._contentDimension; }
private _activeGroup!: IEditorGroupView;
get activeGroup(): IEditorGroupView {
return this._activeGroup;
}
@@ -463,7 +463,11 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro
}
}
private shouldRestoreFocus(target: Element): boolean {
private shouldRestoreFocus(target: Element | undefined): boolean {
if (!target) {
return false;
}
const activeElement = document.activeElement;
if (activeElement === document.body) {
@@ -490,7 +494,7 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro
const group = this.doAddGroup(locationView, direction);
if (options && options.activate) {
if (options?.activate) {
this.doSetGroupActive(group);
}
@@ -503,7 +507,7 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro
// Add to grid widget
this.gridWidget.addView(
newGroupView,
Sizing.Distribute,
this.getSplitSizingStyle(),
locationView,
this.toGridViewDirection(direction),
);
@@ -520,6 +524,10 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro
return newGroupView;
}
private getSplitSizingStyle(): Sizing {
return this._partOptions.splitSizing === 'split' ? Sizing.Split : Sizing.Distribute;
}
private doCreateGroupView(from?: IEditorGroupView | ISerializedEditorGroup | null): IEditorGroupView {
// Create group view
@@ -670,7 +678,7 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro
}
// Remove from grid widget & dispose
this.gridWidget.removeView(groupView, Sizing.Distribute);
this.gridWidget.removeView(groupView, this.getSplitSizingStyle());
groupView.dispose();
// Restore focus if we had it previously (we run this after gridWidget.removeView() is called
@@ -700,7 +708,7 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro
const restoreFocus = this.shouldRestoreFocus(sourceView.element);
// Move through grid widget API
this.gridWidget.moveView(sourceView, Sizing.Distribute, targetView, this.toGridViewDirection(direction));
this.gridWidget.moveView(sourceView, this.getSplitSizingStyle(), targetView, this.toGridViewDirection(direction));
// Restore focus if we had it previously (we run this after gridWidget.removeView() is called
// because removing a view can mean to reparent it and thus focus would be removed otherwise)
@@ -744,7 +752,7 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro
const inactive = !sourceView.isActive(editor) || this._activeGroup !== sourceView;
const copyOptions: ICopyEditorOptions = { index, inactive, preserveFocus: inactive };
if (options && options.mode === MergeGroupMode.COPY_EDITORS) {
if (options?.mode === MergeGroupMode.COPY_EDITORS) {
sourceView.copyEditor(editor, targetView, copyOptions);
} else {
sourceView.moveEditor(editor, targetView, copyOptions);
@@ -796,7 +804,8 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro
}
updateStyles(): void {
this.container.style.backgroundColor = this.getColor(editorBackground);
const container = assertIsDefined(this.container);
container.style.backgroundColor = this.getColor(editorBackground) || '';
const separatorBorderStyle = { separatorBorder: this.gridSeparatorBorder, background: this.theme.getColor(EDITOR_PANE_BACKGROUND) || Color.transparent };
this.gridWidget.style(separatorBorderStyle);
@@ -817,7 +826,7 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro
this.centeredLayoutWidget = this._register(new CenteredViewLayout(this.container, this.gridWidgetView, this.globalMemento[EditorPart.EDITOR_PART_CENTERED_VIEW_STORAGE_KEY]));
// Drop support
this._register(this.instantiationService.createInstance(EditorDropTarget, this, this.container));
this._register(this.createEditorDropTarget(this.container, {}));
return this.container;
}
@@ -850,7 +859,11 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro
}
// Signal restored
Promise.all(this.groups.map(group => group.whenRestored)).finally(() => this.whenRestoredResolve());
Promise.all(this.groups.map(group => group.whenRestored)).finally(() => {
if (this.whenRestoredResolve) {
this.whenRestoredResolve();
}
});
// Update container
this.updateContainer();
@@ -861,7 +874,7 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro
private doCreateGridControlWithPreviousState(): boolean {
const uiState: IEditorPartUIState = this.workspaceMemento[EditorPart.EDITOR_PART_UI_STATE_STORAGE_KEY];
if (uiState && uiState.serializedGrid) {
if (uiState?.serializedGrid) {
try {
// MRU
@@ -950,7 +963,8 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro
}
private updateContainer(): void {
toggleClass(this.container, 'empty', this.isEmpty);
const container = assertIsDefined(this.container);
toggleClass(container, 'empty', this.isEmpty);
}
private notifyGroupIndexChange(): void {
@@ -1024,15 +1038,19 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro
//#endregion
setVisible(visible: boolean): void {
this._onDidVisibilityChange.fire(visible);
}
toJSON(): object {
return {
type: Parts.EDITOR_PART
};
}
//#region TODO@matt this should move into some kind of service
createEditorDropTarget(container: HTMLElement, delegate: EditorDropTargetDelegate): IDisposable {
return this.instantiationService.createInstance(EditorDropTarget, this, container, delegate);
}
//#endregion
}
registerSingleton(IEditorGroupsService, EditorPart);

View File

@@ -15,7 +15,7 @@ import { QuickOpenHandler } from 'vs/workbench/browser/quickopen';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IEditorGroupsService, IEditorGroup, EditorsOrder, GroupsOrder } from 'vs/workbench/services/editor/common/editorGroupsService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { EditorInput, toResource, SideBySideEditor } from 'vs/workbench/common/editor';
import { toResource, SideBySideEditor, IEditorInput } from 'vs/workbench/common/editor';
import { compareItemsByScore, scoreItem, ScorerCache, prepareQuery } from 'vs/base/parts/quickopen/common/quickOpenScorer';
import { CancellationToken } from 'vs/base/common/cancellation';
import { withNullAsUndefined } from 'vs/base/common/types';
@@ -23,7 +23,7 @@ import { withNullAsUndefined } from 'vs/base/common/types';
export class EditorPickerEntry extends QuickOpenEntryGroup {
constructor(
private editor: EditorInput,
private editor: IEditorInput,
private _group: IEditorGroup,
@IModeService private readonly modeService: IModeService,
@IModelService private readonly modelService: IModelService

View File

@@ -7,7 +7,7 @@ import 'vs/css!./media/editorstatus';
import * as nls from 'vs/nls';
import { runAtThisOrScheduleAtNextAnimationFrame } from 'vs/base/browser/dom';
import { format } from 'vs/base/common/strings';
import { extname, basename } from 'vs/base/common/resources';
import { extname, basename, isEqual } from 'vs/base/common/resources';
import { areFunctions, withNullAsUndefined, withUndefinedAsNull } from 'vs/base/common/types';
import { URI } from 'vs/base/common/uri';
import { Action } from 'vs/base/common/actions';
@@ -39,7 +39,7 @@ import { ConfigurationChangedEvent, IEditorOptions, EditorOption } from 'vs/edit
import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration';
import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { deepClone } from 'vs/base/common/objects';
import { ICodeEditor, isCodeEditor, isDiffEditor, getCodeEditor } from 'vs/editor/browser/editorBrowser';
import { ICodeEditor, getCodeEditor } from 'vs/editor/browser/editorBrowser';
import { Schemas } from 'vs/base/common/network';
import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences';
import { IQuickInputService, IQuickPickItem, QuickPickInput } from 'vs/platform/quickinput/common/quickInput';
@@ -57,7 +57,7 @@ import { IQueryEditorService } from 'sql/workbench/services/queryEditor/common/q
class SideBySideEditorEncodingSupport implements IEncodingSupport {
constructor(private master: IEncodingSupport, private details: IEncodingSupport) { }
getEncoding(): string {
getEncoding(): string | undefined {
return this.master.getEncoding(); // always report from modified (right hand) side
}
@@ -281,7 +281,7 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution {
private readonly screenRedearModeElement = this._register(new MutableDisposable<IStatusbarEntryAccessor>());
private readonly indentationElement = this._register(new MutableDisposable<IStatusbarEntryAccessor>());
private readonly selectionElement = this._register(new MutableDisposable<IStatusbarEntryAccessor>());
private readonly encodingElement = this._register(new MutableDisposable<IStatusbarEntryAccessor>());
private readonly encodingElement = Object.keys(SUPPORTED_ENCODINGS).length > 1 ? this._register(new MutableDisposable<IStatusbarEntryAccessor>()) : undefined;
private readonly eolElement = this._register(new MutableDisposable<IStatusbarEntryAccessor>());
private readonly modeElement = this._register(new MutableDisposable<IStatusbarEntryAccessor>());
private readonly metadataElement = this._register(new MutableDisposable<IStatusbarEntryAccessor>());
@@ -355,7 +355,7 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution {
return this.quickInputService.pick([{ label: nls.localize('noWritableCodeEditor', "The active code editor is read-only.") }]);
}
const picks: QuickPickInput<IQuickPickItem & { run(): void }>[] = [
const picks: QuickPickInput<IQuickPickItem & { run(): void; }>[] = [
activeTextEditorWidget.getAction(IndentUsingSpaces.ID),
activeTextEditorWidget.getAction(IndentUsingTabs.ID),
activeTextEditorWidget.getAction(DetectIndentation.ID),
@@ -366,7 +366,7 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution {
return {
id: a.id,
label: a.label,
detail: Language.isDefaultVariant() ? undefined : a.alias,
detail: (Language.isDefaultVariant() || a.label === a.alias) ? undefined : a.alias,
run: () => {
activeTextEditorWidget.focus();
a.run();
@@ -378,7 +378,7 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution {
picks.unshift({ type: 'separator', label: nls.localize('indentView', "change view") });
const action = await this.quickInputService.pick(picks, { placeHolder: nls.localize('pickAction', "Select Action"), matchOnDetail: true });
return action && action.run();
return action?.run();
}
private updateTabFocusModeElement(visible: boolean): void {
@@ -440,6 +440,10 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution {
}
private updateEncodingElement(text: string | undefined): void {
if (!this.encodingElement) {
return; // return early if encoding should not show (e.g. in Web we only support utf8)
}
if (!text) {
this.encodingElement.clear();
return;
@@ -573,7 +577,7 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution {
this.onSelectionChange(activeCodeEditor);
this.onModeChange(activeCodeEditor);
this.onEOLChange(activeCodeEditor);
this.onEncodingChange(activeControl);
this.onEncodingChange(activeControl, activeCodeEditor);
this.onIndentationChange(activeCodeEditor);
this.onMetadataChange(activeControl);
@@ -733,20 +737,23 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution {
const textModel = editorWidget.getModel();
if (textModel) {
info.selections.forEach(selection => {
info.charactersSelected! += textModel.getValueLengthInRange(selection);
if (typeof info.charactersSelected !== 'number') {
info.charactersSelected = 0;
}
info.charactersSelected += textModel.getCharacterCountInRange(selection);
});
}
// Compute the visible column for one selection. This will properly handle tabs and their configured widths
if (info.selections.length === 1) {
const visibleColumn = editorWidget.getVisibleColumnFromPosition(editorWidget.getPosition()!);
const editorPosition = editorWidget.getPosition();
let selectionClone = info.selections[0].clone(); // do not modify the original position we got from the editor
selectionClone = new Selection(
selectionClone.selectionStartLineNumber,
selectionClone.selectionStartColumn,
selectionClone.positionLineNumber,
visibleColumn
let selectionClone = new Selection(
info.selections[0].selectionStartLineNumber,
info.selections[0].selectionStartColumn,
info.selections[0].positionLineNumber,
editorPosition ? editorWidget.getStatusbarColumn(editorPosition) : info.selections[0].positionColumn
);
info.selections[0] = selectionClone;
@@ -769,19 +776,21 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution {
this.updateState(info);
}
private onEncodingChange(e?: IBaseEditor): void {
if (e && !this.isActiveEditor(e)) {
private onEncodingChange(editor: IBaseEditor | undefined, editorWidget: ICodeEditor | undefined): void {
if (editor && !this.isActiveEditor(editor)) {
return;
}
const info: StateDelta = { encoding: undefined };
// We only support text based editors
if (e && (isCodeEditor(e.getControl()) || isDiffEditor(e.getControl()))) {
const encodingSupport: IEncodingSupport | null = e.input ? toEditorWithEncodingSupport(e.input) : null;
// We only support text based editors that have a model associated
// This ensures we do not show the encoding picker while an editor
// is still loading.
if (editor && editorWidget?.hasModel()) {
const encodingSupport: IEncodingSupport | null = editor.input ? toEditorWithEncodingSupport(editor.input) : null;
if (encodingSupport) {
const rawEncoding = encodingSupport.getEncoding();
const encodingInfo = SUPPORTED_ENCODINGS[rawEncoding];
const encodingInfo = typeof rawEncoding === 'string' ? SUPPORTED_ENCODINGS[rawEncoding] : undefined;
if (encodingInfo) {
info.encoding = encodingInfo.labelShort; // if we have a label, take it from there
} else {
@@ -797,8 +806,10 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution {
const activeControl = this.editorService.activeControl;
if (activeControl) {
const activeResource = toResource(activeControl.input, { supportSideBySide: SideBySideEditor.MASTER });
if (activeResource && activeResource.toString() === resource.toString()) {
return this.onEncodingChange(activeControl); // only update if the encoding changed for the active resource
if (activeResource && isEqual(activeResource, resource)) {
const activeCodeEditor = withNullAsUndefined(getCodeEditor(activeControl.getControl()));
return this.onEncodingChange(activeControl, activeCodeEditor); // only update if the encoding changed for the active resource
}
}
}
@@ -877,7 +888,7 @@ export class ChangeModeAction extends Action {
const resource = this.editorService.activeEditor ? toResource(this.editorService.activeEditor, { supportSideBySide: SideBySideEditor.MASTER }) : null;
let hasLanguageSupport = !!resource;
if (resource && resource.scheme === Schemas.untitled && !this.untitledEditorService.hasAssociatedFilePath(resource)) {
if (resource?.scheme === Schemas.untitled && !this.untitledEditorService.hasAssociatedFilePath(resource)) {
hasLanguageSupport = false; // no configuration for untitled resources (e.g. "Untitled-1")
}
@@ -1041,11 +1052,11 @@ export class ChangeModeAction extends Action {
let fakeResource: URI | undefined;
const extensions = this.modeService.getExtensions(lang);
if (extensions && extensions.length) {
if (extensions?.length) {
fakeResource = URI.file(extensions[0]);
} else {
const filenames = this.modeService.getFilenames(lang);
if (filenames && filenames.length) {
if (filenames?.length) {
fakeResource = URI.file(filenames[0]);
}
}
@@ -1089,12 +1100,12 @@ export class ChangeEOLAction extends Action {
{ label: nlsEOLCRLF, eol: EndOfLineSequence.CRLF },
];
const selectedIndex = (textModel && textModel.getEOL() === '\n') ? 0 : 1;
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] });
if (eol) {
const activeCodeEditor = getCodeEditor(this.editorService.activeTextEditorWidget);
if (activeCodeEditor && activeCodeEditor.hasModel() && isWritableCodeEditor(activeCodeEditor)) {
if (activeCodeEditor?.hasModel() && isWritableCodeEditor(activeCodeEditor)) {
textModel = activeCodeEditor.getModel();
textModel.pushEOL(eol.eol);
}
@@ -1134,14 +1145,19 @@ export class ChangeEncodingAction extends Action {
return this.quickInputService.pick([{ label: nls.localize('noFileEditor', "No file active at this time") }]);
}
let saveWithEncodingPick: IQuickPickItem;
let reopenWithEncodingPick: IQuickPickItem;
if (Language.isDefaultVariant()) {
saveWithEncodingPick = { label: nls.localize('saveWithEncoding', "Save with Encoding") };
reopenWithEncodingPick = { label: nls.localize('reopenWithEncoding', "Reopen with Encoding") };
} else {
saveWithEncodingPick = { label: nls.localize('saveWithEncoding', "Save with Encoding"), detail: 'Save with Encoding', };
reopenWithEncodingPick = { label: nls.localize('reopenWithEncoding', "Reopen with Encoding"), detail: 'Reopen with Encoding' };
const saveWithEncodingPick: IQuickPickItem = { label: nls.localize('saveWithEncoding', "Save with Encoding") };
const reopenWithEncodingPick: IQuickPickItem = { label: nls.localize('reopenWithEncoding', "Reopen with Encoding") };
if (!Language.isDefaultVariant()) {
const saveWithEncodingAlias = 'Save with Encoding';
if (saveWithEncodingAlias !== saveWithEncodingPick.label) {
saveWithEncodingPick.detail = saveWithEncodingAlias;
}
const reopenWithEncodingAlias = 'Reopen with Encoding';
if (reopenWithEncodingAlias !== reopenWithEncodingPick.label) {
reopenWithEncodingPick.detail = reopenWithEncodingAlias;
}
}
let action: IQuickPickItem;

View File

@@ -8,7 +8,7 @@ import { IOverlayWidget, ICodeEditor, IOverlayWidgetPosition, OverlayWidgetPosit
import { Event, Emitter } from 'vs/base/common/event';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { $, append } from 'vs/base/browser/dom';
import { $, append, clearNode } from 'vs/base/browser/dom';
import { attachStylerCallback } from 'vs/platform/theme/common/styler';
import { buttonBackground, buttonForeground, editorBackground, editorForeground, contrastBorder } from 'vs/platform/theme/common/colorRegistry';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
@@ -31,12 +31,14 @@ export class FloatingClickWidget extends Widget implements IOverlayWidget {
constructor(
private editor: ICodeEditor,
private label: string,
keyBindingAction: string,
keyBindingAction: string | null,
@IKeybindingService keybindingService: IKeybindingService,
@IThemeService private readonly themeService: IThemeService
) {
super();
this._domNode = $('.floating-click-widget');
if (keyBindingAction) {
const keybinding = keybindingService.lookupKeybinding(keyBindingAction);
if (keybinding) {
@@ -60,7 +62,7 @@ export class FloatingClickWidget extends Widget implements IOverlayWidget {
}
render() {
this._domNode = $('.floating-click-widget');
clearNode(this._domNode);
this._register(attachStylerCallback(this.themeService, { buttonBackground, buttonForeground, editorBackground, editorForeground, contrastBorder }, colors => {
const backgroundColor = colors.buttonBackground ? colors.buttonBackground : colors.editorBackground;
@@ -73,9 +75,9 @@ export class FloatingClickWidget extends Widget implements IOverlayWidget {
this._domNode.style.color = foregroundColor.toString();
}
const borderColor = colors.contrastBorder ? colors.contrastBorder.toString() : null;
this._domNode.style.borderWidth = borderColor ? '1px' : null;
this._domNode.style.borderStyle = borderColor ? 'solid' : null;
const borderColor = colors.contrastBorder ? colors.contrastBorder.toString() : '';
this._domNode.style.borderWidth = borderColor ? '1px' : '';
this._domNode.style.borderStyle = borderColor ? 'solid' : '';
this._domNode.style.borderColor = borderColor;
}));
@@ -99,7 +101,7 @@ export class OpenWorkspaceButtonContribution extends Disposable implements IEdit
return editor.getContribution<OpenWorkspaceButtonContribution>(OpenWorkspaceButtonContribution.ID);
}
private static readonly ID = 'editor.contrib.openWorkspaceButton';
public static readonly ID = 'editor.contrib.openWorkspaceButton';
private openWorkspaceButton: FloatingClickWidget | undefined;
@@ -120,10 +122,6 @@ export class OpenWorkspaceButtonContribution extends Disposable implements IEdit
this._register(this.editor.onDidChangeModel(e => this.update()));
}
getId(): string {
return OpenWorkspaceButtonContribution.ID;
}
private update(): void {
if (!this.shouldShowButton(this.editor)) {
this.disposeOpenWorkspaceWidgetRenderer();

View File

@@ -186,7 +186,7 @@
overflow: visible; /* ...but still show the close button on hover, focus and when dirty */
}
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-button-off > .tab-close {
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-button-off:not(.dirty) > .tab-close {
display: none; /* hide the close action bar when we are configured to hide it */
}
@@ -232,11 +232,16 @@
padding-right: 5px; /* we need less room when sizing is shrink */
}
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-button-off.dirty-border-top > .tab-close {
display: none; /* hide dirty state when highlightModifiedTabs is enabled and when running without close button */
}
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-button-off.dirty:not(.dirty-border-top) {
background-repeat: no-repeat;
background-position-y: center;
background-position-x: calc(100% - 6px); /* to the right of the tab label */
padding-right: 28px; /* make room for dirty indication when we are running without close button */
padding-right: 0; /* remove extra padding when we are running without close button */
}
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-button-off > .tab-close {
pointer-events: none; /* don't allow dirty state/close button to be clicked when running without close button */
}
/* Editor Actions */

View File

@@ -14,7 +14,7 @@ import { EDITOR_TITLE_HEIGHT } from 'vs/workbench/browser/parts/editor/editor';
import { IAction } from 'vs/base/common/actions';
import { CLOSE_EDITOR_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands';
import { Color } from 'vs/base/common/color';
import { withNullAsUndefined } from 'vs/base/common/types';
import { withNullAsUndefined, assertIsDefined, assertAllDefined } from 'vs/base/common/types';
interface IRenderedEditorLabel {
editor?: IEditorInput;
@@ -22,23 +22,23 @@ interface IRenderedEditorLabel {
}
export class NoTabsTitleControl extends TitleControl {
private titleContainer: HTMLElement;
private editorLabel: IResourceLabel;
private titleContainer: HTMLElement | undefined;
private editorLabel: IResourceLabel | undefined;
private activeLabel: IRenderedEditorLabel = Object.create(null);
protected create(parent: HTMLElement): void {
this.titleContainer = parent;
this.titleContainer.draggable = true;
const titleContainer = this.titleContainer = parent;
titleContainer.draggable = true;
//Container listeners
this.registerContainerListeners();
this.registerContainerListeners(titleContainer);
// Gesture Support
this._register(Gesture.addTarget(this.titleContainer));
this._register(Gesture.addTarget(titleContainer));
const labelContainer = document.createElement('div');
addClass(labelContainer, 'label-container');
this.titleContainer.appendChild(labelContainer);
titleContainer.appendChild(labelContainer);
// Editor Label
this.editorLabel = this._register(this.instantiationService.createInstance(ResourceLabel, labelContainer, undefined)).element;
@@ -46,41 +46,41 @@ export class NoTabsTitleControl extends TitleControl {
// Breadcrumbs
this.createBreadcrumbsControl(labelContainer, { showFileIcons: false, showSymbolIcons: true, showDecorationColors: false, breadcrumbsBackground: () => Color.transparent });
toggleClass(this.titleContainer, 'breadcrumbs', Boolean(this.breadcrumbsControl));
this._register({ dispose: () => removeClass(this.titleContainer, 'breadcrumbs') }); // import to remove because the container is a shared dom node
toggleClass(titleContainer, 'breadcrumbs', Boolean(this.breadcrumbsControl));
this._register({ dispose: () => removeClass(titleContainer, 'breadcrumbs') }); // import to remove because the container is a shared dom node
// Right Actions Container
const actionsContainer = document.createElement('div');
addClass(actionsContainer, 'title-actions');
this.titleContainer.appendChild(actionsContainer);
titleContainer.appendChild(actionsContainer);
// Editor actions toolbar
this.createEditorActionsToolBar(actionsContainer);
}
private registerContainerListeners(): void {
private registerContainerListeners(titleContainer: HTMLElement): void {
// Group dragging
this.enableGroupDragging(this.titleContainer);
this.enableGroupDragging(titleContainer);
// Pin on double click
this._register(addDisposableListener(this.titleContainer, EventType.DBLCLICK, (e: MouseEvent) => this.onTitleDoubleClick(e)));
this._register(addDisposableListener(titleContainer, EventType.DBLCLICK, (e: MouseEvent) => this.onTitleDoubleClick(e)));
// Detect mouse click
this._register(addDisposableListener(this.titleContainer, EventType.MOUSE_UP, (e: MouseEvent) => this.onTitleClick(e)));
this._register(addDisposableListener(titleContainer, EventType.MOUSE_UP, (e: MouseEvent) => this.onTitleClick(e)));
// Detect touch
this._register(addDisposableListener(this.titleContainer, TouchEventType.Tap, (e: GestureEvent) => this.onTitleClick(e)));
this._register(addDisposableListener(titleContainer, TouchEventType.Tap, (e: GestureEvent) => this.onTitleClick(e)));
// Context Menu
this._register(addDisposableListener(this.titleContainer, EventType.CONTEXT_MENU, (e: Event) => {
this._register(addDisposableListener(titleContainer, EventType.CONTEXT_MENU, (e: Event) => {
if (this.group.activeEditor) {
this.onContextMenu(this.group.activeEditor, e, this.titleContainer);
this.onContextMenu(this.group.activeEditor, e, titleContainer);
}
}));
this._register(addDisposableListener(this.titleContainer, TouchEventType.Contextmenu, (e: Event) => {
this._register(addDisposableListener(titleContainer, TouchEventType.Contextmenu, (e: Event) => {
if (this.group.activeEditor) {
this.onContextMenu(this.group.activeEditor, e, this.titleContainer);
this.onContextMenu(this.group.activeEditor, e, titleContainer);
}
}));
}
@@ -157,10 +157,11 @@ export class NoTabsTitleControl extends TitleControl {
updateEditorDirty(editor: IEditorInput): void {
this.ifEditorIsActive(editor, () => {
const titleContainer = assertIsDefined(this.titleContainer);
if (editor.isDirty()) {
addClass(this.titleContainer, 'dirty');
addClass(titleContainer, 'dirty');
} else {
removeClass(this.titleContainer, 'dirty');
removeClass(titleContainer, 'dirty');
}
});
}
@@ -176,7 +177,9 @@ export class NoTabsTitleControl extends TitleControl {
}
protected handleBreadcrumbsEnablementChange(): void {
toggleClass(this.titleContainer, 'breadcrumbs', Boolean(this.breadcrumbsControl));
const titleContainer = assertIsDefined(this.titleContainer);
toggleClass(titleContainer, 'breadcrumbs', Boolean(this.breadcrumbsControl));
this.redraw();
}
@@ -230,9 +233,10 @@ export class NoTabsTitleControl extends TitleControl {
}
// Clear if there is no editor
const [titleContainer, editorLabel] = assertAllDefined(this.titleContainer, this.editorLabel);
if (!editor) {
removeClass(this.titleContainer, 'dirty');
this.editorLabel.clear();
removeClass(titleContainer, 'dirty');
editorLabel.clear();
this.clearEditorActionsToolbar();
}
@@ -261,11 +265,11 @@ export class NoTabsTitleControl extends TitleControl {
title = ''; // dont repeat what is already shown
}
this.editorLabel.setResource({ name, description, resource: resource || undefined }, { title: typeof title === 'string' ? title : undefined, italic: !isEditorPinned, extraClasses: ['no-tabs', 'title-label'] });
editorLabel.setResource({ name, description, resource: resource || undefined }, { title: typeof title === 'string' ? title : undefined, italic: !isEditorPinned, extraClasses: ['no-tabs', 'title-label'] });
if (isGroupActive) {
this.editorLabel.element.style.color = this.getColor(TAB_ACTIVE_FOREGROUND);
editorLabel.element.style.color = this.getColor(TAB_ACTIVE_FOREGROUND);
} else {
this.editorLabel.element.style.color = this.getColor(TAB_UNFOCUSED_ACTIVE_FOREGROUND);
editorLabel.element.style.color = this.getColor(TAB_UNFOCUSED_ACTIVE_FOREGROUND);
}
// Update Editor Actions Toolbar

View File

@@ -16,7 +16,7 @@ import { IMAGE_PREVIEW_BORDER } from 'vs/workbench/common/theme';
export interface IResourceDescriptor {
readonly resource: URI;
readonly name: string;
readonly size: number;
readonly size?: number;
readonly etag?: string;
readonly mime: string;
}
@@ -82,8 +82,8 @@ export class ResourceViewer {
container.className = 'monaco-resource-viewer';
// Large Files
if (descriptor.size > ResourceViewer.MAX_OPEN_INTERNAL_SIZE) {
return FileTooLargeFileView.create(container, descriptor, scrollbar, delegate);
if (typeof descriptor.size === 'number' && descriptor.size > ResourceViewer.MAX_OPEN_INTERNAL_SIZE) {
return FileTooLargeFileView.create(container, descriptor.size, scrollbar, delegate);
}
// Seemingly Binary Files
@@ -96,11 +96,11 @@ export class ResourceViewer {
class FileTooLargeFileView {
static create(
container: HTMLElement,
descriptor: IResourceDescriptor,
descriptorSize: number,
scrollbar: DomScrollableElement,
delegate: ResourceViewerDelegate
) {
const size = BinarySize.formatSize(descriptor.size);
const size = BinarySize.formatSize(descriptorSize);
delegate.metadataClb(size);
DOM.clearNode(container);

View File

@@ -17,6 +17,7 @@ import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsSe
import { SplitView, Sizing, Orientation } from 'vs/base/browser/ui/splitview/splitview';
import { Event, Relay, Emitter } from 'vs/base/common/event';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { assertIsDefined } from 'vs/base/common/types';
export class SideBySideEditor extends BaseEditor {
@@ -47,10 +48,10 @@ export class SideBySideEditor extends BaseEditor {
protected masterEditor?: BaseEditor;
protected detailsEditor?: BaseEditor;
private masterEditorContainer: HTMLElement;
private detailsEditorContainer: HTMLElement;
private masterEditorContainer: HTMLElement | undefined;
private detailsEditorContainer: HTMLElement | undefined;
private splitview: SplitView;
private splitview: SplitView | undefined;
private dimension: DOM.Dimension = new DOM.Dimension(0, 0);
private onDidCreateEditors = this._register(new Emitter<{ width: number; height: number; } | undefined>());
@@ -69,8 +70,8 @@ export class SideBySideEditor extends BaseEditor {
protected createEditor(parent: HTMLElement): void {
DOM.addClass(parent, 'side-by-side-editor');
this.splitview = this._register(new SplitView(parent, { orientation: Orientation.HORIZONTAL }));
this._register(this.splitview.onDidSashReset(() => this.splitview.distributeViewSizes()));
const splitview = this.splitview = this._register(new SplitView(parent, { orientation: Orientation.HORIZONTAL }));
this._register(this.splitview.onDidSashReset(() => splitview.distributeViewSizes()));
this.detailsEditorContainer = DOM.$('.details-editor-container');
this.splitview.addView({
@@ -140,7 +141,9 @@ export class SideBySideEditor extends BaseEditor {
layout(dimension: DOM.Dimension): void {
this.dimension = dimension;
this.splitview.layout(dimension.width);
const splitview = assertIsDefined(this.splitview);
splitview.layout(dimension.width);
}
getControl(): IEditorControl | undefined {
@@ -179,8 +182,8 @@ export class SideBySideEditor extends BaseEditor {
}
private setNewInput(newInput: SideBySideEditorInput, options: EditorOptions | undefined, token: CancellationToken): Promise<void> {
const detailsEditor = this.doCreateEditor(newInput.details, this.detailsEditorContainer);
const masterEditor = this.doCreateEditor(newInput.master, this.masterEditorContainer);
const detailsEditor = this.doCreateEditor(newInput.details, assertIsDefined(this.detailsEditorContainer));
const masterEditor = this.doCreateEditor(newInput.master, assertIsDefined(this.masterEditorContainer));
return this.onEditorsCreated(detailsEditor, masterEditor, newInput.details, newInput.master, options, token);
}
@@ -234,8 +237,13 @@ export class SideBySideEditor extends BaseEditor {
this.masterEditor = undefined;
}
this.detailsEditorContainer.innerHTML = '';
this.masterEditorContainer.innerHTML = '';
if (this.detailsEditorContainer) {
DOM.clearNode(this.detailsEditorContainer);
}
if (this.masterEditorContainer) {
DOM.clearNode(this.masterEditorContainer);
}
}
dispose(): void {

View File

@@ -40,7 +40,7 @@ import { CloseOneEditorAction } from 'vs/workbench/browser/parts/editor/editorAc
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { BreadcrumbsControl } from 'vs/workbench/browser/parts/editor/breadcrumbsControl';
import { IFileService } from 'vs/platform/files/common/files';
import { withNullAsUndefined } from 'vs/base/common/types';
import { withNullAsUndefined, assertAllDefined, assertIsDefined } from 'vs/base/common/types';
import { ILabelService } from 'vs/platform/label/common/label';
// {{SQL CARBON EDIT}} -- Display the editor's tab color
@@ -51,7 +51,7 @@ import { GlobalNewUntitledFileAction } from 'vs/workbench/contrib/files/browser/
// {{SQL CARBON EDIT}} -- End
interface IEditorInputLabel {
name: string;
name?: string;
description?: string;
title?: string;
}
@@ -60,19 +60,20 @@ type AugmentedLabel = IEditorInputLabel & { editor: IEditorInput };
export class TabsTitleControl extends TitleControl {
private titleContainer: HTMLElement;
private tabsContainer: HTMLElement;
private editorToolbarContainer: HTMLElement;
private tabsScrollbar: ScrollableElement;
private titleContainer: HTMLElement | undefined;
private tabsContainer: HTMLElement | undefined;
private editorToolbarContainer: HTMLElement | undefined;
private tabsScrollbar: ScrollableElement | undefined;
private closeOneEditorAction: CloseOneEditorAction;
private tabResourceLabels: ResourceLabels;
private tabLabels: IEditorInputLabel[] = [];
private tabDisposables: IDisposable[] = [];
private dimension: Dimension;
private dimension: Dimension | undefined;
private readonly layoutScheduled = this._register(new MutableDisposable());
private blockRevealActiveTab: boolean;
private blockRevealActiveTab: boolean | undefined;
constructor(
parent: HTMLElement,
@@ -97,6 +98,9 @@ export class TabsTitleControl extends TitleControl {
@ILabelService labelService: ILabelService
) {
super(parent, accessor, group, contextMenuService, instantiationService, contextKeyService, keybindingService, telemetryService, notificationService, menuService, quickOpenService, themeService, extensionService, configurationService, fileService, labelService);
this.tabResourceLabels = this._register(this.instantiationService.createInstance(ResourceLabels, DEFAULT_LABELS_CONTAINER));
this.closeOneEditorAction = this._register(this.instantiationService.createInstance(CloseOneEditorAction, CloseOneEditorAction.ID, CloseOneEditorAction.LABEL));
}
protected create(parent: HTMLElement): void {
@@ -113,13 +117,13 @@ export class TabsTitleControl extends TitleControl {
this.tabsContainer.draggable = true;
addClass(this.tabsContainer, 'tabs-container');
// Tabs Container listeners
this.registerTabsContainerListeners();
// Tabs Scrollbar
this.tabsScrollbar = this._register(this.createTabsScrollbar(this.tabsContainer));
tabsAndActionsContainer.appendChild(this.tabsScrollbar.getDomNode());
// Tabs Container listeners
this.registerTabsContainerListeners(this.tabsContainer, this.tabsScrollbar);
// Editor Toolbar Container
this.editorToolbarContainer = document.createElement('div');
addClass(this.editorToolbarContainer, 'editor-actions');
@@ -128,17 +132,11 @@ export class TabsTitleControl extends TitleControl {
// Editor Actions Toolbar
this.createEditorActionsToolBar(this.editorToolbarContainer);
// Close Action
this.closeOneEditorAction = this._register(this.instantiationService.createInstance(CloseOneEditorAction, CloseOneEditorAction.ID, CloseOneEditorAction.LABEL));
// Breadcrumbs (are on a separate row below tabs and actions)
const breadcrumbsContainer = document.createElement('div');
addClass(breadcrumbsContainer, 'tabs-breadcrumbs');
this.titleContainer.appendChild(breadcrumbsContainer);
this.createBreadcrumbsControl(breadcrumbsContainer, { showFileIcons: true, showSymbolIcons: true, showDecorationColors: false, breadcrumbsBackground: breadcrumbsBackground });
// Tab Labels
this.tabResourceLabels = this._register(this.instantiationService.createInstance(ResourceLabels, DEFAULT_LABELS_CONTAINER));
}
private createTabsScrollbar(scrollable: HTMLElement): ScrollableElement {
@@ -170,23 +168,23 @@ export class TabsTitleControl extends TitleControl {
this.group.relayout();
}
private registerTabsContainerListeners(): void {
private registerTabsContainerListeners(tabsContainer: HTMLElement, tabsScrollbar: ScrollableElement): void {
// Group dragging
this.enableGroupDragging(this.tabsContainer);
this.enableGroupDragging(tabsContainer);
// Forward scrolling inside the container to our custom scrollbar
this._register(addDisposableListener(this.tabsContainer, EventType.SCROLL, () => {
if (hasClass(this.tabsContainer, 'scroll')) {
this.tabsScrollbar.setScrollPosition({
scrollLeft: this.tabsContainer.scrollLeft // during DND the container gets scrolled so we need to update the custom scrollbar
this._register(addDisposableListener(tabsContainer, EventType.SCROLL, () => {
if (hasClass(tabsContainer, 'scroll')) {
tabsScrollbar.setScrollPosition({
scrollLeft: tabsContainer.scrollLeft // during DND the container gets scrolled so we need to update the custom scrollbar
});
}
}));
// New file when double clicking on tabs container (but not tabs)
this._register(addDisposableListener(this.tabsContainer, EventType.DBLCLICK, e => {
if (e.target === this.tabsContainer) {
this._register(addDisposableListener(tabsContainer, EventType.DBLCLICK, e => {
if (e.target === tabsContainer) {
EventHelper.stop(e);
// {{SQL CARBON EDIT}}
this.commandService.executeCommand(GlobalNewUntitledFileAction.ID).then(undefined, err => this.notificationService.warn(err));
@@ -194,29 +192,31 @@ export class TabsTitleControl extends TitleControl {
}));
// Prevent auto-scrolling (https://github.com/Microsoft/vscode/issues/16690)
this._register(addDisposableListener(this.tabsContainer, EventType.MOUSE_DOWN, (e: MouseEvent) => {
this._register(addDisposableListener(tabsContainer, EventType.MOUSE_DOWN, (e: MouseEvent) => {
if (e.button === 1) {
e.preventDefault();
}
}));
// Drop support
this._register(new DragAndDropObserver(this.tabsContainer, {
this._register(new DragAndDropObserver(tabsContainer, {
onDragEnter: e => {
// Always enable support to scroll while dragging
addClass(this.tabsContainer, 'scroll');
addClass(tabsContainer, 'scroll');
// Return if the target is not on the tabs container
if (e.target !== this.tabsContainer) {
this.updateDropFeedback(this.tabsContainer, false); // fixes https://github.com/Microsoft/vscode/issues/52093
if (e.target !== tabsContainer) {
this.updateDropFeedback(tabsContainer, false); // fixes https://github.com/Microsoft/vscode/issues/52093
return;
}
// Return if transfer is unsupported
if (!this.isSupportedDropTransfer(e)) {
e.dataTransfer!.dropEffect = 'none';
if (e.dataTransfer) {
e.dataTransfer.dropEffect = 'none';
}
return;
}
@@ -225,38 +225,46 @@ export class TabsTitleControl extends TitleControl {
if (this.editorTransfer.hasData(DraggedEditorIdentifier.prototype)) {
isLocalDragAndDrop = true;
const localDraggedEditor = this.editorTransfer.getData(DraggedEditorIdentifier.prototype)![0].identifier;
if (this.group.id === localDraggedEditor.groupId && this.group.getIndexOfEditor(localDraggedEditor.editor) === this.group.count - 1) {
e.dataTransfer!.dropEffect = 'none';
return;
const data = this.editorTransfer.getData(DraggedEditorIdentifier.prototype);
if (Array.isArray(data)) {
const localDraggedEditor = data[0].identifier;
if (this.group.id === localDraggedEditor.groupId && this.group.getIndexOfEditor(localDraggedEditor.editor) === this.group.count - 1) {
if (e.dataTransfer) {
e.dataTransfer.dropEffect = 'none';
}
return;
}
}
}
// Update the dropEffect to "copy" if there is no local data to be dragged because
// in that case we can only copy the data into and not move it from its source
if (!isLocalDragAndDrop) {
e.dataTransfer!.dropEffect = 'copy';
if (e.dataTransfer) {
e.dataTransfer.dropEffect = 'copy';
}
}
this.updateDropFeedback(this.tabsContainer, true);
this.updateDropFeedback(tabsContainer, true);
},
onDragLeave: e => {
this.updateDropFeedback(this.tabsContainer, false);
removeClass(this.tabsContainer, 'scroll');
this.updateDropFeedback(tabsContainer, false);
removeClass(tabsContainer, 'scroll');
},
onDragEnd: e => {
this.updateDropFeedback(this.tabsContainer, false);
removeClass(this.tabsContainer, 'scroll');
this.updateDropFeedback(tabsContainer, false);
removeClass(tabsContainer, 'scroll');
},
onDrop: e => {
this.updateDropFeedback(this.tabsContainer, false);
removeClass(this.tabsContainer, 'scroll');
this.updateDropFeedback(tabsContainer, false);
removeClass(tabsContainer, 'scroll');
if (e.target === this.tabsContainer) {
this.onDrop(e, this.group.count);
if (e.target === tabsContainer) {
this.onDrop(e, this.group.count, tabsContainer);
}
}
}));
@@ -273,8 +281,9 @@ export class TabsTitleControl extends TitleControl {
openEditor(editor: IEditorInput): void {
// Create tabs as needed
for (let i = this.tabsContainer.children.length; i < this.group.count; i++) {
this.tabsContainer.appendChild(this.createTab(i));
const [tabsContainer, tabsScrollbar] = assertAllDefined(this.tabsContainer, this.tabsScrollbar);
for (let i = tabsContainer.children.length; i < this.group.count; i++) {
tabsContainer.appendChild(this.createTab(i, tabsContainer, tabsScrollbar));
}
// An add of a tab requires to recompute all labels
@@ -305,10 +314,11 @@ export class TabsTitleControl extends TitleControl {
if (this.group.activeEditor) {
// Remove tabs that got closed
while (this.tabsContainer.children.length > this.group.count) {
const tabsContainer = assertIsDefined(this.tabsContainer);
while (tabsContainer.children.length > this.group.count) {
// Remove one tab from container (must be the last to keep indexes in order!)
(this.tabsContainer.lastChild as HTMLElement).remove();
(tabsContainer.lastChild as HTMLElement).remove();
// Remove associated tab label and widget
this.tabDisposables.pop()!.dispose();
@@ -323,7 +333,9 @@ export class TabsTitleControl extends TitleControl {
// No tabs to show
else {
clearNode(this.tabsContainer);
if (this.tabsContainer) {
clearNode(this.tabsContainer);
}
this.tabDisposables = dispose(this.tabDisposables);
this.tabResourceLabels.clear();
@@ -419,13 +431,14 @@ export class TabsTitleControl extends TitleControl {
private withTab(editor: IEditorInput, fn: (tabContainer: HTMLElement, tabLabelWidget: IResourceLabel, tabLabel: IEditorInputLabel) => void): void {
const editorIndex = this.group.getIndexOfEditor(editor);
const tabContainer = this.tabsContainer.children[editorIndex] as HTMLElement;
const tabsContainer = assertIsDefined(this.tabsContainer);
const tabContainer = tabsContainer.children[editorIndex] as HTMLElement;
if (tabContainer) {
fn(tabContainer, this.tabResourceLabels.get(editorIndex), this.tabLabels[editorIndex]);
}
}
private createTab(index: number): HTMLElement {
private createTab(index: number, tabsContainer: HTMLElement, tabsScrollbar: ScrollableElement): HTMLElement {
// Tab Container
const tabContainer = document.createElement('div');
@@ -462,14 +475,14 @@ export class TabsTitleControl extends TitleControl {
tabActionBar.onDidBeforeRun(() => this.blockRevealActiveTabOnce());
// Eventing
const eventsDisposable = this.registerTabListeners(tabContainer, index);
const eventsDisposable = this.registerTabListeners(tabContainer, index, tabsContainer, tabsScrollbar);
this.tabDisposables.push(combinedDisposable(eventsDisposable, tabActionBar, tabActionRunner, editorLabel));
return tabContainer;
}
private registerTabListeners(tab: HTMLElement, index: number): IDisposable {
private registerTabListeners(tab: HTMLElement, index: number, tabsContainer: HTMLElement, tabsScrollbar: ScrollableElement): IDisposable {
const disposables = new DisposableStore();
const handleClickOrTouch = (e: MouseEvent | GestureEvent): void => {
@@ -511,7 +524,7 @@ export class TabsTitleControl extends TitleControl {
// Touch Scroll Support
disposables.add(addDisposableListener(tab, TouchEventType.Change, (e: GestureEvent) => {
this.tabsScrollbar.setScrollPosition({ scrollLeft: this.tabsScrollbar.getScrollPosition().scrollLeft - e.translationX });
tabsScrollbar.setScrollPosition({ scrollLeft: tabsScrollbar.getScrollPosition().scrollLeft - e.translationX });
}));
// Close on mouse middle click
@@ -572,7 +585,7 @@ export class TabsTitleControl extends TitleControl {
if (target) {
handled = true;
this.group.openEditor(target, { preserveFocus: true });
(<HTMLElement>this.tabsContainer.childNodes[targetIndex]).focus();
(<HTMLElement>tabsContainer.childNodes[targetIndex]).focus();
}
}
@@ -581,8 +594,8 @@ export class TabsTitleControl extends TitleControl {
}
// moving in the tabs container can have an impact on scrolling position, so we need to update the custom scrollbar
this.tabsScrollbar.setScrollPosition({
scrollLeft: this.tabsContainer.scrollLeft
tabsScrollbar.setScrollPosition({
scrollLeft: tabsContainer.scrollLeft
});
}));
@@ -612,7 +625,9 @@ export class TabsTitleControl extends TitleControl {
this.editorTransfer.setData([new DraggedEditorIdentifier({ editor, groupId: this.group.id })], DraggedEditorIdentifier.prototype);
e.dataTransfer!.effectAllowed = 'copyMove';
if (e.dataTransfer) {
e.dataTransfer.effectAllowed = 'copyMove';
}
// Apply some datatransfer types to allow for dragging the element outside of the application
const resource = toResource(editor, { supportSideBySide: SideBySideEditor.MASTER });
@@ -634,7 +649,10 @@ export class TabsTitleControl extends TitleControl {
// Return if transfer is unsupported
if (!this.isSupportedDropTransfer(e)) {
e.dataTransfer!.dropEffect = 'none';
if (e.dataTransfer) {
e.dataTransfer.dropEffect = 'none';
}
return;
}
@@ -643,17 +661,25 @@ export class TabsTitleControl extends TitleControl {
if (this.editorTransfer.hasData(DraggedEditorIdentifier.prototype)) {
isLocalDragAndDrop = true;
const localDraggedEditor = this.editorTransfer.getData(DraggedEditorIdentifier.prototype)![0].identifier;
if (localDraggedEditor.editor === this.group.getEditor(index) && localDraggedEditor.groupId === this.group.id) {
e.dataTransfer!.dropEffect = 'none';
return;
const data = this.editorTransfer.getData(DraggedEditorIdentifier.prototype);
if (Array.isArray(data)) {
const localDraggedEditor = data[0].identifier;
if (localDraggedEditor.editor === this.group.getEditor(index) && localDraggedEditor.groupId === this.group.id) {
if (e.dataTransfer) {
e.dataTransfer.dropEffect = 'none';
}
return;
}
}
}
// Update the dropEffect to "copy" if there is no local data to be dragged because
// in that case we can only copy the data into and not move it from its source
if (!isLocalDragAndDrop) {
e.dataTransfer!.dropEffect = 'copy';
if (e.dataTransfer) {
e.dataTransfer.dropEffect = 'copy';
}
}
this.updateDropFeedback(tab, true, index);
@@ -675,7 +701,7 @@ export class TabsTitleControl extends TitleControl {
removeClass(tab, 'dragged-over');
this.updateDropFeedback(tab, false, index);
this.onDrop(e, index);
this.onDrop(e, index, tabsContainer);
}
}));
@@ -684,9 +710,12 @@ export class TabsTitleControl extends TitleControl {
private isSupportedDropTransfer(e: DragEvent): boolean {
if (this.groupTransfer.hasData(DraggedEditorGroupIdentifier.prototype)) {
const group = this.groupTransfer.getData(DraggedEditorGroupIdentifier.prototype)![0];
if (group.identifier === this.group.id) {
return false; // groups cannot be dropped on title area it originates from
const data = this.groupTransfer.getData(DraggedEditorGroupIdentifier.prototype);
if (Array.isArray(data)) {
const group = data[0];
if (group.identifier === this.group.id) {
return false; // groups cannot be dropped on title area it originates from
}
}
return true;
@@ -709,8 +738,8 @@ export class TabsTitleControl extends TitleControl {
const isActiveTab = isTab && !!editor && this.group.isActive(editor);
// Background
const noDNDBackgroundColor = isTab ? this.getColor(isActiveTab ? TAB_ACTIVE_BACKGROUND : TAB_INACTIVE_BACKGROUND) : null;
element.style.backgroundColor = isDND ? this.getColor(EDITOR_DRAG_AND_DROP_BACKGROUND) : noDNDBackgroundColor;
const noDNDBackgroundColor = isTab ? this.getColor(isActiveTab ? TAB_ACTIVE_BACKGROUND : TAB_INACTIVE_BACKGROUND) : '';
element.style.backgroundColor = (isDND ? this.getColor(EDITOR_DRAG_AND_DROP_BACKGROUND) : noDNDBackgroundColor) || '';
// Outline
const activeContrastBorderColor = this.getColor(activeContrastBorder);
@@ -745,7 +774,7 @@ export class TabsTitleControl extends TitleControl {
// Build labels and descriptions for each editor
const labels = this.group.editors.map(editor => ({
editor,
name: editor.getName()!,
name: editor.getName(),
description: editor.getDescription(verbosity),
title: withNullAsUndefined(editor.getTitle(Verbosity.LONG))
}));
@@ -856,7 +885,8 @@ export class TabsTitleControl extends TitleControl {
private forEachTab(fn: (editor: IEditorInput, index: number, tabContainer: HTMLElement, tabLabelWidget: IResourceLabel, tabLabel: IEditorInputLabel) => void): void {
this.group.editors.forEach((editor, index) => {
const tabContainer = this.tabsContainer.children[index] as HTMLElement;
const tabsContainer = assertIsDefined(this.tabsContainer);
const tabContainer = tabsContainer.children[index] as HTMLElement;
if (tabContainer) {
fn(editor, index, tabContainer, this.tabResourceLabels.get(index), this.tabLabels[index]);
}
@@ -870,7 +900,7 @@ export class TabsTitleControl extends TitleControl {
// Borders / Outline
const borderRightColor = (this.getColor(TAB_BORDER) || this.getColor(contrastBorder));
tabContainer.style.borderRight = borderRightColor ? `1px solid ${borderRightColor}` : null;
tabContainer.style.borderRight = borderRightColor ? `1px solid ${borderRightColor}` : '';
tabContainer.style.outlineColor = this.getColor(activeContrastBorder) || '';
// Settings
@@ -929,7 +959,7 @@ export class TabsTitleControl extends TitleControl {
// Container
addClass(tabContainer, 'active');
tabContainer.setAttribute('aria-selected', 'true');
tabContainer.style.backgroundColor = this.getColor(isGroupActive ? TAB_ACTIVE_BACKGROUND : TAB_UNFOCUSED_ACTIVE_BACKGROUND);
tabContainer.style.backgroundColor = this.getColor(isGroupActive ? TAB_ACTIVE_BACKGROUND : TAB_UNFOCUSED_ACTIVE_BACKGROUND) || '';
const activeTabBorderColorBottom = this.getColor(isGroupActive ? TAB_ACTIVE_BORDER : TAB_UNFOCUSED_ACTIVE_BORDER);
if (activeTabBorderColorBottom) {
@@ -959,8 +989,8 @@ export class TabsTitleControl extends TitleControl {
// Container
removeClass(tabContainer, 'active');
tabContainer.setAttribute('aria-selected', 'false');
tabContainer.style.backgroundColor = this.getColor(TAB_INACTIVE_BACKGROUND);
tabContainer.style.boxShadow = null;
tabContainer.style.backgroundColor = this.getColor(TAB_INACTIVE_BACKGROUND) || '';
tabContainer.style.boxShadow = '';
// Label
tabLabelWidget.element.style.color = this.getColor(isGroupActive ? TAB_INACTIVE_FOREGROUND : TAB_UNFOCUSED_INACTIVE_FOREGROUND);
@@ -1013,7 +1043,7 @@ export class TabsTitleControl extends TitleControl {
return hasModifiedBorderColor;
}
layout(dimension: Dimension): void {
layout(dimension: Dimension | undefined): void {
this.dimension = dimension;
const activeTab = this.group.activeEditor ? this.getTab(this.group.activeEditor) : undefined;
@@ -1026,7 +1056,9 @@ export class TabsTitleControl extends TitleControl {
// 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(() => {
this.doLayout(this.dimension);
const dimension = assertIsDefined(this.dimension);
this.doLayout(dimension);
this.layoutScheduled.clear();
});
}
@@ -1038,16 +1070,18 @@ export class TabsTitleControl extends TitleControl {
return;
}
const [tabsContainer, tabsScrollbar] = assertAllDefined(this.tabsContainer, this.tabsScrollbar);
if (this.breadcrumbsControl && !this.breadcrumbsControl.isHidden()) {
this.breadcrumbsControl.layout({ width: dimension.width, height: BreadcrumbsControl.HEIGHT });
this.tabsScrollbar.getDomNode().style.height = `${dimension.height - BreadcrumbsControl.HEIGHT}px`;
tabsScrollbar.getDomNode().style.height = `${dimension.height - BreadcrumbsControl.HEIGHT}px`;
}
const visibleContainerWidth = this.tabsContainer.offsetWidth;
const totalContainerWidth = this.tabsContainer.scrollWidth;
const visibleContainerWidth = tabsContainer.offsetWidth;
const totalContainerWidth = tabsContainer.scrollWidth;
let activeTabPosX: number;
let activeTabWidth: number;
let activeTabPosX: number | undefined;
let activeTabWidth: number | undefined;
if (!this.blockRevealActiveTab) {
activeTabPosX = activeTab.offsetLeft;
@@ -1055,33 +1089,33 @@ export class TabsTitleControl extends TitleControl {
}
// Update scrollbar
this.tabsScrollbar.setScrollDimensions({
tabsScrollbar.setScrollDimensions({
width: visibleContainerWidth,
scrollWidth: totalContainerWidth
});
// Return now if we are blocked to reveal the active tab and clear flag
if (this.blockRevealActiveTab) {
if (this.blockRevealActiveTab || typeof activeTabPosX !== 'number' || typeof activeTabWidth !== 'number') {
this.blockRevealActiveTab = false;
return;
}
// Reveal the active one
const containerScrollPosX = this.tabsScrollbar.getScrollPosition().scrollLeft;
const activeTabFits = activeTabWidth! <= visibleContainerWidth;
const containerScrollPosX = tabsScrollbar.getScrollPosition().scrollLeft;
const activeTabFits = activeTabWidth <= visibleContainerWidth;
// Tab is overflowing to the right: Scroll minimally until the element is fully visible to the right
// Note: only try to do this if we actually have enough width to give to show the tab fully!
if (activeTabFits && containerScrollPosX + visibleContainerWidth < activeTabPosX! + activeTabWidth!) {
this.tabsScrollbar.setScrollPosition({
scrollLeft: containerScrollPosX + ((activeTabPosX! + activeTabWidth!) /* right corner of tab */ - (containerScrollPosX + visibleContainerWidth) /* right corner of view port */)
if (activeTabFits && containerScrollPosX + visibleContainerWidth < activeTabPosX + activeTabWidth) {
tabsScrollbar.setScrollPosition({
scrollLeft: containerScrollPosX + ((activeTabPosX + activeTabWidth) /* right corner of tab */ - (containerScrollPosX + visibleContainerWidth) /* right corner of view port */)
});
}
// Tab is overlflowng to the left or does not fit: Scroll it into view to the left
else if (containerScrollPosX > activeTabPosX! || !activeTabFits) {
this.tabsScrollbar.setScrollPosition({
scrollLeft: activeTabPosX!
else if (containerScrollPosX > activeTabPosX || !activeTabFits) {
tabsScrollbar.setScrollPosition({
scrollLeft: activeTabPosX
});
}
}
@@ -1089,7 +1123,9 @@ export class TabsTitleControl extends TitleControl {
private getTab(editor: IEditorInput): HTMLElement | undefined {
const editorIndex = this.group.getIndexOfEditor(editor);
if (editorIndex >= 0) {
return this.tabsContainer.children[editorIndex] as HTMLElement;
const tabsContainer = assertIsDefined(this.tabsContainer);
return tabsContainer.children[editorIndex] as HTMLElement;
}
return undefined;
@@ -1116,49 +1152,55 @@ export class TabsTitleControl extends TitleControl {
return !!findParentWithClass(element, 'action-item', 'tab');
}
private onDrop(e: DragEvent, targetIndex: number): void {
private onDrop(e: DragEvent, targetIndex: number, tabsContainer: HTMLElement): void {
EventHelper.stop(e, true);
this.updateDropFeedback(this.tabsContainer, false);
removeClass(this.tabsContainer, 'scroll');
this.updateDropFeedback(tabsContainer, false);
removeClass(tabsContainer, 'scroll');
// Local Editor DND
if (this.editorTransfer.hasData(DraggedEditorIdentifier.prototype)) {
const draggedEditor = this.editorTransfer.getData(DraggedEditorIdentifier.prototype)![0].identifier;
const sourceGroup = this.accessor.getGroup(draggedEditor.groupId);
const data = this.editorTransfer.getData(DraggedEditorIdentifier.prototype);
if (Array.isArray(data)) {
const draggedEditor = data[0].identifier;
const sourceGroup = this.accessor.getGroup(draggedEditor.groupId);
if (sourceGroup) {
if (sourceGroup) {
// Move editor to target position and index
if (this.isMoveOperation(e, draggedEditor.groupId)) {
sourceGroup.moveEditor(draggedEditor.editor, this.group, { index: targetIndex });
// Move editor to target position and index
if (this.isMoveOperation(e, draggedEditor.groupId)) {
sourceGroup.moveEditor(draggedEditor.editor, this.group, { index: targetIndex });
}
// Copy editor to target position and index
else {
sourceGroup.copyEditor(draggedEditor.editor, this.group, { index: targetIndex });
}
}
// Copy editor to target position and index
else {
sourceGroup.copyEditor(draggedEditor.editor, this.group, { index: targetIndex });
}
this.group.focus();
this.editorTransfer.clearData(DraggedEditorIdentifier.prototype);
}
this.group.focus();
this.editorTransfer.clearData(DraggedEditorIdentifier.prototype);
}
// Local Editor Group DND
else if (this.groupTransfer.hasData(DraggedEditorGroupIdentifier.prototype)) {
const sourceGroup = this.accessor.getGroup(this.groupTransfer.getData(DraggedEditorGroupIdentifier.prototype)![0].identifier);
const data = this.groupTransfer.getData(DraggedEditorGroupIdentifier.prototype);
if (data) {
const sourceGroup = this.accessor.getGroup(data[0].identifier);
if (sourceGroup) {
const mergeGroupOptions: IMergeGroupOptions = { index: targetIndex };
if (!this.isMoveOperation(e, sourceGroup.id)) {
mergeGroupOptions.mode = MergeGroupMode.COPY_EDITORS;
if (sourceGroup) {
const mergeGroupOptions: IMergeGroupOptions = { index: targetIndex };
if (!this.isMoveOperation(e, sourceGroup.id)) {
mergeGroupOptions.mode = MergeGroupMode.COPY_EDITORS;
}
this.accessor.mergeGroup(sourceGroup, this.group, mergeGroupOptions);
}
this.accessor.mergeGroup(sourceGroup, this.group, mergeGroupOptions);
this.group.focus();
this.groupTransfer.clearData(DraggedEditorGroupIdentifier.prototype);
}
this.group.focus();
this.groupTransfer.clearData(DraggedEditorGroupIdentifier.prototype);
}
// External DND

View File

@@ -5,7 +5,7 @@
import * as nls from 'vs/nls';
import * as objects from 'vs/base/common/objects';
import { isFunction, isObject, isArray } from 'vs/base/common/types';
import { isFunction, isObject, isArray, assertIsDefined } 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';
@@ -110,7 +110,7 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditor {
}
// Set Editor Model
const diffEditor = this.getControl();
const diffEditor = assertIsDefined(this.getControl());
const resolvedDiffEditorModel = <TextDiffEditorModel>resolvedModel;
diffEditor.setModel(resolvedDiffEditorModel.textDiffEditorModel);
@@ -123,7 +123,7 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditor {
// Otherwise restore View State
let hasPreviousViewState = false;
if (!optionsGotApplied) {
hasPreviousViewState = this.restoreTextDiffEditorViewState(input);
hasPreviousViewState = this.restoreTextDiffEditorViewState(input, diffEditor);
}
// Diff navigator
@@ -148,17 +148,18 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditor {
setOptions(options: EditorOptions | undefined): void {
const textOptions = <TextEditorOptions>options;
if (textOptions && isFunction(textOptions.apply)) {
textOptions.apply(this.getControl(), ScrollType.Smooth);
const diffEditor = assertIsDefined(this.getControl());
textOptions.apply(diffEditor, ScrollType.Smooth);
}
}
private restoreTextDiffEditorViewState(input: EditorInput): boolean {
if (input instanceof DiffEditorInput) {
const resource = this.toDiffEditorViewStateResource(input);
private restoreTextDiffEditorViewState(editor: EditorInput, control: IDiffEditor): boolean {
if (editor instanceof DiffEditorInput) {
const resource = this.toDiffEditorViewStateResource(editor);
if (resource) {
const viewState = this.loadTextEditorViewState(resource);
if (viewState) {
this.getControl().restoreViewState(viewState);
control.restoreViewState(viewState);
return true;
}
@@ -268,7 +269,10 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditor {
this.saveTextDiffEditorViewState(this.input);
// Clear Model
this.getControl().setModel(null);
const diffEditor = this.getControl();
if (diffEditor) {
diffEditor.setModel(null);
}
// Pass to super
super.clearInput();
@@ -278,8 +282,8 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditor {
return this.diffNavigator;
}
getControl(): IDiffEditor {
return super.getControl() as IDiffEditor;
getControl(): IDiffEditor | undefined {
return super.getControl() as IDiffEditor | undefined;
}
protected loadTextEditorViewState(resource: URI): IDiffEditorViewState {
@@ -317,7 +321,7 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditor {
}
private retrieveTextDiffEditorViewState(resource: URI): IDiffEditorViewState | null {
const control = this.getControl();
const control = assertIsDefined(this.getControl());
const model = control.getModel();
if (!model || !model.modified || !model.original) {
return null; // view state always needs a model

View File

@@ -3,11 +3,11 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
import { localize } from 'vs/nls';
import { URI } from 'vs/base/common/uri';
import * as objects from 'vs/base/common/objects';
import * as types from 'vs/base/common/types';
import * as DOM from 'vs/base/browser/dom';
import { distinct, deepClone, assign } from 'vs/base/common/objects';
import { isObject, assertIsDefined } from 'vs/base/common/types';
import { Dimension } from 'vs/base/browser/dom';
import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';
import { EditorInput, EditorOptions, IEditorMemento, ITextEditor } from 'vs/workbench/common/editor';
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
@@ -37,8 +37,8 @@ export interface IEditorConfiguration {
* be subclassed and not instantiated.
*/
export abstract class BaseTextEditor extends BaseEditor implements ITextEditor {
private editorControl: IEditor;
private _editorContainer: HTMLElement;
private editorControl: IEditor | undefined;
private _editorContainer: HTMLElement | undefined;
private hasPendingConfigurationChange: boolean | undefined;
private lastAppliedEditorOptions?: IEditorOptions;
private editorMemento: IEditorMemento<IEditorViewState>;
@@ -96,8 +96,8 @@ export abstract class BaseTextEditor extends BaseEditor implements ITextEditor {
protected computeConfiguration(configuration: IEditorConfiguration): IEditorOptions {
// Specific editor options always overwrite user configuration
const editorConfiguration: IEditorOptions = types.isObject(configuration.editor) ? objects.deepClone(configuration.editor) : Object.create(null);
objects.assign(editorConfiguration, this.getConfigurationOverrides());
const editorConfiguration: IEditorOptions = isObject(configuration.editor) ? deepClone(configuration.editor) : Object.create(null);
assign(editorConfiguration, this.getConfigurationOverrides());
// ARIA label
editorConfiguration.ariaLabel = this.computeAriaLabel();
@@ -111,7 +111,7 @@ export abstract class BaseTextEditor extends BaseEditor implements ITextEditor {
// Apply group information to help identify in which group we are
if (ariaLabel) {
if (this.group) {
ariaLabel = nls.localize('editorLabelWithGroup', "{0}, {1}.", ariaLabel, this.group.label);
ariaLabel = localize('editorLabelWithGroup', "{0}, {1}.", ariaLabel, this.group.label);
}
}
@@ -120,7 +120,7 @@ export abstract class BaseTextEditor extends BaseEditor implements ITextEditor {
protected getConfigurationOverrides(): IEditorOptions {
const overrides = {};
objects.assign(overrides, {
assign(overrides, {
overviewRulerLanes: 3,
lineNumbersMinChars: 3,
fixedOverflowWidgets: true
@@ -133,7 +133,7 @@ export abstract class BaseTextEditor extends BaseEditor implements ITextEditor {
// Editor for Text
this._editorContainer = parent;
this.editorControl = this._register(this.createEditorControl(parent, this.computeConfiguration(this.configurationService.getValue<IEditorConfiguration>(this.getResource()!))));
this.editorControl = this._register(this.createEditorControl(parent, this.computeConfiguration(this.configurationService.getValue<IEditorConfiguration>(this.getResource()))));
// Model & Language changes
const codeEditor = getCodeEditor(this.editorControl);
@@ -197,33 +197,40 @@ export abstract class BaseTextEditor extends BaseEditor implements ITextEditor {
// Update editor options after having set the input. We do this because there can be
// editor input specific options (e.g. an ARIA label depending on the input showing)
this.updateEditorConfiguration();
this._editorContainer.setAttribute('aria-label', this.computeAriaLabel());
const editorContainer = assertIsDefined(this._editorContainer);
editorContainer.setAttribute('aria-label', this.computeAriaLabel());
}
protected setEditorVisible(visible: boolean, group: IEditorGroup | undefined): void {
// Pass on to Editor
const editorControl = assertIsDefined(this.editorControl);
if (visible) {
this.consumePendingConfigurationChangeEvent();
this.editorControl.onVisible();
editorControl.onVisible();
} else {
this.editorControl.onHide();
editorControl.onHide();
}
super.setEditorVisible(visible, group);
}
focus(): void {
this.editorControl.focus();
}
layout(dimension: DOM.Dimension): void {
// Pass on to Editor
this.editorControl.layout(dimension);
const editorControl = assertIsDefined(this.editorControl);
editorControl.focus();
}
getControl(): IEditor {
layout(dimension: Dimension): void {
// Pass on to Editor
const editorControl = assertIsDefined(this.editorControl);
editorControl.layout(dimension);
}
getControl(): IEditor | undefined {
return this.editorControl;
}
@@ -296,7 +303,7 @@ export abstract class BaseTextEditor extends BaseEditor implements ITextEditor {
// have been applied to the editor directly.
let editorSettingsToApply = editorConfiguration;
if (this.lastAppliedEditorOptions) {
editorSettingsToApply = objects.distinct(this.lastAppliedEditorOptions, editorSettingsToApply);
editorSettingsToApply = distinct(this.lastAppliedEditorOptions, editorSettingsToApply);
}
if (Object.keys(editorSettingsToApply).length > 0) {

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
import * as types from 'vs/base/common/types';
import { assertIsDefined, isFunction } from 'vs/base/common/types';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { IEditorOptions } from 'vs/editor/common/config/editorOptions';
import { TextEditorOptions, EditorInput, EditorOptions } from 'vs/workbench/common/editor';
@@ -19,7 +19,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { Event } from 'vs/base/common/event';
import { ScrollType } from 'vs/editor/common/editorCommon';
import { ScrollType, IEditor } from 'vs/editor/common/editorCommon';
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { CancellationToken } from 'vs/base/common/cancellation';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
@@ -74,36 +74,37 @@ export class AbstractTextResourceEditor extends BaseTextEditor {
}
// Set Editor Model
const textEditor = this.getControl();
const textEditor = assertIsDefined(this.getControl());
const textEditorModel = resolvedModel.textEditorModel;
textEditor.setModel(textEditorModel);
// Apply Options from TextOptions
let optionsGotApplied = false;
const textOptions = <TextEditorOptions>options;
if (textOptions && types.isFunction(textOptions.apply)) {
if (textOptions && isFunction(textOptions.apply)) {
optionsGotApplied = textOptions.apply(textEditor, ScrollType.Immediate);
}
// Otherwise restore View State
if (!optionsGotApplied) {
this.restoreTextResourceEditorViewState(input);
this.restoreTextResourceEditorViewState(input, textEditor);
}
}
private restoreTextResourceEditorViewState(input: EditorInput) {
if (input instanceof UntitledEditorInput || input instanceof ResourceEditorInput) {
const viewState = this.loadTextEditorViewState(input.getResource());
private restoreTextResourceEditorViewState(editor: EditorInput, control: IEditor) {
if (editor instanceof UntitledEditorInput || editor instanceof ResourceEditorInput) {
const viewState = this.loadTextEditorViewState(editor.getResource());
if (viewState) {
this.getControl().restoreViewState(viewState);
control.restoreViewState(viewState);
}
}
}
setOptions(options: EditorOptions | undefined): void {
const textOptions = <TextEditorOptions>options;
if (textOptions && types.isFunction(textOptions.apply)) {
textOptions.apply(this.getControl(), ScrollType.Smooth);
if (textOptions && isFunction(textOptions.apply)) {
const textEditor = assertIsDefined(this.getControl());
textOptions.apply(textEditor, ScrollType.Smooth);
}
}
@@ -120,7 +121,7 @@ export class AbstractTextResourceEditor extends BaseTextEditor {
const isReadonly = !(this.input instanceof UntitledEditorInput);
let ariaLabel: string;
const inputName = input && input.getName();
const inputName = input?.getName();
if (isReadonly) {
ariaLabel = inputName ? nls.localize('readonlyEditorWithInputAriaLabel', "{0}. Readonly text editor.", inputName) : nls.localize('readonlyEditorAriaLabel', "Readonly text editor.");
} else {
@@ -149,7 +150,10 @@ export class AbstractTextResourceEditor extends BaseTextEditor {
this.saveTextResourceEditorViewState(this.input);
// Clear Model
this.getControl().setModel(null);
const textEditor = this.getControl();
if (textEditor) {
textEditor.setModel(null);
}
super.clearInput();
}

View File

@@ -39,7 +39,7 @@ import { Themable } from 'vs/workbench/common/theme';
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 } from 'vs/base/common/types';
import { withNullAsUndefined, withUndefinedAsNull, assertIsDefined } from 'vs/base/common/types';
import { ILabelService } from 'vs/platform/label/common/label';
export interface IToolbarActions {
@@ -57,7 +57,7 @@ export abstract class TitleControl extends Themable {
private currentPrimaryEditorActionIds: string[] = [];
private currentSecondaryEditorActionIds: string[] = [];
private editorActionsToolbar: ToolBar;
private editorActionsToolbar: ToolBar | undefined;
private resourceContext: ResourceContextKey;
private editorPinnedContext: IContextKey<boolean>;
@@ -189,7 +189,8 @@ export abstract class TitleControl extends Themable {
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
) {
this.editorActionsToolbar.setActions(primaryEditorActions, secondaryEditorActions)();
const editorActionsToolbar = assertIsDefined(this.editorActionsToolbar);
editorActionsToolbar.setActions(primaryEditorActions, secondaryEditorActions)();
this.currentPrimaryEditorActionIds = primaryEditorActionIds;
this.currentSecondaryEditorActionIds = secondaryEditorActionIds;
@@ -228,7 +229,7 @@ export abstract class TitleControl extends Themable {
const activeControl = this.group.activeControl;
if (activeControl instanceof BaseEditor) {
const codeEditor = getCodeEditor(activeControl.getControl());
const scopedContextKeyService = codeEditor && codeEditor.invokeWithinContext(accessor => accessor.get(IContextKeyService)) || this.contextKeyService;
const scopedContextKeyService = codeEditor?.invokeWithinContext(accessor => accessor.get(IContextKeyService)) || this.contextKeyService;
const titleBarMenu = this.menuService.createMenu(MenuId.EditorTitle, scopedContextKeyService);
this.editorToolBarMenuDisposables.add(titleBarMenu);
this.editorToolBarMenuDisposables.add(titleBarMenu.onDidChange(() => {
@@ -242,7 +243,9 @@ export abstract class TitleControl extends Themable {
}
protected clearEditorActionsToolbar(): void {
this.editorActionsToolbar.setActions([], [])();
if (this.editorActionsToolbar) {
this.editorActionsToolbar.setActions([], [])();
}
this.currentPrimaryEditorActionIds = [];
this.currentSecondaryEditorActionIds = [];
@@ -258,7 +261,9 @@ export abstract class TitleControl extends Themable {
// Set editor group as transfer
this.groupTransfer.setData([new DraggedEditorGroupIdentifier(this.group.id)], DraggedEditorGroupIdentifier.prototype);
e.dataTransfer!.effectAllowed = 'copyMove';
if (e.dataTransfer) {
e.dataTransfer.effectAllowed = 'copyMove';
}
// If tabs are disabled, treat dragging as if an editor tab was dragged
if (!this.accessor.partOptions.showTabs) {

View File

@@ -38,6 +38,7 @@
height: 22px;
margin-right: 4px;
margin-left: 4px;
font-size: 18px;
background-position: center;
background-repeat: no-repeat;
}
@@ -70,8 +71,7 @@
}
.monaco-workbench .notifications-list-container .notification-list-item:hover .notification-list-item-toolbar-container,
.monaco-workbench .notifications-list-container .monaco-list-row.focused .notification-list-item .notification-list-item-toolbar-container,
.monaco-workbench .notifications-list-container .notification-list-item.expanded .notification-list-item-toolbar-container {
.monaco-workbench .notifications-list-container .monaco-list-row.focused .notification-list-item .notification-list-item-toolbar-container {
display: block;
}

View File

@@ -43,7 +43,7 @@ export class ClearAllNotificationsAction extends Action {
label: string,
@ICommandService private readonly commandService: ICommandService
) {
super(id, label, 'codicon-close-all');
super(id, label, 'codicon-clear-all');
}
run(notification: INotificationViewItem): Promise<any> {
@@ -63,7 +63,7 @@ export class HideNotificationsCenterAction extends Action {
label: string,
@ICommandService private readonly commandService: ICommandService
) {
super(id, label, 'codicon-chevron-down');
super(id, label, 'codicon-close');
}
run(notification: INotificationViewItem): Promise<any> {

View File

@@ -22,21 +22,23 @@ import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
import { ClearAllNotificationsAction, HideNotificationsCenterAction, NotificationActionRunner } from 'vs/workbench/browser/parts/notifications/notificationsActions';
import { IAction } from 'vs/base/common/actions';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { assertAllDefined, assertIsDefined } from 'vs/base/common/types';
export class NotificationsCenter extends Themable {
private static MAX_DIMENSIONS = new Dimension(450, 400);
private static readonly MAX_DIMENSIONS = new Dimension(450, 400);
private readonly _onDidChangeVisibility: Emitter<void> = this._register(new Emitter<void>());
readonly onDidChangeVisibility: Event<void> = this._onDidChangeVisibility.event;
private notificationsCenterContainer: HTMLElement;
private notificationsCenterHeader: HTMLElement;
private notificationsCenterTitle: HTMLSpanElement;
private notificationsList: NotificationsList;
private _isVisible: boolean;
private workbenchDimensions: Dimension;
private notificationsCenterContainer: HTMLElement | undefined;
private notificationsCenterHeader: HTMLElement | undefined;
private notificationsCenterTitle: HTMLSpanElement | undefined;
private notificationsList: NotificationsList | undefined;
private _isVisible: boolean | undefined;
private workbenchDimensions: Dimension | undefined;
private notificationsCenterVisibleContextKey: IContextKey<boolean>;
private clearAllAction: ClearAllNotificationsAction | undefined;
constructor(
private container: HTMLElement,
@@ -61,12 +63,13 @@ export class NotificationsCenter extends Themable {
}
get isVisible(): boolean {
return this._isVisible;
return !!this._isVisible;
}
show(): void {
if (this._isVisible) {
this.notificationsList.show(true /* focus */);
const notificationsList = assertIsDefined(this.notificationsList);
notificationsList.show(true /* focus */);
return; // already visible
}
@@ -80,18 +83,19 @@ export class NotificationsCenter extends Themable {
this.updateTitle();
// Make visible
const [notificationsList, notificationsCenterContainer] = assertAllDefined(this.notificationsList, this.notificationsCenterContainer);
this._isVisible = true;
addClass(this.notificationsCenterContainer, 'visible');
this.notificationsList.show();
addClass(notificationsCenterContainer, 'visible');
notificationsList.show();
// Layout
this.layout(this.workbenchDimensions);
// Show all notifications that are present now
this.notificationsList.updateNotificationsList(0, 0, this.model.notifications);
notificationsList.updateNotificationsList(0, 0, this.model.notifications);
// Focus first
this.notificationsList.focusFirst();
notificationsList.focusFirst();
// Theming
this.updateStyles();
@@ -104,10 +108,14 @@ export class NotificationsCenter extends Themable {
}
private updateTitle(): void {
const [notificationsCenterTitle, clearAllAction] = assertAllDefined(this.notificationsCenterTitle, this.clearAllAction);
if (this.model.notifications.length === 0) {
this.notificationsCenterTitle.textContent = localize('notificationsEmpty', "No new notifications");
notificationsCenterTitle.textContent = localize('notificationsEmpty', "No new notifications");
clearAllAction.enabled = false;
} else {
this.notificationsCenterTitle.textContent = localize('notifications', "Notifications");
notificationsCenterTitle.textContent = localize('notifications', "Notifications");
clearAllAction.enabled = true;
}
}
@@ -139,12 +147,12 @@ export class NotificationsCenter extends Themable {
actionRunner
}));
this.clearAllAction = this._register(this.instantiationService.createInstance(ClearAllNotificationsAction, ClearAllNotificationsAction.ID, ClearAllNotificationsAction.LABEL));
notificationsToolBar.push(this.clearAllAction, { icon: true, label: false, keybinding: this.getKeybindingLabel(this.clearAllAction) });
const hideAllAction = this._register(this.instantiationService.createInstance(HideNotificationsCenterAction, HideNotificationsCenterAction.ID, HideNotificationsCenterAction.LABEL));
notificationsToolBar.push(hideAllAction, { icon: true, label: false, keybinding: this.getKeybindingLabel(hideAllAction) });
const clearAllAction = this._register(this.instantiationService.createInstance(ClearAllNotificationsAction, ClearAllNotificationsAction.ID, ClearAllNotificationsAction.LABEL));
notificationsToolBar.push(clearAllAction, { icon: true, label: false, keybinding: this.getKeybindingLabel(clearAllAction) });
// Notifications List
this.notificationsList = this.instantiationService.createInstance(NotificationsList, this.notificationsCenterContainer, {
ariaLabel: localize('notificationsList', "Notifications List")
@@ -167,16 +175,17 @@ export class NotificationsCenter extends Themable {
let focusGroup = false;
// Update notifications list based on event
const [notificationsList, notificationsCenterContainer] = assertAllDefined(this.notificationsList, this.notificationsCenterContainer);
switch (e.kind) {
case NotificationChangeType.ADD:
this.notificationsList.updateNotificationsList(e.index, 0, [e.item]);
notificationsList.updateNotificationsList(e.index, 0, [e.item]);
break;
case NotificationChangeType.CHANGE:
this.notificationsList.updateNotificationsList(e.index, 1, [e.item]);
notificationsList.updateNotificationsList(e.index, 1, [e.item]);
break;
case NotificationChangeType.REMOVE:
focusGroup = isAncestor(document.activeElement, this.notificationsCenterContainer);
this.notificationsList.updateNotificationsList(e.index, 1);
focusGroup = isAncestor(document.activeElement, notificationsCenterContainer);
notificationsList.updateNotificationsList(e.index, 1);
break;
}
@@ -195,7 +204,7 @@ export class NotificationsCenter extends Themable {
}
hide(): void {
if (!this._isVisible || !this.notificationsCenterContainer) {
if (!this._isVisible || !this.notificationsCenterContainer || !this.notificationsList) {
return; // already hidden
}
@@ -219,22 +228,22 @@ export class NotificationsCenter extends Themable {
}
protected updateStyles(): void {
if (this.notificationsCenterContainer) {
if (this.notificationsCenterContainer && this.notificationsCenterHeader) {
const widgetShadowColor = this.getColor(widgetShadow);
this.notificationsCenterContainer.style.boxShadow = widgetShadowColor ? `0 0px 8px ${widgetShadowColor}` : null;
this.notificationsCenterContainer.style.boxShadow = widgetShadowColor ? `0 0px 8px ${widgetShadowColor}` : '';
const borderColor = this.getColor(NOTIFICATIONS_CENTER_BORDER);
this.notificationsCenterContainer.style.border = borderColor ? `1px solid ${borderColor}` : null;
this.notificationsCenterContainer.style.border = borderColor ? `1px solid ${borderColor}` : '';
const headerForeground = this.getColor(NOTIFICATIONS_CENTER_HEADER_FOREGROUND);
this.notificationsCenterHeader.style.color = headerForeground ? headerForeground.toString() : null;
const headerBackground = this.getColor(NOTIFICATIONS_CENTER_HEADER_BACKGROUND);
this.notificationsCenterHeader.style.background = headerBackground ? headerBackground.toString() : null;
this.notificationsCenterHeader.style.background = headerBackground ? headerBackground.toString() : '';
}
}
layout(dimension: Dimension): void {
layout(dimension: Dimension | undefined): void {
this.workbenchDimensions = dimension;
if (this._isVisible && this.notificationsCenterContainer) {
@@ -264,7 +273,8 @@ export class NotificationsCenter extends Themable {
}
// Apply to list
this.notificationsList.layout(Math.min(maxWidth, availableWidth), Math.min(maxHeight, availableHeight));
const notificationsList = assertIsDefined(this.notificationsList);
notificationsList.layout(Math.min(maxWidth, availableWidth), Math.min(maxHeight, availableHeight));
}
}

View File

@@ -16,10 +16,11 @@ import { NotificationsListDelegate, NotificationRenderer } from 'vs/workbench/br
import { NotificationActionRunner, CopyNotificationMessageAction } from 'vs/workbench/browser/parts/notifications/notificationsActions';
import { NotificationFocusedContext } from 'vs/workbench/browser/parts/notifications/notificationsCommands';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { assertIsDefined, assertAllDefined } from 'vs/base/common/types';
export class NotificationsList extends Themable {
private listContainer: HTMLElement;
private list: WorkbenchList<INotificationViewItem>;
private listContainer: HTMLElement | undefined;
private list: WorkbenchList<INotificationViewItem> | undefined;
private viewModel: INotificationViewItem[];
private isVisible: boolean | undefined;
@@ -38,7 +39,8 @@ export class NotificationsList extends Themable {
show(focus?: boolean): void {
if (this.isVisible) {
if (focus) {
this.list.domFocus();
const list = assertIsDefined(this.list);
list.domFocus();
}
return; // already visible
@@ -54,7 +56,8 @@ export class NotificationsList extends Themable {
// Focus
if (focus) {
this.list.domFocus();
const list = assertIsDefined(this.list);
list.domFocus();
}
}
@@ -70,7 +73,7 @@ export class NotificationsList extends Themable {
const renderer = this.instantiationService.createInstance(NotificationRenderer, actionRunner);
// List
this.list = this._register(this.instantiationService.createInstance(
const list = this.list = this._register(this.instantiationService.createInstance(
WorkbenchList,
'NotificationsList',
this.listContainer,
@@ -85,13 +88,13 @@ export class NotificationsList extends Themable {
// Context menu to copy message
const copyAction = this._register(this.instantiationService.createInstance(CopyNotificationMessageAction, CopyNotificationMessageAction.ID, CopyNotificationMessageAction.LABEL));
this._register((this.list.onContextMenu(e => {
this._register((list.onContextMenu(e => {
if (!e.element) {
return;
}
this.contextMenuService.showContextMenu({
getAnchor: () => e.anchor!,
getAnchor: () => e.anchor,
getActions: () => [copyAction],
getActionsContext: () => e.element,
actionRunner
@@ -99,27 +102,27 @@ export class NotificationsList extends Themable {
})));
// Toggle on double click
this._register((this.list.onMouseDblClick(event => (event.element as INotificationViewItem).toggle())));
this._register((list.onMouseDblClick(event => (event.element as INotificationViewItem).toggle())));
// Clear focus when DOM focus moves out
// Use document.hasFocus() to not clear the focus when the entire window lost focus
// This ensures that when the focus comes back, the notification is still focused
const listFocusTracker = this._register(trackFocus(this.list.getHTMLElement()));
const listFocusTracker = this._register(trackFocus(list.getHTMLElement()));
this._register(listFocusTracker.onDidBlur(() => {
if (document.hasFocus()) {
this.list.setFocus([]);
list.setFocus([]);
}
}));
// Context key
NotificationFocusedContext.bindTo(this.list.contextKeyService);
NotificationFocusedContext.bindTo(list.contextKeyService);
// Only allow for focus in notifications, as the
// selection is too strong over the contents of
// the notification
this._register(this.list.onSelectionChange(e => {
this._register(list.onSelectionChange(e => {
if (e.indexes.length > 0) {
this.list.setSelection([]);
list.setSelection([]);
}
}));
@@ -129,23 +132,24 @@ export class NotificationsList extends Themable {
}
updateNotificationsList(start: number, deleteCount: number, items: INotificationViewItem[] = []) {
const listHasDOMFocus = isAncestor(document.activeElement, this.listContainer);
const [list, listContainer] = assertAllDefined(this.list, this.listContainer);
const listHasDOMFocus = isAncestor(document.activeElement, listContainer);
// Remember focus and relative top of that item
const focusedIndex = this.list.getFocus()[0];
const focusedIndex = list.getFocus()[0];
const focusedItem = this.viewModel[focusedIndex];
let focusRelativeTop: number | null = null;
if (typeof focusedIndex === 'number') {
focusRelativeTop = this.list.getRelativeTop(focusedIndex);
focusRelativeTop = list.getRelativeTop(focusedIndex);
}
// Update view model
this.viewModel.splice(start, deleteCount, ...items);
// Update list
this.list.splice(start, deleteCount, items);
this.list.layout();
list.splice(start, deleteCount, items);
list.layout();
// Hide if no more notifications to show
if (this.viewModel.length === 0) {
@@ -167,15 +171,15 @@ export class NotificationsList extends Themable {
}
if (typeof focusRelativeTop === 'number') {
this.list.reveal(indexToFocus, focusRelativeTop);
list.reveal(indexToFocus, focusRelativeTop);
}
this.list.setFocus([indexToFocus]);
list.setFocus([indexToFocus]);
}
// Restore DOM focus if we had focus before
if (listHasDOMFocus) {
this.list.domFocus();
list.domFocus();
}
}
@@ -204,7 +208,7 @@ export class NotificationsList extends Themable {
}
hasFocus(): boolean {
if (!this.isVisible || !this.list) {
if (!this.isVisible || !this.listContainer) {
return false; // hidden
}
@@ -217,7 +221,7 @@ export class NotificationsList extends Themable {
this.listContainer.style.color = foreground ? foreground.toString() : null;
const background = this.getColor(NOTIFICATIONS_BACKGROUND);
this.listContainer.style.background = background ? background.toString() : null;
this.listContainer.style.background = background ? background.toString() : '';
const outlineColor = this.getColor(contrastBorder);
this.listContainer.style.outlineColor = outlineColor ? outlineColor.toString() : '';
@@ -225,7 +229,7 @@ export class NotificationsList extends Themable {
}
layout(width: number, maxHeight?: number): void {
if (this.list) {
if (this.listContainer && this.list) {
this.listContainer.style.width = `${width}px`;
if (typeof maxHeight === 'number') {

View File

@@ -18,11 +18,12 @@ import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editor
import { NotificationsToastsVisibleContext } from 'vs/workbench/browser/parts/notifications/notificationsCommands';
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { localize } from 'vs/nls';
import { Severity } from 'vs/platform/notification/common/notification';
import { Severity, NotificationsFilter } from 'vs/platform/notification/common/notification';
import { ScrollbarVisibility } from 'vs/base/common/scrollable';
import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
import { IHostService } from 'vs/workbench/services/host/browser/host';
import { timeout } from 'vs/base/common/async';
import { assertIsDefined } from 'vs/base/common/types';
interface INotificationToast {
item: INotificationViewItem;
@@ -40,8 +41,8 @@ enum ToastVisibility {
export class NotificationsToasts extends Themable {
private static MAX_WIDTH = 450;
private static MAX_NOTIFICATIONS = 3;
private static readonly MAX_WIDTH = 450;
private static readonly MAX_NOTIFICATIONS = 3;
private static PURGE_TIMEOUT: { [severity: number]: number } = (() => {
const intervals = Object.create(null);
@@ -52,8 +53,8 @@ export class NotificationsToasts extends Themable {
return intervals;
})();
private notificationsToastsContainer: HTMLElement;
private workbenchDimensions: Dimension;
private notificationsToastsContainer: HTMLElement | undefined;
private workbenchDimensions: Dimension | undefined;
private isNotificationsCenterVisible: boolean | undefined;
private mapNotificationToToast: Map<INotificationViewItem, INotificationToast>;
private notificationsToastsVisibleContextKey: IContextKey<boolean>;
@@ -91,6 +92,13 @@ export class NotificationsToasts extends Themable {
// Update toasts on notification changes
this._register(this.model.onDidNotificationChange(e => this.onDidNotificationChange(e)));
});
// Filter
this._register(this.model.onDidFilterChange(filter => {
if (filter === NotificationsFilter.SILENT || filter === NotificationsFilter.ERROR) {
this.hide();
}
}));
}
private async onCanShowNotifications(): Promise<void> {
@@ -125,15 +133,16 @@ export class NotificationsToasts extends Themable {
}
// Lazily create toasts containers
if (!this.notificationsToastsContainer) {
this.notificationsToastsContainer = document.createElement('div');
addClass(this.notificationsToastsContainer, 'notifications-toasts');
let notificationsToastsContainer = this.notificationsToastsContainer;
if (!notificationsToastsContainer) {
notificationsToastsContainer = this.notificationsToastsContainer = document.createElement('div');
addClass(notificationsToastsContainer, 'notifications-toasts');
this.container.appendChild(this.notificationsToastsContainer);
this.container.appendChild(notificationsToastsContainer);
}
// Make Visible
addClass(this.notificationsToastsContainer, 'visible');
addClass(notificationsToastsContainer, 'visible');
const itemDisposables = new DisposableStore();
@@ -141,11 +150,11 @@ export class NotificationsToasts extends Themable {
const notificationToastContainer = document.createElement('div');
addClass(notificationToastContainer, 'notification-toast-container');
const firstToast = this.notificationsToastsContainer.firstChild;
const firstToast = notificationsToastsContainer.firstChild;
if (firstToast) {
this.notificationsToastsContainer.insertBefore(notificationToastContainer, firstToast); // always first
notificationsToastsContainer.insertBefore(notificationToastContainer, firstToast); // always first
} else {
this.notificationsToastsContainer.appendChild(notificationToastContainer);
notificationsToastsContainer.appendChild(notificationToastContainer);
}
// Toast
@@ -155,6 +164,7 @@ export class NotificationsToasts extends Themable {
// Create toast with item and show
const notificationList = this.instantiationService.createInstance(NotificationsList, notificationToast, {
ariaRole: 'dialog', // https://github.com/microsoft/vscode/issues/82728
ariaLabel: localize('notificationsToast', "Notification Toast"),
verticalScrollMode: ScrollbarVisibility.Hidden
});
@@ -164,8 +174,8 @@ export class NotificationsToasts extends Themable {
this.mapNotificationToToast.set(item, toast);
itemDisposables.add(toDisposable(() => {
if (this.isVisible(toast)) {
this.notificationsToastsContainer.removeChild(toast.container);
if (this.isVisible(toast) && notificationsToastsContainer) {
notificationsToastsContainer.removeChild(toast.container);
}
}));
@@ -318,7 +328,7 @@ export class NotificationsToasts extends Themable {
}
hide(): void {
const focusGroup = isAncestor(document.activeElement, this.notificationsToastsContainer);
const focusGroup = this.notificationsToastsContainer ? isAncestor(document.activeElement, this.notificationsToastsContainer) : false;
this.removeToasts();
@@ -412,10 +422,10 @@ export class NotificationsToasts extends Themable {
protected updateStyles(): void {
this.mapNotificationToToast.forEach(t => {
const widgetShadowColor = this.getColor(widgetShadow);
t.toast.style.boxShadow = widgetShadowColor ? `0 0px 8px ${widgetShadowColor}` : null;
t.toast.style.boxShadow = widgetShadowColor ? `0 0px 8px ${widgetShadowColor}` : '';
const borderColor = this.getColor(NOTIFICATIONS_TOAST_BORDER);
t.toast.style.border = borderColor ? `1px solid ${borderColor}` : null;
t.toast.style.border = borderColor ? `1px solid ${borderColor}` : '';
});
}
@@ -443,7 +453,7 @@ export class NotificationsToasts extends Themable {
return notificationToasts.reverse(); // from newest to oldest
}
layout(dimension: Dimension): void {
layout(dimension: Dimension | undefined): void {
this.workbenchDimensions = dimension;
const maxDimensions = this.computeMaxDimensions();
@@ -525,10 +535,11 @@ export class NotificationsToasts extends Themable {
return;
}
const notificationsToastsContainer = assertIsDefined(this.notificationsToastsContainer);
if (visible) {
this.notificationsToastsContainer.appendChild(toast.container);
notificationsToastsContainer.appendChild(toast.container);
} else {
this.notificationsToastsContainer.removeChild(toast.container);
notificationsToastsContainer.removeChild(toast.container);
}
}

View File

@@ -21,7 +21,7 @@ import { ActivePanelContext, PanelPositionContext } from 'vs/workbench/common/pa
export class ClosePanelAction extends Action {
static readonly ID = 'workbench.action.closePanel';
static LABEL = nls.localize('closePanel', "Close Panel");
static readonly LABEL = nls.localize('closePanel', "Close Panel");
constructor(
id: string,
@@ -40,7 +40,7 @@ export class ClosePanelAction extends Action {
export class TogglePanelAction extends Action {
static readonly ID = 'workbench.action.togglePanel';
static LABEL = nls.localize('togglePanel', "Toggle Panel");
static readonly LABEL = nls.localize('togglePanel', "Toggle Panel");
constructor(
id: string,
@@ -209,7 +209,7 @@ export class SwitchPanelViewAction extends Action {
export class PreviousPanelViewAction extends SwitchPanelViewAction {
static readonly ID = 'workbench.action.previousPanelView';
static LABEL = nls.localize('previousPanelView', 'Previous Panel View');
static readonly LABEL = nls.localize('previousPanelView', 'Previous Panel View');
constructor(
id: string,
@@ -227,7 +227,7 @@ export class PreviousPanelViewAction extends SwitchPanelViewAction {
export class NextPanelViewAction extends SwitchPanelViewAction {
static readonly ID = 'workbench.action.nextPanelView';
static LABEL = nls.localize('nextPanelView', 'Next Panel View');
static readonly LABEL = nls.localize('nextPanelView', 'Next Panel View');
constructor(
id: string,

View File

@@ -5,7 +5,7 @@
import 'vs/css!./media/panelpart';
import { IAction } from 'vs/base/common/actions';
import { Event, Emitter } from 'vs/base/common/event';
import { Event } from 'vs/base/common/event';
import { Registry } from 'vs/platform/registry/common/platform';
import { ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar';
import { IPanel, ActivePanelContext, PanelFocusContext } from 'vs/workbench/common/panel';
@@ -30,7 +30,7 @@ import { Dimension, trackFocus } from 'vs/base/browser/dom';
import { localize } from 'vs/nls';
import { IDisposable } from 'vs/base/common/lifecycle';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { isUndefinedOrNull, withUndefinedAsNull, withNullAsUndefined } from 'vs/base/common/types';
import { isUndefinedOrNull, assertIsDefined } from 'vs/base/common/types';
import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
@@ -60,28 +60,25 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
readonly snap = true;
get preferredHeight(): number | undefined {
const sidebarDimension = this.layoutService.getDimension(Parts.SIDEBAR_PART);
return sidebarDimension.height * 0.4;
// Don't worry about titlebar or statusbar visibility
// The difference is minimal and keeps this function clean
return this.layoutService.dimension.height * 0.4;
}
get preferredWidth(): number | undefined {
const statusbarPart = this.layoutService.getDimension(Parts.STATUSBAR_PART);
return statusbarPart.width * 0.4;
return this.layoutService.dimension.width * 0.4;
}
//#endregion
get onDidPanelOpen(): Event<{ panel: IPanel, focus: boolean }> { return Event.map(this.onDidCompositeOpen.event, compositeOpen => ({ panel: compositeOpen.composite, focus: compositeOpen.focus })); }
get onDidPanelOpen(): Event<{ panel: IPanel, focus: boolean; }> { return Event.map(this.onDidCompositeOpen.event, compositeOpen => ({ panel: compositeOpen.composite, focus: compositeOpen.focus })); }
readonly onDidPanelClose: Event<IPanel> = this.onDidCompositeClose.event;
private _onDidVisibilityChange = this._register(new Emitter<boolean>());
readonly onDidVisibilityChange: Event<boolean> = this._onDidVisibilityChange.event;
private activePanelContextKey: IContextKey<string>;
private panelFocusContextKey: IContextKey<boolean>;
private compositeBar: CompositeBar;
private compositeActions: Map<string, { activityAction: PanelActivityAction, pinnedAction: ToggleCompositePinnedAction }> = new Map();
private compositeActions: Map<string, { activityAction: PanelActivityAction, pinnedAction: ToggleCompositePinnedAction; }> = new Map();
private blockOpeningPanel = false;
private _contentDimension: Dimension | undefined;
@@ -123,7 +120,7 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
openComposite: (compositeId: string) => Promise.resolve(this.openPanel(compositeId, true)),
getActivityAction: (compositeId: string) => this.getCompositeActions(compositeId).activityAction,
getCompositePinnedAction: (compositeId: string) => this.getCompositeActions(compositeId).pinnedAction,
getOnCompositeClickAction: (compositeId: string) => this.instantiationService.createInstance(PanelActivityAction, this.getPanel(compositeId)),
getOnCompositeClickAction: (compositeId: string) => this.instantiationService.createInstance(PanelActivityAction, assertIsDefined(this.getPanel(compositeId))),
getContextMenuActions: () => [
this.instantiationService.createInstance(TogglePanelPositionAction, TogglePanelPositionAction.ID, TogglePanelPositionAction.LABEL),
this.instantiationService.createInstance(TogglePanelAction, TogglePanelAction.ID, localize('hidePanel', "Hide Panel"))
@@ -208,19 +205,19 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
updateStyles(): void {
super.updateStyles();
const container = this.getContainer();
container.style.backgroundColor = this.getColor(PANEL_BACKGROUND);
container.style.borderLeftColor = this.getColor(PANEL_BORDER) || this.getColor(contrastBorder);
const container = assertIsDefined(this.getContainer());
container.style.backgroundColor = this.getColor(PANEL_BACKGROUND) || '';
container.style.borderLeftColor = this.getColor(PANEL_BORDER) || this.getColor(contrastBorder) || '';
const title = this.getTitleArea();
if (title) {
title.style.borderTopColor = this.getColor(PANEL_BORDER) || this.getColor(contrastBorder);
title.style.borderTopColor = this.getColor(PANEL_BORDER) || this.getColor(contrastBorder) || '';
}
}
openPanel(id: string, focus?: boolean): Panel | null {
openPanel(id: string, focus?: boolean): Panel | undefined {
if (this.blockOpeningPanel) {
return null; // Workaround against a potential race condition
return undefined; // Workaround against a potential race condition
}
// First check if panel is hidden and show if so
@@ -233,7 +230,7 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
}
}
return withUndefinedAsNull(this.openComposite(id, focus));
return this.openComposite(id, focus);
}
showActivity(panelId: string, badge: IBadge, clazz?: string): IDisposable {
@@ -241,15 +238,15 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
}
getPanel(panelId: string): IPanelIdentifier | undefined {
return withNullAsUndefined(Registry.as<PanelRegistry>(PanelExtensions.Panels).getPanel(panelId));
return Registry.as<PanelRegistry>(PanelExtensions.Panels).getPanel(panelId);
}
getPanels(): PanelDescriptor[] {
getPanels(): readonly PanelDescriptor[] {
return Registry.as<PanelRegistry>(PanelExtensions.Panels).getPanels()
.sort((v1, v2) => typeof v1.order === 'number' && typeof v2.order === 'number' ? v1.order - v2.order : NaN);
}
getPinnedPanels(): PanelDescriptor[] {
getPinnedPanels(): readonly PanelDescriptor[] {
const pinnedCompositeIds = this.compositeBar.getPinnedComposites().map(c => c.id);
return this.getPanels()
.filter(p => pinnedCompositeIds.indexOf(p.id) !== -1)
@@ -263,7 +260,7 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
];
}
getActivePanel(): IPanel | null {
getActivePanel(): IPanel | undefined {
return this.getActiveComposite();
}
@@ -303,9 +300,9 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
}
if (this.layoutService.getPanelPosition() === Position.RIGHT) {
this._contentDimension = new Dimension(width - 1, height!); // Take into account the 1px border when layouting
this._contentDimension = new Dimension(width - 1, height); // Take into account the 1px border when layouting
} else {
this._contentDimension = new Dimension(width, height!);
this._contentDimension = new Dimension(width, height);
}
// Layout contents
@@ -316,7 +313,7 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
}
private layoutCompositeBar(): void {
if (this._contentDimension) {
if (this._contentDimension && this.dimension) {
let availableWidth = this._contentDimension.width - 40; // take padding into account
if (this.toolBar) {
availableWidth = Math.max(PanelPart.MIN_COMPOSITE_BAR_WIDTH, availableWidth - this.getToolbarWidth()); // adjust height for global actions showing
@@ -326,11 +323,11 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
}
}
private getCompositeActions(compositeId: string): { activityAction: PanelActivityAction, pinnedAction: ToggleCompositePinnedAction } {
private getCompositeActions(compositeId: string): { activityAction: PanelActivityAction, pinnedAction: ToggleCompositePinnedAction; } {
let compositeActions = this.compositeActions.get(compositeId);
if (!compositeActions) {
compositeActions = {
activityAction: this.instantiationService.createInstance(PanelActivityAction, this.getPanel(compositeId)),
activityAction: this.instantiationService.createInstance(PanelActivityAction, assertIsDefined(this.getPanel(compositeId))),
pinnedAction: new ToggleCompositePinnedAction(this.getPanel(compositeId), this.compositeBar)
};
@@ -357,7 +354,7 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
private getToolbarWidth(): number {
const activePanel = this.getActivePanel();
if (!activePanel) {
if (!activePanel || !this.toolBar) {
return 0;
}
@@ -446,10 +443,6 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
this.storageService.store(PanelPart.PINNED_PANELS, value, StorageScope.GLOBAL);
}
setVisible(visible: boolean): void {
this._onDidVisibilityChange.fire(visible);
}
toJSON(): object {
return {
type: Parts.PANEL_PART

View File

@@ -305,8 +305,8 @@ class QuickInput extends Disposable implements IQuickInput {
this.ui.inputBox.showDecoration(severity);
if (severity === Severity.Error) {
const styles = this.ui.inputBox.stylesForType(severity);
this.ui.message.style.backgroundColor = styles.background ? `${styles.background}` : null;
this.ui.message.style.border = styles.border ? `1px solid ${styles.border}` : null;
this.ui.message.style.backgroundColor = styles.background ? `${styles.background}` : '';
this.ui.message.style.border = styles.border ? `1px solid ${styles.border}` : '';
this.ui.message.style.paddingBottom = '4px';
} else {
this.ui.message.style.backgroundColor = '';
@@ -323,7 +323,7 @@ class QuickInput extends Disposable implements IQuickInput {
class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPick<T> {
private static INPUT_BOX_ARIA_LABEL = localize('quickInputBox.ariaLabel', "Type to narrow down results.");
private static readonly INPUT_BOX_ARIA_LABEL = localize('quickInputBox.ariaLabel', "Type to narrow down results.");
private _value = '';
private _placeholder: string;
@@ -762,7 +762,7 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
class InputBox extends QuickInput implements IInputBox {
private static noPromptMessage = localize('inputModeEntry', "Press 'Enter' to confirm your input or 'Escape' to cancel");
private static readonly noPromptMessage = localize('inputModeEntry', "Press 'Enter' to confirm your input or 'Escape' to cancel");
private _value = '';
private _valueSelection: Readonly<[number, number]>;
@@ -1511,16 +1511,16 @@ export class QuickInputService extends Component implements IQuickInputService {
if (this.ui) {
// TODO
const titleColor = { dark: 'rgba(255, 255, 255, 0.105)', light: 'rgba(0,0,0,.06)', hc: 'black' }[theme.type];
this.titleBar.style.backgroundColor = titleColor ? titleColor.toString() : null;
this.titleBar.style.backgroundColor = titleColor ? titleColor.toString() : '';
this.ui.inputBox.style(theme);
const quickInputBackground = theme.getColor(QUICK_INPUT_BACKGROUND);
this.ui.container.style.backgroundColor = quickInputBackground ? quickInputBackground.toString() : null;
this.ui.container.style.backgroundColor = quickInputBackground ? quickInputBackground.toString() : '';
const quickInputForeground = theme.getColor(QUICK_INPUT_FOREGROUND);
this.ui.container.style.color = quickInputForeground ? quickInputForeground.toString() : null;
const contrastBorderColor = theme.getColor(contrastBorder);
this.ui.container.style.border = contrastBorderColor ? `1px solid ${contrastBorderColor}` : null;
this.ui.container.style.border = contrastBorderColor ? `1px solid ${contrastBorderColor}` : '';
const widgetShadowColor = theme.getColor(widgetShadow);
this.ui.container.style.boxShadow = widgetShadowColor ? `0 5px 8px ${widgetShadowColor}` : null;
this.ui.container.style.boxShadow = widgetShadowColor ? `0 5px 8px ${widgetShadowColor}` : '';
}
}

View File

@@ -11,7 +11,7 @@ import { WorkbenchList } from 'vs/platform/list/browser/listService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IQuickPickItem, IQuickPickItemButtonEvent, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput';
import { IMatch } from 'vs/base/common/filters';
import { matchesFuzzyOcticonAware, parseOcticons } from 'vs/base/common/octicon';
import { matchesFuzzyCodiconAware, parseCodicons } from 'vs/base/common/codicon';
import { compareAnything } from 'vs/base/common/comparers';
import { Emitter, Event } from 'vs/base/common/event';
import { assign } from 'vs/base/common/objects';
@@ -114,7 +114,7 @@ class ListElementRenderer implements IListRenderer<ListElement, IListElementTemp
const row2 = dom.append(rows, $('.quick-input-list-row'));
// Label
data.label = new IconLabel(row1, { supportHighlights: true, supportDescriptionHighlights: true, supportOcticons: true });
data.label = new IconLabel(row1, { supportHighlights: true, supportDescriptionHighlights: true, supportCodicons: true });
// Detail
const detailContainer = dom.append(row2, $('.quick-input-list-label-meta'));
@@ -152,14 +152,14 @@ class ListElementRenderer implements IListRenderer<ListElement, IListElementTemp
// ARIA label
data.entry.setAttribute('aria-label', [element.saneLabel, element.saneDescription, element.saneDetail]
.map(s => s && parseOcticons(s).text)
.map(s => s && parseCodicons(s).text)
.filter(s => !!s)
.join(', '));
// Separator
if (element.separator && element.separator.label) {
data.separator.textContent = element.separator.label;
data.separator.style.display = null;
data.separator.style.display = '';
} else {
data.separator.style.display = 'none';
}
@@ -490,12 +490,12 @@ export class QuickInputList {
});
}
// Filter by value (since we support octicons, use octicon aware fuzzy matching)
// Filter by value (since we support codicons, use codicon aware fuzzy matching)
else {
this.elements.forEach(element => {
const labelHighlights = this.matchOnLabel ? withNullAsUndefined(matchesFuzzyOcticonAware(query, parseOcticons(element.saneLabel))) : undefined;
const descriptionHighlights = this.matchOnDescription ? withNullAsUndefined(matchesFuzzyOcticonAware(query, parseOcticons(element.saneDescription || ''))) : undefined;
const detailHighlights = this.matchOnDetail ? withNullAsUndefined(matchesFuzzyOcticonAware(query, parseOcticons(element.saneDetail || ''))) : undefined;
const labelHighlights = this.matchOnLabel ? withNullAsUndefined(matchesFuzzyCodiconAware(query, parseCodicons(element.saneLabel))) : undefined;
const descriptionHighlights = this.matchOnDescription ? withNullAsUndefined(matchesFuzzyCodiconAware(query, parseCodicons(element.saneDescription || ''))) : undefined;
const detailHighlights = this.matchOnDetail ? withNullAsUndefined(matchesFuzzyCodiconAware(query, parseCodicons(element.saneDetail || ''))) : undefined;
if (labelHighlights || descriptionHighlights || detailHighlights) {
element.labelHighlights = labelHighlights;

View File

@@ -69,21 +69,21 @@ export class QuickOpenController extends Component implements IQuickOpenService
private readonly _onHide: Emitter<void> = this._register(new Emitter<void>());
readonly onHide: Event<void> = this._onHide.event;
private preserveInput: boolean;
private isQuickOpen: boolean;
private lastInputValue: string;
private lastSubmittedInputValue: string;
private quickOpenWidget: QuickOpenWidget;
private preserveInput: boolean | undefined;
private isQuickOpen: boolean | undefined;
private lastInputValue: string | undefined;
private lastSubmittedInputValue: string | undefined;
private quickOpenWidget: QuickOpenWidget | undefined;
private mapResolvedHandlersToPrefix: Map<string, Promise<QuickOpenHandler>> = new Map();
private mapContextKeyToContext: Map<string, IContextKey<boolean>> = new Map();
private handlerOnOpenCalled: Set<string> = new Set();
private promisesToCompleteOnHide: ValueCallback[] = [];
private previousActiveHandlerDescriptor: QuickOpenHandlerDescriptor | null;
private previousActiveHandlerDescriptor: QuickOpenHandlerDescriptor | null | undefined;
private actionProvider = new ContributableActionProvider();
private closeOnFocusLost: boolean;
private searchInEditorHistory: boolean;
private closeOnFocusLost: boolean | undefined;
private searchInEditorHistory: boolean | undefined;
private editorHistoryHandler: EditorHistoryHandler;
private pendingGetResultsInvocation: CancellationTokenSource | null;
private pendingGetResultsInvocation: CancellationTokenSource | null = null;
constructor(
@IEditorGroupsService private readonly editorGroupService: IEditorGroupsService,
@@ -107,7 +107,7 @@ export class QuickOpenController extends Component implements IQuickOpenService
private registerListeners(): void {
this._register(this.configurationService.onDidChangeConfiguration(() => this.updateConfiguration()));
this._register(this.layoutService.onTitleBarVisibilityChange(() => this.positionQuickOpenWidget()));
this._register(this.layoutService.onPartVisibilityChange(() => this.positionQuickOpenWidget()));
this._register(browser.onDidChangeZoomLevel(() => this.positionQuickOpenWidget()));
this._register(this.layoutService.onLayout(dimension => this.layout(dimension)));
}
@@ -173,12 +173,12 @@ export class QuickOpenController extends Component implements IQuickOpenService
// Create upon first open
if (!this.quickOpenWidget) {
this.quickOpenWidget = this._register(new QuickOpenWidget(
const quickOpenWidget: QuickOpenWidget = this.quickOpenWidget = this._register(new QuickOpenWidget(
this.layoutService.getWorkbenchElement(),
{
onOk: () => this.onOk(),
onCancel: () => { /* ignore */ },
onType: (value: string) => this.onType(value || ''),
onType: (value: string) => this.onType(quickOpenWidget, value || ''),
onShow: () => this.handleOnShow(),
onHide: (reason) => this.handleOnHide(reason),
onFocusLost: () => !this.closeOnFocusLost
@@ -186,8 +186,7 @@ export class QuickOpenController extends Component implements IQuickOpenService
inputPlaceHolder: this.hasHandler(HELP_PREFIX) ? nls.localize('quickOpenInput', "Type '?' to get help on the actions you can take from here") : '',
keyboardSupport: false,
treeCreator: (container, config, opts) => this.instantiationService.createInstance(WorkbenchTree, container, config, opts)
}
));
}));
this._register(attachQuickOpenStyler(this.quickOpenWidget, this.themeService, { background: QUICK_INPUT_BACKGROUND, foreground: QUICK_INPUT_FOREGROUND }));
const quickOpenContainer = this.quickOpenWidget.create();
@@ -307,7 +306,7 @@ export class QuickOpenController extends Component implements IQuickOpenService
}
}
if (key && key.get()) {
if (key?.get()) {
return; // already active context
}
@@ -339,7 +338,7 @@ export class QuickOpenController extends Component implements IQuickOpenService
}
}
private onType(value: string): void {
private onType(quickOpenWidget: QuickOpenWidget, value: string): void {
// cancel any pending get results invocation and create new
this.cancelPendingGetResultsInvocation();
@@ -351,16 +350,16 @@ export class QuickOpenController extends Component implements IQuickOpenService
const registry = Registry.as<IQuickOpenRegistry>(Extensions.Quickopen);
const handlerDescriptor = registry.getQuickOpenHandler(value);
const defaultHandlerDescriptor = registry.getDefaultQuickOpenHandler();
const instantProgress = handlerDescriptor && handlerDescriptor.instantProgress;
const instantProgress = handlerDescriptor?.instantProgress;
const contextKey = handlerDescriptor ? handlerDescriptor.contextKey : defaultHandlerDescriptor.contextKey;
// Reset Progress
if (!instantProgress) {
this.quickOpenWidget.getProgressBar().stop().hide();
quickOpenWidget.getProgressBar().stop().hide();
}
// Reset Extra Class
this.quickOpenWidget.setExtraClass(null);
quickOpenWidget.setExtraClass(null);
// Update context
this.setQuickOpenContextKey(contextKey);
@@ -374,7 +373,7 @@ export class QuickOpenController extends Component implements IQuickOpenService
// Trigger onOpen
this.resolveHandler(handlerDescriptor || defaultHandlerDescriptor);
this.quickOpenWidget.setInput(this.getEditorHistoryWithGroupLabel(), { autoFocusFirstEntry: true });
quickOpenWidget.setInput(this.getEditorHistoryWithGroupLabel(), { autoFocusFirstEntry: true });
// If quickOpen entered empty we have to clear the prefill-cache
this.lastInputValue = '';
@@ -388,7 +387,7 @@ export class QuickOpenController extends Component implements IQuickOpenService
if (handlerDescriptor) {
this.isQuickOpen = false;
resultPromise = this.handleSpecificHandler(handlerDescriptor, value, pendingResultsInvocationToken);
resultPromise = this.handleSpecificHandler(quickOpenWidget, handlerDescriptor, value, pendingResultsInvocationToken);
}
// Otherwise handle default handlers if no specific handler present
@@ -396,7 +395,7 @@ export class QuickOpenController extends Component implements IQuickOpenService
this.isQuickOpen = true;
// Cache the value for prefilling the quickOpen next time is opened
this.lastInputValue = trimmedValue;
resultPromise = this.handleDefaultHandler(defaultHandlerDescriptor, value, pendingResultsInvocationToken);
resultPromise = this.handleDefaultHandler(quickOpenWidget, defaultHandlerDescriptor, value, pendingResultsInvocationToken);
}
// Remember as the active one
@@ -405,7 +404,7 @@ export class QuickOpenController extends Component implements IQuickOpenService
// Progress if task takes a long time
setTimeout(() => {
if (!resultPromiseDone && !pendingResultsInvocationToken.isCancellationRequested) {
this.quickOpenWidget.getProgressBar().infinite().show();
quickOpenWidget.getProgressBar().infinite().show();
}
}, instantProgress ? 0 : 800);
@@ -414,7 +413,7 @@ export class QuickOpenController extends Component implements IQuickOpenService
resultPromiseDone = true;
if (!pendingResultsInvocationToken.isCancellationRequested) {
this.quickOpenWidget.getProgressBar().hide();
quickOpenWidget.getProgressBar().hide();
}
pendingResultsInvocationTokenSource.dispose();
@@ -428,7 +427,7 @@ export class QuickOpenController extends Component implements IQuickOpenService
});
}
private async handleDefaultHandler(handler: QuickOpenHandlerDescriptor, value: string, token: CancellationToken): Promise<void> {
private async handleDefaultHandler(quickOpenWidget: QuickOpenWidget, handler: QuickOpenHandlerDescriptor, value: string, token: CancellationToken): Promise<void> {
// Fill in history results if matching and we are configured to search in history
let matchingHistoryEntries: QuickOpenEntry[];
@@ -451,8 +450,8 @@ export class QuickOpenController extends Component implements IQuickOpenService
// If we have matching entries from history we want to show them directly and not wait for the other results to come in
// This also applies when we used to have entries from a previous run and now there are no more history results matching
const previousInput = this.quickOpenWidget.getInput();
const wasShowingHistory = previousInput && previousInput.entries && previousInput.entries.some(e => e instanceof EditorHistoryEntry || e instanceof EditorHistoryEntryGroup);
const previousInput = quickOpenWidget.getInput();
const wasShowingHistory = previousInput?.entries?.some(e => e instanceof EditorHistoryEntry || e instanceof EditorHistoryEntryGroup);
if (wasShowingHistory || matchingHistoryEntries.length > 0) {
(async () => {
if (resolvedHandler.hasShortResponseTime()) {
@@ -460,7 +459,7 @@ export class QuickOpenController extends Component implements IQuickOpenService
}
if (!token.isCancellationRequested && !inputSet) {
this.quickOpenWidget.setInput(quickOpenModel, { autoFocusFirstEntry: true });
quickOpenWidget.setInput(quickOpenModel, { autoFocusFirstEntry: true });
inputSet = true;
}
})();
@@ -472,17 +471,17 @@ export class QuickOpenController extends Component implements IQuickOpenService
// now is the time to show the input if we did not have set it before
if (!inputSet) {
this.quickOpenWidget.setInput(quickOpenModel, { autoFocusFirstEntry: true });
quickOpenWidget.setInput(quickOpenModel, { autoFocusFirstEntry: true });
inputSet = true;
}
// merge history and default handler results
const handlerResults = (result && result.entries) || [];
this.mergeResults(quickOpenModel, handlerResults, types.withNullAsUndefined(resolvedHandler.getGroupLabel()));
const handlerResults = result?.entries || [];
this.mergeResults(quickOpenWidget, quickOpenModel, handlerResults, types.withNullAsUndefined(resolvedHandler.getGroupLabel()));
}
}
private mergeResults(quickOpenModel: QuickOpenModel, handlerResults: QuickOpenEntry[], groupLabel: string | undefined): void {
private mergeResults(quickOpenWidget: QuickOpenWidget, quickOpenModel: QuickOpenModel, handlerResults: QuickOpenEntry[], groupLabel: string | undefined): void {
// Remove results already showing by checking for a "resource" property
const mapEntryToResource = this.mapEntriesToResource(quickOpenModel);
@@ -501,17 +500,17 @@ export class QuickOpenController extends Component implements IQuickOpenService
const useTopBorder = quickOpenModel.getEntries().length > 0;
additionalHandlerResults[0] = new QuickOpenEntryGroup(additionalHandlerResults[0], groupLabel, useTopBorder);
quickOpenModel.addEntries(additionalHandlerResults);
this.quickOpenWidget.refresh(quickOpenModel, { autoFocusFirstEntry });
quickOpenWidget.refresh(quickOpenModel, { autoFocusFirstEntry });
}
// Otherwise if no results are present (even from histoy) indicate this to the user
else if (quickOpenModel.getEntries().length === 0) {
quickOpenModel.addEntries([new PlaceholderQuickOpenEntry(nls.localize('noResultsFound1', "No results found"))]);
this.quickOpenWidget.refresh(quickOpenModel, { autoFocusFirstEntry: true });
quickOpenWidget.refresh(quickOpenModel, { autoFocusFirstEntry: true });
}
}
private async handleSpecificHandler(handlerDescriptor: QuickOpenHandlerDescriptor, value: string, token: CancellationToken): Promise<void> {
private async handleSpecificHandler(quickOpenWidget: QuickOpenWidget, handlerDescriptor: QuickOpenHandlerDescriptor, value: string, token: CancellationToken): Promise<void> {
const resolvedHandler = await this.resolveHandler(handlerDescriptor);
// Remove handler prefix from search value
@@ -523,7 +522,7 @@ export class QuickOpenController extends Component implements IQuickOpenService
const placeHolderLabel = (typeof canRun === 'string') ? canRun : nls.localize('canNotRunPlaceholder', "This quick open handler can not be used in the current context");
const model = new QuickOpenModel([new PlaceholderQuickOpenEntry(placeHolderLabel)], this.actionProvider);
this.showModel(model, resolvedHandler.getAutoFocus(value, { model, quickNavigateConfiguration: this.quickOpenWidget.getQuickNavigateConfiguration() }), types.withNullAsUndefined(resolvedHandler.getAriaLabel()));
this.showModel(quickOpenWidget, model, resolvedHandler.getAutoFocus(value, { model, quickNavigateConfiguration: quickOpenWidget.getQuickNavigateConfiguration() }), types.withNullAsUndefined(resolvedHandler.getAriaLabel()));
return;
}
@@ -531,12 +530,12 @@ export class QuickOpenController extends Component implements IQuickOpenService
// Support extra class from handler
const extraClass = resolvedHandler.getClass();
if (extraClass) {
this.quickOpenWidget.setExtraClass(extraClass);
quickOpenWidget.setExtraClass(extraClass);
}
// When handlers change, clear the result list first before loading the new results
if (this.previousActiveHandlerDescriptor !== handlerDescriptor) {
this.clearModel();
this.clearModel(quickOpenWidget);
}
// Receive Results from Handler and apply
@@ -544,28 +543,28 @@ export class QuickOpenController extends Component implements IQuickOpenService
if (!token.isCancellationRequested) {
if (!result || !result.entries.length) {
const model = new QuickOpenModel([new PlaceholderQuickOpenEntry(resolvedHandler.getEmptyLabel(value))]);
this.showModel(model, resolvedHandler.getAutoFocus(value, { model, quickNavigateConfiguration: this.quickOpenWidget.getQuickNavigateConfiguration() }), types.withNullAsUndefined(resolvedHandler.getAriaLabel()));
this.showModel(quickOpenWidget, model, resolvedHandler.getAutoFocus(value, { model, quickNavigateConfiguration: quickOpenWidget.getQuickNavigateConfiguration() }), types.withNullAsUndefined(resolvedHandler.getAriaLabel()));
} else {
this.showModel(result, resolvedHandler.getAutoFocus(value, { model: result, quickNavigateConfiguration: this.quickOpenWidget.getQuickNavigateConfiguration() }), types.withNullAsUndefined(resolvedHandler.getAriaLabel()));
this.showModel(quickOpenWidget, result, resolvedHandler.getAutoFocus(value, { model: result, quickNavigateConfiguration: quickOpenWidget.getQuickNavigateConfiguration() }), types.withNullAsUndefined(resolvedHandler.getAriaLabel()));
}
}
}
private showModel(model: IModel<any>, autoFocus?: IAutoFocus, ariaLabel?: string): void {
private showModel(quickOpenWidget: QuickOpenWidget, model: IModel<any>, autoFocus?: IAutoFocus, ariaLabel?: string): void {
// If the given model is already set in the widget, refresh and return early
if (this.quickOpenWidget.getInput() === model) {
this.quickOpenWidget.refresh(model, autoFocus);
if (quickOpenWidget.getInput() === model) {
quickOpenWidget.refresh(model, autoFocus);
return;
}
// Otherwise just set it
this.quickOpenWidget.setInput(model, autoFocus, ariaLabel);
quickOpenWidget.setInput(model, autoFocus, ariaLabel);
}
private clearModel(): void {
this.showModel(new QuickOpenModel(), undefined);
private clearModel(quickOpenWidget: QuickOpenWidget): void {
this.showModel(quickOpenWidget, new QuickOpenModel(), undefined);
}
private mapEntriesToResource(model: QuickOpenModel): { [resource: string]: QuickOpenEntry; } {

View File

@@ -32,6 +32,7 @@ import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { LayoutPriority } from 'vs/base/browser/ui/grid/grid';
import { assertIsDefined } from 'vs/base/common/types';
export class SidebarPart extends CompositePart<Viewlet> implements IViewletService {
@@ -58,7 +59,6 @@ export class SidebarPart extends CompositePart<Viewlet> implements IViewletServi
}
const width = viewlet.getOptimalWidth();
if (typeof width !== 'number') {
return undefined; // {{SQL CARBON EDIT}} strict-null-check
}
@@ -70,9 +70,6 @@ export class SidebarPart extends CompositePart<Viewlet> implements IViewletServi
get onDidViewletRegister(): Event<ViewletDescriptor> { return <Event<ViewletDescriptor>>this.viewletRegistry.onDidRegister; }
private _onDidVisibilityChange = this._register(new Emitter<boolean>());
readonly onDidVisibilityChange: Event<boolean> = this._onDidVisibilityChange.event;
private _onDidViewletDeregister = this._register(new Emitter<ViewletDescriptor>());
readonly onDidViewletDeregister: Event<ViewletDescriptor> = this._onDidViewletDeregister.event;
@@ -173,19 +170,19 @@ export class SidebarPart extends CompositePart<Viewlet> implements IViewletServi
super.updateStyles();
// Part container
const container = this.getContainer();
const container = assertIsDefined(this.getContainer());
container.style.backgroundColor = this.getColor(SIDE_BAR_BACKGROUND);
container.style.backgroundColor = this.getColor(SIDE_BAR_BACKGROUND) || '';
container.style.color = this.getColor(SIDE_BAR_FOREGROUND);
const borderColor = this.getColor(SIDE_BAR_BORDER) || this.getColor(contrastBorder);
const isPositionLeft = this.layoutService.getSideBarPosition() === SideBarPosition.LEFT;
container.style.borderRightWidth = borderColor && isPositionLeft ? '1px' : null;
container.style.borderRightStyle = borderColor && isPositionLeft ? 'solid' : null;
container.style.borderRightColor = isPositionLeft ? borderColor : null;
container.style.borderLeftWidth = borderColor && !isPositionLeft ? '1px' : null;
container.style.borderLeftStyle = borderColor && !isPositionLeft ? 'solid' : null;
container.style.borderLeftColor = !isPositionLeft ? borderColor : null;
container.style.borderRightWidth = borderColor && isPositionLeft ? '1px' : '';
container.style.borderRightStyle = borderColor && isPositionLeft ? 'solid' : '';
container.style.borderRightColor = isPositionLeft ? borderColor || '' : '';
container.style.borderLeftWidth = borderColor && !isPositionLeft ? '1px' : '';
container.style.borderLeftStyle = borderColor && !isPositionLeft ? 'solid' : '';
container.style.borderLeftColor = !isPositionLeft ? borderColor || '' : '';
}
layout(width: number, height: number): void {
@@ -198,7 +195,7 @@ export class SidebarPart extends CompositePart<Viewlet> implements IViewletServi
// Viewlet service
getActiveViewlet(): IViewlet | null {
getActiveViewlet(): IViewlet | undefined {
return <IViewlet>this.getActiveComposite();
}
@@ -210,7 +207,7 @@ export class SidebarPart extends CompositePart<Viewlet> implements IViewletServi
this.hideActiveComposite();
}
async openViewlet(id: string | undefined, focus?: boolean): Promise<IViewlet | null> {
async openViewlet(id: string | undefined, focus?: boolean): Promise<IViewlet | undefined> {
if (typeof id === 'string' && this.getViewlet(id)) {
return this.doOpenViewlet(id, focus);
}
@@ -221,12 +218,21 @@ export class SidebarPart extends CompositePart<Viewlet> implements IViewletServi
return this.doOpenViewlet(id, focus);
}
return null;
return undefined;
}
getViewlets(): ViewletDescriptor[] {
return this.viewletRegistry.getViewlets()
.sort((v1, v2) => v1.order! - v2.order!);
return this.viewletRegistry.getViewlets().sort((v1, v2) => {
if (typeof v1.order !== 'number') {
return -1;
}
if (typeof v2.order !== 'number') {
return 1;
}
return v1.order - v2.order;
});
}
getDefaultViewletId(): string {
@@ -237,9 +243,9 @@ export class SidebarPart extends CompositePart<Viewlet> implements IViewletServi
return this.getViewlets().filter(viewlet => viewlet.id === id)[0];
}
private doOpenViewlet(id: string, focus?: boolean): Viewlet | null {
private doOpenViewlet(id: string, focus?: boolean): Viewlet | undefined {
if (this.blockOpeningViewlet) {
return null; // Workaround against a potential race condition
return undefined; // Workaround against a potential race condition
}
// First check if sidebar is hidden and show if so
@@ -275,10 +281,6 @@ export class SidebarPart extends CompositePart<Viewlet> implements IViewletServi
}
}
setVisible(visible: boolean): void {
this._onDidVisibilityChange.fire(visible);
}
toJSON(): object {
return {
type: Parts.SIDEBAR_PART
@@ -308,7 +310,7 @@ class FocusSideBarAction extends Action {
}
// Focus into active viewlet
let viewlet = this.viewletService.getActiveViewlet();
const viewlet = this.viewletService.getActiveViewlet();
if (viewlet) {
viewlet.focus();
}

View File

@@ -104,7 +104,8 @@
pointer-events: none;
}
.monaco-workbench .part.statusbar > .items-container > .statusbar-item span.octicon {
.monaco-workbench .part.statusbar > .items-container > .statusbar-item span.codicon {
text-align: center;
font-size: 14px;
color: inherit;
}

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 { OcticonLabel } from 'vs/base/browser/ui/octiconLabel/octiconLabel';
import { CodiconLabel } from 'vs/base/browser/ui/codiconLabel/codiconLabel';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { Part } from 'vs/workbench/browser/part';
@@ -27,11 +27,12 @@ import { INotificationService } from 'vs/platform/notification/common/notificati
import { IStorageService, StorageScope, IWorkspaceStorageChangeEvent } from 'vs/platform/storage/common/storage';
import { Parts, IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { coalesce } from 'vs/base/common/arrays';
import { coalesce, find } from 'vs/base/common/arrays';
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
import { ToggleStatusbarVisibilityAction } from 'vs/workbench/browser/actions/layoutActions';
import { Separator } from 'vs/base/browser/ui/actionbar/actionbar';
import { values } from 'vs/base/common/map';
import { assertIsDefined } from 'vs/base/common/types';
interface IPendingStatusbarEntry {
id: string;
@@ -175,13 +176,7 @@ class StatusbarViewModel extends Disposable {
}
findEntry(container: HTMLElement): IStatusbarViewModelEntry | undefined {
for (const entry of this._entries) {
if (entry.container === container) {
return entry;
}
}
return undefined;
return find(this._entries, entry => entry.container === container);
}
getEntries(alignment: StatusbarAlignment): IStatusbarViewModelEntry[] {
@@ -238,7 +233,10 @@ class StatusbarViewModel extends Disposable {
return entryB.priority - entryA.priority; // higher priority towards the left
}
return mapEntryToIndex.get(entryA)! - mapEntryToIndex.get(entryB)!; // otherwise maintain stable order
const indexA = mapEntryToIndex.get(entryA);
const indexB = mapEntryToIndex.get(entryB);
return indexA! - indexB!; // otherwise maintain stable order (both values known to be in map)
}
if (entryA.alignment === StatusbarAlignment.LEFT) {
@@ -310,8 +308,8 @@ class ToggleStatusbarEntryVisibilityAction extends Action {
class HideStatusbarEntryAction extends Action {
constructor(id: string, private model: StatusbarViewModel) {
super(id, nls.localize('hide', "Hide"), undefined, true);
constructor(id: string, name: string, private model: StatusbarViewModel) {
super(id, nls.localize('hide', "Hide '{0}'", name), undefined, true);
}
run(): Promise<any> {
@@ -334,14 +332,14 @@ export class StatusbarPart extends Part implements IStatusbarService {
//#endregion
private styleElement!: HTMLStyleElement;
private styleElement: HTMLStyleElement | undefined;
private pendingEntries: IPendingStatusbarEntry[] = [];
private readonly viewModel: StatusbarViewModel;
private leftItemsContainer!: HTMLElement;
private rightItemsContainer!: HTMLElement;
private leftItemsContainer: HTMLElement | undefined;
private rightItemsContainer: HTMLElement | undefined;
constructor(
@IInstantiationService private readonly instantiationService: IInstantiationService,
@@ -475,7 +473,7 @@ export class StatusbarPart extends Part implements IStatusbarService {
...this.viewModel.getEntries(StatusbarAlignment.LEFT),
...this.viewModel.getEntries(StatusbarAlignment.RIGHT).reverse() // reversing due to flex: row-reverse
].forEach(entry => {
const target = entry.alignment === StatusbarAlignment.LEFT ? this.leftItemsContainer : this.rightItemsContainer;
const target = assertIsDefined(entry.alignment === StatusbarAlignment.LEFT ? this.leftItemsContainer : this.rightItemsContainer);
target.appendChild(entry.container);
});
@@ -488,7 +486,7 @@ export class StatusbarPart extends Part implements IStatusbarService {
entries.reverse(); // reversing due to flex: row-reverse
}
const target = alignment === StatusbarAlignment.LEFT ? this.leftItemsContainer : this.rightItemsContainer;
const target = assertIsDefined(alignment === StatusbarAlignment.LEFT ? this.leftItemsContainer : this.rightItemsContainer);
// find an entry that has lower priority than the new one
// and then insert the item before that one
@@ -562,7 +560,7 @@ export class StatusbarPart extends Part implements IStatusbarService {
if (statusEntryUnderMouse) {
actions.push(new Separator());
actions.push(new HideStatusbarEntryAction(statusEntryUnderMouse.id, this.viewModel));
actions.push(new HideStatusbarEntryAction(statusEntryUnderMouse.id, statusEntryUnderMouse.name, this.viewModel));
}
return actions;
@@ -571,10 +569,10 @@ export class StatusbarPart extends Part implements IStatusbarService {
updateStyles(): void {
super.updateStyles();
const container = this.getContainer();
const container = assertIsDefined(this.getContainer());
// Background colors
const backgroundColor = this.getColor(this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY ? STATUS_BAR_BACKGROUND : STATUS_BAR_NO_FOLDER_BACKGROUND);
const backgroundColor = this.getColor(this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY ? STATUS_BAR_BACKGROUND : STATUS_BAR_NO_FOLDER_BACKGROUND) || '';
container.style.backgroundColor = backgroundColor;
container.style.color = this.getColor(this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY ? STATUS_BAR_FOREGROUND : STATUS_BAR_NO_FOLDER_FOREGROUND);
@@ -630,7 +628,7 @@ class StatusbarEntryItem extends Disposable {
private entry!: IStatusbarEntry;
private labelContainer!: HTMLElement;
private label!: OcticonLabel;
private label!: CodiconLabel;
private readonly foregroundListener = this._register(new MutableDisposable());
private readonly backgroundListener = this._register(new MutableDisposable());
@@ -659,7 +657,7 @@ class StatusbarEntryItem extends Disposable {
this.labelContainer.tabIndex = -1; // allows screen readers to read title, but still prevents tab focus.
// Label
this.label = new OcticonLabel(this.labelContainer);
this.label = new CodiconLabel(this.labelContainer);
// Add to parent
this.container.appendChild(this.labelContainer);
@@ -691,8 +689,9 @@ class StatusbarEntryItem extends Disposable {
if (!this.entry || entry.command !== this.entry.command) {
this.commandListener.clear();
if (entry.command) {
this.commandListener.value = addDisposableListener(this.labelContainer, EventType.CLICK, () => this.executeCommand(entry.command!, entry.arguments));
const command = entry.command;
if (command) {
this.commandListener.value = addDisposableListener(this.labelContainer, EventType.CLICK, () => this.executeCommand(command, entry.arguments));
removeClass(this.labelContainer, 'disabled');
} else {
@@ -779,7 +778,7 @@ class StatusbarEntryItem extends Disposable {
}
if (isBackground) {
container.style.backgroundColor = colorResult;
container.style.backgroundColor = colorResult || '';
} else {
container.style.color = colorResult;
}

View File

@@ -106,54 +106,29 @@
.monaco-workbench.fullscreen .part.titlebar > .window-controls-container {
display: none;
background-color: transparent;
}
.monaco-workbench .part.titlebar > .window-controls-container > .window-icon-bg {
.monaco-workbench .part.titlebar > .window-controls-container > .window-icon {
display: inline-block;
-webkit-app-region: no-drag;
line-height: 30px;
height: 100%;
width: 33.34%;
width: 46px;
font-size: 16px;
}
.monaco-workbench .part.titlebar > .window-controls-container .window-icon svg {
shape-rendering: crispEdges;
text-align: center;
}
.monaco-workbench .part.titlebar.titlebar > .window-controls-container .window-close {
-webkit-mask: url('chrome-close.svg') no-repeat 50% 50%;
}
.monaco-workbench .part.titlebar.titlebar > .window-controls-container .window-unmaximize {
-webkit-mask: url('chrome-restore.svg') no-repeat 50% 50%;
}
.monaco-workbench .part.titlebar > .window-controls-container .window-maximize {
-webkit-mask: url('chrome-maximize.svg') no-repeat 50% 50%;
}
.monaco-workbench .part.titlebar > .window-controls-container .window-minimize {
-webkit-mask: url('chrome-minimize.svg') no-repeat 50% 50%;
}
.monaco-workbench .part.titlebar > .window-controls-container > .window-icon-bg > .window-icon {
height: 100%;
width: 100%;
-webkit-mask-size: 23.1%;
}
.monaco-workbench .part.titlebar > .window-controls-container > .window-icon-bg:hover {
.monaco-workbench .part.titlebar > .window-controls-container > .window-icon:hover {
background-color: rgba(255, 255, 255, 0.1);
}
.monaco-workbench .part.titlebar.light > .window-controls-container > .window-icon-bg:hover {
.monaco-workbench .part.titlebar.light > .window-controls-container > .window-icon:hover {
background-color: rgba(0, 0, 0, 0.1);
}
.monaco-workbench .part.titlebar > .window-controls-container > .window-icon-bg.window-close-bg:hover {
.monaco-workbench .part.titlebar > .window-controls-container > .window-icon.window-close:hover {
background-color: rgba(232, 17, 35, 0.9);
}
.monaco-workbench .part.titlebar > .window-controls-container .window-icon.window-close:hover {
background-color: white;
color: white;
}

View File

@@ -6,7 +6,7 @@
import * as nls from 'vs/nls';
import { IMenuService, MenuId, IMenu, SubmenuItemAction } from 'vs/platform/actions/common/actions';
import { registerThemingParticipant, ITheme, ICssStyleCollector, IThemeService } from 'vs/platform/theme/common/themeService';
import { MenuBarVisibility, getTitleBarStyle, IWindowOpenable } from 'vs/platform/windows/common/windows';
import { MenuBarVisibility, getTitleBarStyle, IWindowOpenable, getMenuBarVisibility } from 'vs/platform/windows/common/windows';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IAction, Action } from 'vs/base/common/actions';
import { Separator } from 'vs/base/browser/ui/actionbar/actionbar';
@@ -77,11 +77,11 @@ export abstract class MenubarControl extends Disposable {
'Help': nls.localize({ key: 'mHelp', comment: ['&& denotes a mnemonic'] }, "&&Help")
};
protected recentlyOpened: IRecentlyOpened;
protected recentlyOpened: IRecentlyOpened = { files: [], workspaces: [] };
protected menuUpdater: RunOnceScheduler;
protected static MAX_MENU_RECENT_ENTRIES = 10;
protected static readonly MAX_MENU_RECENT_ENTRIES = 10;
constructor(
protected readonly menuService: IMenuService,
@@ -121,6 +121,9 @@ export abstract class MenubarControl extends Disposable {
protected abstract doUpdateMenubar(firstTime: boolean): void;
protected registerListeners(): void {
// Listen for window focus changes
this._register(this.hostService.onDidChangeFocus(e => this.onDidChangeWindowFocus(e)));
// Update when config changes
this._register(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationUpdated(e)));
@@ -179,6 +182,13 @@ export abstract class MenubarControl extends Disposable {
return result;
}
protected onDidChangeWindowFocus(hasFocus: boolean): void {
// When we regain focus, update the recent menu items
if (hasFocus) {
this.onRecentlyOpenedChange();
}
}
private onConfigurationUpdated(event: IConfigurationChangeEvent): void {
if (this.keys.some(key => event.affectsConfiguration(key))) {
this.updateMenubar();
@@ -260,10 +270,10 @@ export abstract class MenubarControl extends Disposable {
}
export class CustomMenubarControl extends MenubarControl {
private menubar: MenuBar;
private container: HTMLElement;
private alwaysOnMnemonics: boolean;
private focusInsideMenubar: boolean;
private menubar: MenuBar | undefined;
private container: HTMLElement | undefined;
private alwaysOnMnemonics: boolean = false;
private focusInsideMenubar: boolean = false;
private readonly _onVisibilityChange: Emitter<boolean>;
private readonly _onFocusStateChange: Emitter<boolean>;
@@ -435,9 +445,9 @@ export class CustomMenubarControl extends MenubarControl {
return null;
case StateType.Idle:
const windowId = this.electronEnvironmentService.windowId;
const context = `window:${this.electronEnvironmentService ? this.electronEnvironmentService.windowId : 'any'}`;
return new Action('update.check', nls.localize({ key: 'checkForUpdates', comment: ['&& denotes a mnemonic'] }, "Check for &&Updates..."), undefined, true, () =>
this.updateService.checkForUpdates({ windowId }));
this.updateService.checkForUpdates(context));
case StateType.CheckingForUpdates:
return new Action('update.checking', nls.localize('checkingForUpdates', "Checking for Updates..."), undefined, false);
@@ -463,7 +473,7 @@ export class CustomMenubarControl extends MenubarControl {
}
private get currentMenubarVisibility(): MenuBarVisibility {
return this.configurationService.getValue<MenuBarVisibility>('window.menuBarVisibility');
return getMenuBarVisibility(this.configurationService, this.environmentService);
}
private get currentDisableMenuBarAltFocus(): boolean {
@@ -519,6 +529,11 @@ export class CustomMenubarControl extends MenubarControl {
}
private setupCustomMenubar(firstTime: boolean): void {
// If there is no container, we cannot setup the menubar
if (!this.container) {
return;
}
if (firstTime) {
this.menubar = this._register(new MenuBar(
this.container, {
@@ -527,12 +542,13 @@ export class CustomMenubarControl extends MenubarControl {
visibility: this.currentMenubarVisibility,
getKeybinding: (action) => this.keybindingService.lookupKeybinding(action.id),
compactMode: this.currentCompactMenuMode
}
));
}));
this.accessibilityService.alwaysUnderlineAccessKeys().then(val => {
this.alwaysOnMnemonics = val;
this.menubar.update({ enableMnemonics: this.currentEnableMenuBarMnemonics, disableAltFocus: this.currentDisableMenuBarAltFocus, visibility: this.currentMenubarVisibility, getKeybinding: (action) => this.keybindingService.lookupKeybinding(action.id), alwaysOnMnemonics: this.alwaysOnMnemonics, compactMode: this.currentCompactMenuMode });
if (this.menubar) {
this.menubar.update({ enableMnemonics: this.currentEnableMenuBarMnemonics, disableAltFocus: this.currentDisableMenuBarAltFocus, visibility: this.currentMenubarVisibility, getKeybinding: (action) => this.keybindingService.lookupKeybinding(action.id), alwaysOnMnemonics: this.alwaysOnMnemonics, compactMode: this.currentCompactMenuMode });
}
});
this._register(this.menubar.onFocusStateChange(focused => {
@@ -558,7 +574,9 @@ export class CustomMenubarControl extends MenubarControl {
this._register(attachMenuStyler(this.menubar, this.themeService));
} else {
this.menubar.update({ enableMnemonics: this.currentEnableMenuBarMnemonics, disableAltFocus: this.currentDisableMenuBarAltFocus, visibility: this.currentMenubarVisibility, getKeybinding: (action) => this.keybindingService.lookupKeybinding(action.id), alwaysOnMnemonics: this.alwaysOnMnemonics, compactMode: this.currentCompactMenuMode });
if (this.menubar) {
this.menubar.update({ enableMnemonics: this.currentEnableMenuBarMnemonics, disableAltFocus: this.currentDisableMenuBarAltFocus, visibility: this.currentMenubarVisibility, getKeybinding: (action) => this.keybindingService.lookupKeybinding(action.id), alwaysOnMnemonics: this.alwaysOnMnemonics, compactMode: this.currentCompactMenuMode });
}
}
// Update the menu actions
@@ -571,19 +589,20 @@ export class CustomMenubarControl extends MenubarControl {
for (let action of actions) {
this.insertActionsBefore(action, target);
if (action instanceof SubmenuItemAction) {
if (!this.menus[action.item.submenu]) {
this.menus[action.item.submenu] = this.menuService.createMenu(action.item.submenu, this.contextKeyService);
const submenu = this.menus[action.item.submenu];
this._register(submenu!.onDidChange(() => {
let submenu = this.menus[action.item.submenu];
if (!submenu) {
submenu = this.menus[action.item.submenu] = this.menuService.createMenu(action.item.submenu, this.contextKeyService);
this._register(submenu.onDidChange(() => {
if (!this.focusInsideMenubar) {
const actions: IAction[] = [];
updateActions(menu, actions, topLevelTitle);
this.menubar.updateMenu({ actions: actions, label: mnemonicMenuLabel(this.topLevelTitles[topLevelTitle]) });
if (this.menubar) {
this.menubar.updateMenu({ actions: actions, label: mnemonicMenuLabel(this.topLevelTitles[topLevelTitle]) });
}
}
}, this));
}
const submenu = this.menus[action.item.submenu]!;
const submenuActions: SubmenuAction[] = [];
updateActions(submenu, submenuActions, topLevelTitle);
target.push(new SubmenuAction(mnemonicMenuLabel(action.label), submenuActions));
@@ -606,7 +625,9 @@ export class CustomMenubarControl extends MenubarControl {
if (!this.focusInsideMenubar) {
const actions: IAction[] = [];
updateActions(menu, actions, title);
this.menubar.updateMenu({ actions: actions, label: mnemonicMenuLabel(this.topLevelTitles[title]) });
if (this.menubar) {
this.menubar.updateMenu({ actions: actions, label: mnemonicMenuLabel(this.topLevelTitles[title]) });
}
}
}));
}
@@ -616,21 +637,27 @@ export class CustomMenubarControl extends MenubarControl {
updateActions(menu, actions, title);
}
if (!firstTime) {
this.menubar.updateMenu({ actions: actions, label: mnemonicMenuLabel(this.topLevelTitles[title]) });
} else {
this.menubar.push({ actions: actions, label: mnemonicMenuLabel(this.topLevelTitles[title]) });
if (this.menubar) {
if (!firstTime) {
this.menubar.updateMenu({ actions: actions, label: mnemonicMenuLabel(this.topLevelTitles[title]) });
} else {
this.menubar.push({ actions: actions, label: mnemonicMenuLabel(this.topLevelTitles[title]) });
}
}
}
}
private onDidChangeWindowFocus(hasFocus: boolean): void {
protected onDidChangeWindowFocus(hasFocus: boolean): void {
super.onDidChangeWindowFocus(hasFocus);
if (this.container) {
if (hasFocus) {
DOM.removeClass(this.container, 'inactive');
} else {
DOM.addClass(this.container, 'inactive');
this.menubar.blur();
if (this.menubar) {
this.menubar.blur();
}
}
}
}
@@ -638,9 +665,6 @@ export class CustomMenubarControl extends MenubarControl {
protected registerListeners(): void {
super.registerListeners();
// Listen for window focus changes
this._register(this.hostService.onDidChangeFocus(e => this.onDidChangeWindowFocus(e)));
// Listen for maximize/unmaximize
if (!isWeb) {
this._register(Event.any(
@@ -650,7 +674,9 @@ export class CustomMenubarControl extends MenubarControl {
}
this._register(DOM.addDisposableListener(window, DOM.EventType.RESIZE, () => {
this.menubar.blur();
if (this.menubar) {
this.menubar.blur();
}
}));
// Mnemonics require fullscreen in web

View File

@@ -8,7 +8,7 @@ import * as resources from 'vs/base/common/resources';
import { Part } from 'vs/workbench/browser/part';
import { ITitleService, ITitleProperties } from 'vs/workbench/services/title/common/titleService';
import { getZoomFactor } from 'vs/base/browser/browser';
import { MenuBarVisibility, getTitleBarStyle } from 'vs/platform/windows/common/windows';
import { MenuBarVisibility, getTitleBarStyle, getMenuBarVisibility } from 'vs/platform/windows/common/windows';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
import { IAction } from 'vs/base/common/actions';
@@ -18,7 +18,7 @@ import { DisposableStore, dispose } from 'vs/base/common/lifecycle';
import * as nls from 'vs/nls';
import { EditorInput, toResource, Verbosity, SideBySideEditor } from 'vs/workbench/common/editor';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { IWorkspaceContextService, WorkbenchState, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } 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 } from 'vs/workbench/common/theme';
import { isMacintosh, isWindows, isLinux, isWeb } from 'vs/base/common/platform';
@@ -68,19 +68,20 @@ export class TitlebarPart extends Part implements ITitleService {
_serviceBrand: undefined;
private title: HTMLElement;
private dragRegion: HTMLElement;
private windowControls: HTMLElement;
private maxRestoreControl: HTMLElement;
private appIcon: HTMLElement;
private title!: HTMLElement;
private dragRegion: HTMLElement | undefined;
private windowControls: HTMLElement | undefined;
private maxRestoreControl: HTMLElement | undefined;
private appIcon: HTMLElement | undefined;
private customMenubar: CustomMenubarControl | undefined;
private menubar?: HTMLElement;
private resizer: HTMLElement;
private lastLayoutDimensions: Dimension;
private resizer: HTMLElement | undefined;
private lastLayoutDimensions: Dimension | undefined;
private titleBarStyle: 'native' | 'custom';
private pendingTitle: string;
private pendingTitle: string | undefined;
private isInactive: boolean;
private isInactive: boolean = false;
private readonly properties: ITitleProperties = { isPure: true, isAdmin: false };
private readonly activeEditorListeners = this._register(new DisposableStore());
@@ -110,6 +111,8 @@ export class TitlebarPart extends Part implements ITitleService {
this.contextMenu = this._register(menuService.createMenu(MenuId.TitleBarContext, contextKeyService));
this.titleBarStyle = getTitleBarStyle(this.configurationService, this.environmentService);
this.registerListeners();
}
@@ -138,11 +141,13 @@ export class TitlebarPart extends Part implements ITitleService {
this.titleUpdater.schedule();
}
if (event.affectsConfiguration('window.menuBarVisibility')) {
if (this.currentMenubarVisibility === 'compact') {
this.uninstallMenubar();
} else {
this.installMenubar();
if (this.titleBarStyle !== 'native') {
if (event.affectsConfiguration('window.menuBarVisibility')) {
if (this.currentMenubarVisibility === 'compact') {
this.uninstallMenubar();
} else {
this.installMenubar();
}
}
}
@@ -158,8 +163,10 @@ export class TitlebarPart extends Part implements ITitleService {
// Hide title when toggling menu bar
if (!isWeb && this.currentMenubarVisibility === 'toggle' && visible) {
// Hack to fix issue #52522 with layered webkit-app-region elements appearing under cursor
hide(this.dragRegion);
setTimeout(() => show(this.dragRegion), 50);
if (this.dragRegion) {
hide(this.dragRegion);
setTimeout(() => show(this.dragRegion!), 50);
}
}
this.adjustTitleMarginToCenter();
@@ -169,7 +176,7 @@ export class TitlebarPart extends Part implements ITitleService {
}
private onMenubarFocusChanged(focused: boolean) {
if (!isWeb && (isWindows || isLinux) && this.currentMenubarVisibility === 'compact') {
if (!isWeb && (isWindows || isLinux) && this.currentMenubarVisibility === 'compact' && this.dragRegion) {
if (focused) {
hide(this.dragRegion);
} else {
@@ -281,14 +288,22 @@ export class TitlebarPart extends Part implements ITitleService {
// Compute active editor folder
const editorResource = editor ? toResource(editor) : undefined;
let editorFolderResource = editorResource ? resources.dirname(editorResource) : undefined;
if (editorFolderResource && editorFolderResource.path === '.') {
if (editorFolderResource?.path === '.') {
editorFolderResource = undefined;
}
// Compute folder resource
// Single Root Workspace: always the root single workspace in this case
// Otherwise: root folder of the currently active file if any
const folder = this.contextService.getWorkbenchState() === WorkbenchState.FOLDER ? workspace.folders[0] : this.contextService.getWorkspaceFolder(toResource(editor, { supportSideBySide: SideBySideEditor.MASTER })!);
let folder: IWorkspaceFolder | null = null;
if (this.contextService.getWorkbenchState() === WorkbenchState.FOLDER) {
folder = workspace.folders[0];
} else {
const resource = toResource(editor, { supportSideBySide: SideBySideEditor.MASTER });
if (resource) {
folder = this.contextService.getWorkspaceFolder(resource);
}
}
// Variables
const activeEditorShort = editor ? editor.getTitle(Verbosity.SHORT) : '';
@@ -301,7 +316,7 @@ export class TitlebarPart extends Part implements ITitleService {
const rootPath = root ? this.labelService.getUriLabel(root) : '';
const folderName = folder ? folder.name : '';
const folderPath = folder ? this.labelService.getUriLabel(folder.uri) : '';
const dirty = editor && editor.isDirty() ? TitlebarPart.TITLE_DIRTY : '';
const dirty = editor?.isDirty() ? TitlebarPart.TITLE_DIRTY : '';
const appName = this.environmentService.appNameLong;
const remoteName = this.environmentService.configuration.remoteAuthority;
const separator = TitlebarPart.TITLE_SEPARATOR;
@@ -338,6 +353,11 @@ export class TitlebarPart extends Part implements ITitleService {
}
private installMenubar(): void {
// If the menubar is already installed, skip
if (this.menubar) {
return;
}
this.customMenubar = this._register(this.instantiationService.createInstance(CustomMenubarControl));
this.menubar = this.element.insertBefore($('div.menubar'), this.title);
@@ -370,7 +390,7 @@ export class TitlebarPart extends Part implements ITitleService {
// Menubar: install a custom menu bar depending on configuration
// and when not in activity bar
if (getTitleBarStyle(this.configurationService, this.environmentService) !== 'native'
if (this.titleBarStyle !== 'native'
&& (!isMacintosh || isWeb)
&& this.currentMenubarVisibility !== 'compact') {
this.installMenubar();
@@ -400,17 +420,13 @@ export class TitlebarPart extends Part implements ITitleService {
this.windowControls = append(this.element, $('div.window-controls-container'));
// Minimize
const minimizeIconContainer = append(this.windowControls, $('div.window-icon-bg'));
const minimizeIcon = append(minimizeIconContainer, $('div.window-icon'));
addClass(minimizeIcon, 'window-minimize');
const minimizeIcon = append(this.windowControls, $('div.window-icon.window-minimize.codicon.codicon-chrome-minimize'));
this._register(addDisposableListener(minimizeIcon, EventType.CLICK, e => {
this.electronService.minimizeWindow();
}));
// Restore
const restoreIconContainer = append(this.windowControls, $('div.window-icon-bg'));
this.maxRestoreControl = append(restoreIconContainer, $('div.window-icon'));
addClass(this.maxRestoreControl, 'window-max-restore');
this.maxRestoreControl = append(this.windowControls, $('div.window-icon.window-max-restore.codicon'));
this._register(addDisposableListener(this.maxRestoreControl, EventType.CLICK, async e => {
const maximized = await this.electronService.isMaximized();
if (maximized) {
@@ -421,10 +437,7 @@ export class TitlebarPart extends Part implements ITitleService {
}));
// Close
const closeIconContainer = append(this.windowControls, $('div.window-icon-bg'));
addClass(closeIconContainer, 'window-close-bg');
const closeIcon = append(closeIconContainer, $('div.window-icon'));
addClass(closeIcon, 'window-close');
const closeIcon = append(this.windowControls, $('div.window-icon.window-close.codicon.codicon-chrome-close'));
this._register(addDisposableListener(closeIcon, EventType.CLICK, e => {
this.electronService.closeWindow();
}));
@@ -464,11 +477,11 @@ export class TitlebarPart extends Part implements ITitleService {
private onDidChangeMaximized(maximized: boolean) {
if (this.maxRestoreControl) {
if (maximized) {
removeClass(this.maxRestoreControl, 'window-maximize');
addClass(this.maxRestoreControl, 'window-unmaximize');
removeClass(this.maxRestoreControl, 'codicon-chrome-maximize');
addClass(this.maxRestoreControl, 'codicon-chrome-restore');
} else {
removeClass(this.maxRestoreControl, 'window-unmaximize');
addClass(this.maxRestoreControl, 'window-maximize');
removeClass(this.maxRestoreControl, 'codicon-chrome-restore');
addClass(this.maxRestoreControl, 'codicon-chrome-maximize');
}
}
@@ -494,7 +507,7 @@ export class TitlebarPart extends Part implements ITitleService {
removeClass(this.element, 'inactive');
}
const titleBackground = this.getColor(this.isInactive ? TITLE_BAR_INACTIVE_BACKGROUND : TITLE_BAR_ACTIVE_BACKGROUND);
const titleBackground = this.getColor(this.isInactive ? TITLE_BAR_INACTIVE_BACKGROUND : TITLE_BAR_ACTIVE_BACKGROUND) || '';
this.element.style.backgroundColor = titleBackground;
if (titleBackground && Color.fromHex(titleBackground).isLighter()) {
addClass(this.element, 'light');
@@ -506,15 +519,15 @@ export class TitlebarPart extends Part implements ITitleService {
this.element.style.color = titleForeground;
const titleBorder = this.getColor(TITLE_BAR_BORDER);
this.element.style.borderBottom = titleBorder ? `1px solid ${titleBorder}` : null;
this.element.style.borderBottom = titleBorder ? `1px solid ${titleBorder}` : '';
}
}
private onUpdateAppIconDragBehavior() {
const setting = this.configurationService.getValue('window.doubleClickIconToClose');
if (setting) {
if (setting && this.appIcon) {
(this.appIcon.style as any)['-webkit-app-region'] = 'no-drag';
} else {
} else if (this.appIcon) {
(this.appIcon.style as any)['-webkit-app-region'] = 'drag';
}
}
@@ -546,8 +559,8 @@ export class TitlebarPart extends Part implements ITitleService {
// Center between menu and window controls
if (leftMarker > (this.element.clientWidth - this.title.clientWidth) / 2 ||
rightMarker < (this.element.clientWidth + this.title.clientWidth) / 2) {
this.title.style.position = null;
this.title.style.left = null;
this.title.style.position = '';
this.title.style.left = '';
this.title.style.transform = '';
return;
}
@@ -559,7 +572,7 @@ export class TitlebarPart extends Part implements ITitleService {
}
private get currentMenubarVisibility(): MenuBarVisibility {
return this.configurationService.getValue<MenuBarVisibility>('window.menuBarVisibility');
return getMenuBarVisibility(this.configurationService, this.environmentService);
}
updateLayout(dimension: Dimension): void {
@@ -570,14 +583,24 @@ export class TitlebarPart extends Part implements ITitleService {
if ((!isWeb && isMacintosh) || this.currentMenubarVisibility === 'hidden') {
this.title.style.zoom = `${1 / getZoomFactor()}`;
if (!isWeb && (isWindows || isLinux)) {
this.appIcon.style.zoom = `${1 / getZoomFactor()}`;
this.windowControls.style.zoom = `${1 / getZoomFactor()}`;
if (this.appIcon) {
this.appIcon.style.zoom = `${1 / getZoomFactor()}`;
}
if (this.windowControls) {
this.windowControls.style.zoom = `${1 / getZoomFactor()}`;
}
}
} else {
this.title.style.zoom = null;
if (!isWeb && (isWindows || isLinux)) {
this.appIcon.style.zoom = null;
this.windowControls.style.zoom = null;
if (this.appIcon) {
this.appIcon.style.zoom = null;
}
if (this.windowControls) {
this.windowControls.style.zoom = null;
}
}
}
@@ -608,7 +631,7 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
if (titlebarActiveFg) {
collector.addRule(`
.monaco-workbench .part.titlebar > .window-controls-container .window-icon {
background-color: ${titlebarActiveFg};
color: ${titlebarActiveFg};
}
`);
}
@@ -617,7 +640,7 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
if (titlebarInactiveFg) {
collector.addRule(`
.monaco-workbench .part.titlebar.inactive > .window-controls-container .window-icon {
background-color: ${titlebarInactiveFg};
color: ${titlebarInactiveFg};
}
`);
}

View File

@@ -32,7 +32,7 @@ import { WorkbenchAsyncDataTree, TreeResourceNavigator2 } from 'vs/platform/list
import { ViewletPanel, IViewletPanelOptions } from 'vs/workbench/browser/parts/views/panelViewlet';
import { localize } from 'vs/nls';
import { timeout } from 'vs/base/common/async';
import { editorFindMatchHighlight, editorFindMatchHighlightBorder, textLinkForeground, textCodeBlockBackground, focusBorder } from 'vs/platform/theme/common/colorRegistry';
import { textLinkForeground, textCodeBlockBackground, focusBorder, listFilterMatchHighlight, listFilterMatchHighlightBorder } from 'vs/platform/theme/common/colorRegistry';
import { isString } from 'vs/base/common/types';
import { ILabelService } from 'vs/platform/label/common/label';
import { Registry } from 'vs/platform/registry/common/platform';
@@ -673,15 +673,15 @@ class TreeDataSource implements IAsyncDataSource<ITreeItem, ITreeItem> {
// todo@joh,sandy make this proper and contributable from extensions
registerThemingParticipant((theme, collector) => {
const findMatchHighlightColor = theme.getColor(editorFindMatchHighlight);
if (findMatchHighlightColor) {
collector.addRule(`.file-icon-themable-tree .monaco-list-row .content .monaco-highlighted-label .highlight { color: unset !important; background-color: ${findMatchHighlightColor}; }`);
collector.addRule(`.monaco-tl-contents .monaco-highlighted-label .highlight { color: unset !important; background-color: ${findMatchHighlightColor}; }`);
const matchBackgroundColor = theme.getColor(listFilterMatchHighlight);
if (matchBackgroundColor) {
collector.addRule(`.file-icon-themable-tree .monaco-list-row .content .monaco-highlighted-label .highlight { color: unset !important; background-color: ${matchBackgroundColor}; }`);
collector.addRule(`.monaco-tl-contents .monaco-highlighted-label .highlight { color: unset !important; background-color: ${matchBackgroundColor}; }`);
}
const findMatchHighlightColorBorder = theme.getColor(editorFindMatchHighlightBorder);
if (findMatchHighlightColorBorder) {
collector.addRule(`.file-icon-themable-tree .monaco-list-row .content .monaco-highlighted-label .highlight { color: unset !important; border: 1px dotted ${findMatchHighlightColorBorder}; box-sizing: border-box; }`);
collector.addRule(`.monaco-tl-contents .monaco-highlighted-label .highlight { color: unset !important; border: 1px dotted ${findMatchHighlightColorBorder}; box-sizing: border-box; }`);
const matchBorderColor = theme.getColor(listFilterMatchHighlightBorder);
if (matchBorderColor) {
collector.addRule(`.file-icon-themable-tree .monaco-list-row .content .monaco-highlighted-label .highlight { color: unset !important; border: 1px dotted ${matchBorderColor}; box-sizing: border-box; }`);
collector.addRule(`.monaco-tl-contents .monaco-highlighted-label .highlight { color: unset !important; border: 1px dotted ${matchBorderColor}; box-sizing: border-box; }`);
}
const link = theme.getColor(textLinkForeground);
if (link) {
@@ -753,6 +753,23 @@ class TreeRenderer extends Disposable implements ITreeRenderer<ITreeItem, FuzzyS
const treeItemLabel: ITreeItemLabel | undefined = node.label ? node.label : resource ? { label: basename(resource) } : undefined;
const description = isString(node.description) ? node.description : resource && node.description === true ? this.labelService.getUriLabel(dirname(resource), { relative: true }) : undefined;
const label = treeItemLabel ? treeItemLabel.label : undefined;
const matches = (treeItemLabel && treeItemLabel.highlights && label) ? treeItemLabel.highlights.map(([start, end]) => {
if ((Math.abs(start) > label.length) || (Math.abs(end) >= label.length)) {
return ({ start: 0, end: 0 });
}
if (start < 0) {
start = label.length + start;
}
if (end < 0) {
end = label.length + end;
}
if (start > end) {
const swap = start;
start = end;
end = swap;
}
return ({ start, end });
}) : undefined;
const icon = this.themeService.getTheme().type === LIGHT ? node.icon : node.iconDark;
const iconUrl = icon ? URI.revive(icon) : null;
const title = node.tooltip ? node.tooltip : resource ? undefined : label;
@@ -762,9 +779,9 @@ class TreeRenderer extends Disposable implements ITreeRenderer<ITreeItem, FuzzyS
if (resource || node.themeIcon) {
const fileDecorations = this.configurationService.getValue<{ colors: boolean, badges: boolean }>('explorer.decorations');
templateData.resourceLabel.setResource({ name: label, description, resource: resource ? resource : URI.parse('missing:_icon_resource') }, { fileKind: this.getFileKind(node), title, hideIcon: !!iconUrl, fileDecorations, extraClasses: ['custom-view-tree-node-item-resourceLabel'], matches: createMatches(element.filterData) });
templateData.resourceLabel.setResource({ name: label, description, resource: resource ? resource : URI.parse('missing:_icon_resource') }, { fileKind: this.getFileKind(node), title, hideIcon: !!iconUrl, fileDecorations, extraClasses: ['custom-view-tree-node-item-resourceLabel'], matches: matches ? matches : createMatches(element.filterData) });
} else {
templateData.resourceLabel.setResource({ name: label, description }, { title, hideIcon: true, extraClasses: ['custom-view-tree-node-item-resourceLabel'], matches: createMatches(element.filterData) });
templateData.resourceLabel.setResource({ name: label, description }, { title, hideIcon: true, extraClasses: ['custom-view-tree-node-item-resourceLabel'], matches: matches ? matches : createMatches(element.filterData) });
}
templateData.icon.style.backgroundImage = iconUrl ? DOM.asCSSUrl(iconUrl) : '';

View File

@@ -29,6 +29,7 @@ import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
import { IView, FocusedViewContext } from 'vs/workbench/common/views';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { assertIsDefined } from 'vs/base/common/types';
export interface IPanelColors extends IColorMapping {
dropBackground?: ColorIdentifier;
@@ -46,7 +47,7 @@ export interface IViewletPanelOptions extends IPanelOptions {
export abstract class ViewletPanel extends Panel implements IView {
private static AlwaysShowActionsConfig = 'workbench.view.alwaysShowHeaderActions';
private static readonly AlwaysShowActionsConfig = 'workbench.view.alwaysShowHeaderActions';
private _onDidFocus = this._register(new Emitter<void>());
readonly onDidFocus: Event<void> = this._onDidFocus.event;
@@ -67,11 +68,11 @@ export abstract class ViewletPanel extends Panel implements IView {
readonly title: string;
protected actionRunner?: IActionRunner;
protected toolbar: ToolBar;
protected toolbar?: ToolBar;
private readonly showActionsAlways: boolean = false;
private headerContainer: HTMLElement;
private titleContainer: HTMLElement;
protected twistiesContainer: HTMLElement;
private headerContainer?: HTMLElement;
private titleContainer?: HTMLElement;
protected twistiesContainer?: HTMLElement;
constructor(
options: IViewletPanelOptions,
@@ -165,7 +166,9 @@ export abstract class ViewletPanel extends Panel implements IView {
}
protected updateTitle(title: string): void {
this.titleContainer.textContent = title;
if (this.titleContainer) {
this.titleContainer.textContent = title;
}
this._onDidChangeTitleArea.fire();
}
@@ -177,11 +180,16 @@ export abstract class ViewletPanel extends Panel implements IView {
}
private setActions(): void {
this.toolbar.setActions(prepareActions(this.getActions()), prepareActions(this.getSecondaryActions()))();
this.toolbar.context = this.getActionsContext();
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');
toggleClass(this.headerContainer, 'actions-always-visible', shouldAlwaysShowActions);
}
@@ -229,10 +237,10 @@ export class PanelViewlet extends Viewlet {
private lastFocusedPanel: ViewletPanel | undefined;
private panelItems: IViewletPanelItem[] = [];
private panelview: PanelView;
private panelview?: PanelView;
get onDidSashChange(): Event<number> {
return this.panelview.onDidSashChange;
return assertIsDefined(this.panelview).onDidSashChange;
}
protected get panels(): ViewletPanel[] {
@@ -274,7 +282,7 @@ export class PanelViewlet extends Viewlet {
event.stopPropagation();
event.preventDefault();
let anchor: { x: number, y: number } = { x: event.posx, y: event.posy };
let anchor: { x: number, y: number; } = { x: event.posx, y: event.posy };
this.contextMenuService.showContextMenu({
getAnchor: () => anchor,
getActions: () => this.getContextMenuActions()
@@ -332,7 +340,9 @@ export class PanelViewlet extends Viewlet {
}
layout(dimension: Dimension): void {
this.panelview.layout(dimension.height, dimension.width);
if (this.panelview) {
this.panelview.layout(dimension.height, dimension.width);
}
}
getOptimalWidth(): number {
@@ -342,7 +352,7 @@ export class PanelViewlet extends Viewlet {
return Math.max(...sizes);
}
addPanels(panels: { panel: ViewletPanel, size: number, index?: number }[]): void {
addPanels(panels: { panel: ViewletPanel, size: number, index?: number; }[]): void {
const wasSingleView = this.isSingleView();
for (const { panel, size, index } of panels) {
@@ -378,7 +388,7 @@ export class PanelViewlet extends Viewlet {
const panelItem: IViewletPanelItem = { panel, disposable };
this.panelItems.splice(index, 0, panelItem);
this.panelview.addPanel(panel, size, index);
assertIsDefined(this.panelview).addPanel(panel, size, index);
}
removePanels(panels: ViewletPanel[]): void {
@@ -403,7 +413,7 @@ export class PanelViewlet extends Viewlet {
this.lastFocusedPanel = undefined;
}
this.panelview.removePanel(panel);
assertIsDefined(this.panelview).removePanel(panel);
const [panelItem] = this.panelItems.splice(index, 1);
panelItem.disposable.dispose();
@@ -424,15 +434,15 @@ export class PanelViewlet extends Viewlet {
const [panelItem] = this.panelItems.splice(fromIndex, 1);
this.panelItems.splice(toIndex, 0, panelItem);
this.panelview.movePanel(from, to);
assertIsDefined(this.panelview).movePanel(from, to);
}
resizePanel(panel: ViewletPanel, size: number): void {
this.panelview.resizePanel(panel, size);
assertIsDefined(this.panelview).resizePanel(panel, size);
}
getPanelSize(panel: ViewletPanel): number {
return this.panelview.getPanelSize(panel);
return assertIsDefined(this.panelview).getPanelSize(panel);
}
protected updateViewHeaders(): void {
@@ -451,6 +461,8 @@ export class PanelViewlet extends Viewlet {
dispose(): void {
super.dispose();
this.panelItems.forEach(i => i.disposable.dispose());
this.panelview.dispose();
if (this.panelview) {
this.panelview.dispose();
}
}
}

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/views';
import { Disposable, IDisposable, toDisposable, dispose } from 'vs/base/common/lifecycle';
import { Disposable, IDisposable, toDisposable, DisposableStore } from 'vs/base/common/lifecycle';
import { IViewsService, IViewsViewlet, ViewContainer, IViewDescriptor, IViewContainersRegistry, Extensions as ViewExtensions, IView, IViewDescriptorCollection, IViewsRegistry } from 'vs/workbench/common/views';
import { Registry } from 'vs/platform/registry/common/platform';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
@@ -22,14 +22,14 @@ import { IFileIconTheme, IWorkbenchThemeService } from 'vs/workbench/services/th
import { toggleClass, addClass } from 'vs/base/browser/dom';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
function filterViewRegisterEvent(container: ViewContainer, event: Event<{ viewContainer: ViewContainer, views: IViewDescriptor[] }>): Event<IViewDescriptor[]> {
function filterViewRegisterEvent(container: ViewContainer, event: Event<{ viewContainer: ViewContainer, views: IViewDescriptor[]; }>): Event<IViewDescriptor[]> {
return Event.chain(event)
.map(({ views, viewContainer }) => viewContainer === container ? views : [])
.filter(views => views.length > 0)
.event;
}
function filterViewMoveEvent(container: ViewContainer, event: Event<{ from: ViewContainer, to: ViewContainer, views: IViewDescriptor[] }>): Event<{ added?: IViewDescriptor[], removed?: IViewDescriptor[] }> {
function filterViewMoveEvent(container: ViewContainer, event: Event<{ from: ViewContainer, to: ViewContainer, views: IViewDescriptor[]; }>): Event<{ added?: IViewDescriptor[], removed?: IViewDescriptor[]; }> {
return Event.chain(event)
.map(({ views, from, to }) => from === container ? { removed: views } : to === container ? { added: views } : {})
.filter(({ added, removed }) => isNonEmptyArray(added) || isNonEmptyArray(removed))
@@ -78,8 +78,8 @@ class ViewDescriptorCollection extends Disposable implements IViewDescriptorColl
private contextKeys = new CounterSet<string>();
private items: IViewItem[] = [];
private _onDidChange: Emitter<{ added: IViewDescriptor[], removed: IViewDescriptor[] }> = this._register(new Emitter<{ added: IViewDescriptor[], removed: IViewDescriptor[] }>());
readonly onDidChangeActiveViews: Event<{ added: IViewDescriptor[], removed: IViewDescriptor[] }> = this._onDidChange.event;
private _onDidChange: Emitter<{ added: IViewDescriptor[], removed: IViewDescriptor[]; }> = this._register(new Emitter<{ added: IViewDescriptor[], removed: IViewDescriptor[]; }>());
readonly onDidChangeActiveViews: Event<{ added: IViewDescriptor[], removed: IViewDescriptor[]; }> = this._onDidChange.event;
get activeViewDescriptors(): IViewDescriptor[] {
return this.items
@@ -356,7 +356,7 @@ export class ContributableViewsModel extends Disposable {
return viewDescriptor.workspace ? !!viewState.visibleWorkspace : !!viewState.visibleGlobal;
}
private find(id: string): { index: number, visibleIndex: number, viewDescriptor: IViewDescriptor, state: IViewState } {
private find(id: string): { index: number, visibleIndex: number, viewDescriptor: IViewDescriptor, state: IViewState; } {
for (let i = 0, visibleIndex = 0; i < this.viewDescriptors.length; i++) {
const viewDescriptor = this.viewDescriptors[i];
const state = this.viewStates.get(viewDescriptor.id);
@@ -436,8 +436,8 @@ export class ContributableViewsModel extends Disposable {
(a, b) => a.id === b.id ? 0 : a.id < b.id ? -1 : 1
).reverse();
const toRemove: { index: number, viewDescriptor: IViewDescriptor }[] = [];
const toAdd: { index: number, viewDescriptor: IViewDescriptor, size?: number, collapsed: boolean }[] = [];
const toRemove: { index: number, viewDescriptor: IViewDescriptor; }[] = [];
const toAdd: { index: number, viewDescriptor: IViewDescriptor, size?: number, collapsed: boolean; }[] = [];
for (const splice of splices) {
const startViewDescriptor = this.viewDescriptors[splice.start];
@@ -521,7 +521,7 @@ export class PersistentContributableViewsModel extends ContributableViewsModel {
}
private saveWorkspaceViewsStates(): void {
const storedViewsStates: { [id: string]: IStoredWorkspaceViewState } = JSON.parse(this.storageService.get(this.workspaceViewsStateStorageId, StorageScope.WORKSPACE, '{}'));
const storedViewsStates: { [id: string]: IStoredWorkspaceViewState; } = JSON.parse(this.storageService.get(this.workspaceViewsStateStorageId, StorageScope.WORKSPACE, '{}'));
for (const viewDescriptor of this.viewDescriptors) {
const viewState = this.viewStates.get(viewDescriptor.id);
if (viewState) {
@@ -557,7 +557,7 @@ export class PersistentContributableViewsModel extends ContributableViewsModel {
private static loadViewsStates(workspaceViewsStateStorageId: string, globalViewsStateStorageId: string, storageService: IStorageService): Map<string, IViewState> {
const viewStates = new Map<string, IViewState>();
const workspaceViewsStates = <{ [id: string]: IStoredWorkspaceViewState }>JSON.parse(storageService.get(workspaceViewsStateStorageId, StorageScope.WORKSPACE, '{}'));
const workspaceViewsStates = <{ [id: string]: IStoredWorkspaceViewState; }>JSON.parse(storageService.get(workspaceViewsStateStorageId, StorageScope.WORKSPACE, '{}'));
for (const id of Object.keys(workspaceViewsStates)) {
const workspaceViewState = workspaceViewsStates[id];
viewStates.set(id, {
@@ -636,7 +636,7 @@ export class ViewsService extends Disposable implements IViewsService {
_serviceBrand: undefined;
private readonly viewDescriptorCollections: Map<ViewContainer, { viewDescriptorCollection: IViewDescriptorCollection, disposable: IDisposable }>;
private readonly viewDescriptorCollections: Map<ViewContainer, { viewDescriptorCollection: IViewDescriptorCollection, disposable: IDisposable; }>;
private readonly viewDisposable: Map<IViewDescriptor, IDisposable>;
private readonly activeViewContextKeys: Map<string, IContextKey<boolean>>;
@@ -646,7 +646,7 @@ export class ViewsService extends Disposable implements IViewsService {
) {
super();
this.viewDescriptorCollections = new Map<ViewContainer, { viewDescriptorCollection: IViewDescriptorCollection, disposable: IDisposable }>();
this.viewDescriptorCollections = new Map<ViewContainer, { viewDescriptorCollection: IViewDescriptorCollection, disposable: IDisposable; }>();
this.viewDisposable = new Map<IViewDescriptor, IDisposable>();
this.activeViewContextKeys = new Map<string, IContextKey<boolean>>();
@@ -692,13 +692,13 @@ export class ViewsService extends Disposable implements IViewsService {
}
private onDidRegisterViewContainer(viewContainer: ViewContainer): void {
const viewDescriptorCollection = new ViewDescriptorCollection(viewContainer, this.contextKeyService);
const disposables: IDisposable[] = [viewDescriptorCollection];
const disposables = new DisposableStore();
const viewDescriptorCollection = disposables.add(new ViewDescriptorCollection(viewContainer, this.contextKeyService));
this.onDidChangeActiveViews({ added: viewDescriptorCollection.activeViewDescriptors, removed: [] });
viewDescriptorCollection.onDidChangeActiveViews(changed => this.onDidChangeActiveViews(changed), this, disposables);
this.viewDescriptorCollections.set(viewContainer, { viewDescriptorCollection, disposable: toDisposable(() => dispose(disposables)) });
this.viewDescriptorCollections.set(viewContainer, { viewDescriptorCollection, disposable: disposables });
}
private onDidDeregisterViewContainer(viewContainer: ViewContainer): void {
@@ -709,7 +709,7 @@ export class ViewsService extends Disposable implements IViewsService {
}
}
private onDidChangeActiveViews({ added, removed }: { added: IViewDescriptor[], removed: IViewDescriptor[] }): void {
private onDidChangeActiveViews({ added, removed }: { added: IViewDescriptor[], removed: IViewDescriptor[]; }): void {
added.forEach(viewDescriptor => this.getOrCreateActiveViewContextKey(viewDescriptor).set(true));
removed.forEach(viewDescriptor => this.getOrCreateActiveViewContextKey(viewDescriptor).set(false));
}
@@ -717,7 +717,7 @@ export class ViewsService extends Disposable implements IViewsService {
private onDidRegisterViews(container: ViewContainer, views: IViewDescriptor[]): void {
const viewlet = this.viewletService.getViewlet(container.id);
for (const viewDescriptor of views) {
const disposables: IDisposable[] = [];
const disposables = new DisposableStore();
const command: ICommandAction = {
id: viewDescriptor.focusCommand ? viewDescriptor.focusCommand.id : `${viewDescriptor.id}.focus`,
title: { original: `Focus on ${viewDescriptor.name} View`, value: localize('focus view', "Focus on {0} View", viewDescriptor.name) },
@@ -725,9 +725,9 @@ export class ViewsService extends Disposable implements IViewsService {
};
const when = ContextKeyExpr.has(`${viewDescriptor.id}.active`);
disposables.push(CommandsRegistry.registerCommand(command.id, () => this.openView(viewDescriptor.id, true)));
disposables.add(CommandsRegistry.registerCommand(command.id, () => this.openView(viewDescriptor.id, true)));
disposables.push(MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
disposables.add(MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
command,
when
}));
@@ -745,7 +745,7 @@ export class ViewsService extends Disposable implements IViewsService {
});
}
this.viewDisposable.set(viewDescriptor, toDisposable(() => dispose(disposables)));
this.viewDisposable.set(viewDescriptor, disposables);
}
}

View File

@@ -64,6 +64,9 @@ export abstract class ViewContainerViewlet extends PanelViewlet implements IView
super(id, { showHeaderInTitleWhenSingleView, dnd: new DefaultPanelDndController() }, configurationService, layoutService, contextMenuService, telemetryService, themeService, storageService);
const container = Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry).get(id);
if (!container) {
throw new Error('Could not find container');
}
this.viewsModel = this._register(this.instantiationService.createInstance(PersistentContributableViewsModel, container, viewletStateStorageId));
this.viewletState = this.getMemento(StorageScope.WORKSPACE);

View File

@@ -3,11 +3,11 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
import * as objects from 'vs/base/common/objects';
import * as arrays from 'vs/base/common/arrays';
import * as strings from 'vs/base/common/strings';
import * as types from 'vs/base/common/types';
import { localize } from 'vs/nls';
import { mixin, assign } from 'vs/base/common/objects';
import { first } from 'vs/base/common/arrays';
import { startsWith } from 'vs/base/common/strings';
import { isString, assertIsDefined, withNullAsUndefined } from 'vs/base/common/types';
import { Registry } from 'vs/platform/registry/common/platform';
import { Action } from 'vs/base/common/actions';
import { Mode, IEntryRunContext, IAutoFocus, IModel, IQuickNavigateConfiguration } from 'vs/base/parts/quickopen/common/quickOpen';
@@ -111,9 +111,9 @@ export class QuickOpenHandler {
*/
getEmptyLabel(searchString: string): string {
if (searchString.length > 0) {
return nls.localize('noResultsMatching', "No results matching");
return localize('noResultsMatching', "No results matching");
}
return nls.localize('noResultsFound2', "No results found");
return localize('noResultsFound2', "No results found");
}
}
@@ -128,9 +128,9 @@ export interface QuickOpenHandlerHelpEntry {
*/
export class QuickOpenHandlerDescriptor {
prefix: string;
description: string;
description?: string;
contextKey?: string;
helpEntries: QuickOpenHandlerHelpEntry[];
helpEntries?: QuickOpenHandlerHelpEntry[];
instantProgress: boolean;
private id: string;
@@ -145,7 +145,7 @@ export class QuickOpenHandlerDescriptor {
this.contextKey = contextKey;
this.instantProgress = instantProgress;
if (types.isString(param)) {
if (isString(param)) {
this.description = param;
} else {
this.helpEntries = param;
@@ -195,7 +195,7 @@ export interface IQuickOpenRegistry {
class QuickOpenRegistry implements IQuickOpenRegistry {
private handlers: QuickOpenHandlerDescriptor[] = [];
private defaultHandler: QuickOpenHandlerDescriptor;
private defaultHandler: QuickOpenHandlerDescriptor | undefined;
registerQuickOpenHandler(descriptor: QuickOpenHandlerDescriptor): void {
this.handlers.push(descriptor);
@@ -214,11 +214,11 @@ class QuickOpenRegistry implements IQuickOpenRegistry {
}
getQuickOpenHandler(text: string): QuickOpenHandlerDescriptor | null {
return text ? (arrays.first<QuickOpenHandlerDescriptor>(this.handlers, h => strings.startsWith(text, h.prefix)) || null) : null;
return text ? (first<QuickOpenHandlerDescriptor>(this.handlers, h => startsWith(text, h.prefix)) || null) : null;
}
getDefaultQuickOpenHandler(): QuickOpenHandlerDescriptor {
return this.defaultHandler;
return assertIsDefined(this.defaultHandler);
}
}
@@ -275,17 +275,17 @@ export class EditorQuickOpenEntry extends QuickOpenEntry implements IEditorQuick
if (input instanceof EditorInput) {
let opts = this.getOptions();
if (opts) {
opts = objects.mixin(opts, openOptions, true);
opts = mixin(opts, openOptions, true);
} else if (openOptions) {
opts = EditorOptions.create(openOptions);
}
this.editorService.openEditor(input, types.withNullAsUndefined(opts), sideBySide ? SIDE_GROUP : ACTIVE_GROUP);
this.editorService.openEditor(input, withNullAsUndefined(opts), sideBySide ? SIDE_GROUP : ACTIVE_GROUP);
} else {
const resourceInput = <IResourceInput>input;
if (openOptions) {
resourceInput.options = objects.assign(resourceInput.options || Object.create(null), openOptions);
resourceInput.options = assign(resourceInput.options || Object.create(null), openOptions);
}
this.editorService.openEditor(resourceInput, sideBySide ? SIDE_GROUP : ACTIVE_GROUP);

View File

@@ -21,6 +21,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
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 { assertIsDefined } from 'vs/base/common/types';
export abstract class Viewlet extends Composite implements IViewlet {
@@ -34,8 +35,8 @@ export abstract class Viewlet extends Composite implements IViewlet {
super(id, telemetryService, themeService, storageService);
}
getOptimalWidth(): number | null {
return null;
getOptimalWidth(): number | undefined {
return undefined;
}
getContextMenuActions(): IAction[] {
@@ -76,7 +77,7 @@ export const Extensions = {
};
export class ViewletRegistry extends CompositeRegistry<Viewlet> {
private defaultViewletId!: string;
private defaultViewletId: string | undefined;
/**
* Registers a viewlet to the platform.
@@ -120,7 +121,7 @@ export class ViewletRegistry extends CompositeRegistry<Viewlet> {
* Gets the id of the viewlet that should open on startup by default.
*/
getDefaultViewletId(): string {
return this.defaultViewletId;
return assertIsDefined(this.defaultViewletId);
}
}
@@ -166,8 +167,9 @@ export class ShowViewletAction extends Action {
private sidebarHasFocus(): boolean {
const activeViewlet = this.viewletService.getActiveViewlet();
const activeElement = document.activeElement;
const sidebarPart = this.layoutService.getContainer(Parts.SIDEBAR_PART);
return !!(activeViewlet && activeElement && DOM.isAncestor(activeElement, this.layoutService.getContainer(Parts.SIDEBAR_PART)));
return !!(activeViewlet && activeElement && sidebarPart && DOM.isAncestor(activeElement, sidebarPart));
}
}

View File

@@ -288,14 +288,6 @@ class BrowserMain extends Disposable {
let workspace: IWorkspace | undefined = undefined;
if (this.configuration.workspaceProvider) {
workspace = this.configuration.workspaceProvider.workspace;
} else {
// TODO@ben remove me once IWorkspaceProvider API is adopted
const legacyConfiguration = this.configuration as { workspaceUri?: URI, folderUri?: URI };
if (legacyConfiguration.workspaceUri) {
workspace = { workspaceUri: legacyConfiguration.workspaceUri };
} else if (legacyConfiguration.folderUri) {
workspace = { folderUri: legacyConfiguration.folderUri };
}
}
// Multi-root workspace
@@ -311,7 +303,7 @@ class BrowserMain extends Disposable {
return { id: 'empty-window' };
}
private getRemoteUserDataUri(): URI | null {
private getRemoteUserDataUri(): URI | undefined {
const element = document.getElementById('vscode-remote-user-data-uri');
if (element) {
const remoteUserDataPath = element.getAttribute('data-settings');
@@ -320,7 +312,7 @@ class BrowserMain extends Disposable {
}
}
return null;
return undefined;
}
}

View File

@@ -60,6 +60,16 @@ import { isMacintosh, isWindows, isLinux, isWeb, isNative } from 'vs/base/common
],
'description': 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.")
},
'workbench.editor.splitSizing': {
'type': 'string',
'enum': ['distribute', 'split'],
'default': 'distribute',
'enumDescriptions': [
nls.localize('workbench.editor.splitSizingDistribute', "Splits all the editor groups to equal parts."),
nls.localize('workbench.editor.splitSizingSplit', "Splits the active editor group to equal parts.")
],
'description': nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'splitSizing' }, "Controls the sizing of editor groups when splitting them.")
},
'workbench.editor.focusRecentEditorAfterClose': {
'type': 'boolean',
'description': nls.localize('focusRecentEditorAfterClose', "Controls whether tabs are closed in most recently used order or from left to right."),
@@ -364,6 +374,11 @@ import { isMacintosh, isWindows, isLinux, isWeb, isNative } from 'vs/base/common
'type': 'boolean',
'default': false,
'description': nls.localize('zenMode.restore', "Controls whether a window should restore to zen mode if it was exited in zen mode.")
},
'zenMode.silentNotifications': {
'type': 'boolean',
'default': true,
'description': nls.localize('zenMode.silentNotifications', "Controls whether notifications are shown while in zen mode. If true, only error notifications will pop out.")
}
}
});

View File

@@ -19,7 +19,7 @@ import { IEditorInputFactoryRegistry, Extensions as EditorExtensions } from 'vs/
import { IActionBarRegistry, Extensions as ActionBarExtensions } from 'vs/workbench/browser/actions';
import { getSingletonServiceDescriptors } from 'vs/platform/instantiation/common/extensions';
import { Position, Parts, IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
import { IStorageService, WillSaveStateReason, StorageScope, IWillSaveStateEvent } from 'vs/platform/storage/common/storage';
import { IStorageService, WillSaveStateReason, StorageScope } from 'vs/platform/storage/common/storage';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
@@ -157,7 +157,7 @@ export class Workbench extends Layout {
this.renderWorkbench(instantiationService, accessor.get(INotificationService) as NotificationService, storageService, configurationService);
// Workbench Layout
this.createWorkbenchLayout(instantiationService);
this.createWorkbenchLayout();
// Layout
this.layout();
@@ -227,6 +227,20 @@ export class Workbench extends Layout {
configurationService: IConfigurationService
): void {
// Configuration changes
this._register(configurationService.onDidChangeConfiguration(() => this.setFontAliasing(configurationService)));
// Font Info
if (isNative) {
this._register(storageService.onWillSaveState(e => {
if (e.reason === WillSaveStateReason.SHUTDOWN) {
this.storeFontInfo(storageService);
}
}));
} else {
this._register(lifecycleService.onWillShutdown(() => this.storeFontInfo(storageService)));
}
// Lifecycle
this._register(lifecycleService.onBeforeShutdown(event => this._onBeforeShutdown.fire(event)));
this._register(lifecycleService.onWillShutdown(event => this._onWillShutdown.fire(event)));
@@ -234,12 +248,6 @@ export class Workbench extends Layout {
this._onShutdown.fire();
this.dispose();
}));
// Configuration changes
this._register(configurationService.onDidChangeConfiguration(() => this.setFontAliasing(configurationService)));
// Storage
this._register(storageService.onWillSaveState(e => this.storeFontInfo(e, storageService)));
}
private fontAliasing: 'default' | 'antialiased' | 'none' | 'auto' | undefined;
@@ -279,13 +287,19 @@ export class Workbench extends Layout {
readFontInfo(BareFontInfo.createFromRawSettings(configurationService.getValue('editor'), getZoomLevel()));
}
private storeFontInfo(e: IWillSaveStateEvent, storageService: IStorageService): void {
if (e.reason === WillSaveStateReason.SHUTDOWN) {
const serializedFontInfo = serializeFontInfo();
if (serializedFontInfo) {
const serializedFontInfoRaw = JSON.stringify(serializedFontInfo);
private storeFontInfo(storageService: IStorageService): void {
const serializedFontInfo = serializeFontInfo();
if (serializedFontInfo) {
const serializedFontInfoRaw = JSON.stringify(serializedFontInfo);
isNative ? storageService.store('editorFontInfo', serializedFontInfoRaw, StorageScope.GLOBAL) : window.localStorage.setItem('editorFontInfo', serializedFontInfoRaw);
// Font info is very specific to the machine the workbench runs
// on. As such, in the web, we prefer to store this info in
// local storage and not global storage because it would not make
// much sense to synchronize to other machines.
if (isNative) {
storageService.store('editorFontInfo', serializedFontInfoRaw, StorageScope.GLOBAL);
} else {
window.localStorage.setItem('editorFontInfo', serializedFontInfoRaw);
}
}
}