mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-03-30 08:40:29 -04:00
Merge from vscode 81d7885dc2e9dc617e1522697a2966bc4025a45d (#5949)
* Merge from vscode 81d7885dc2e9dc617e1522697a2966bc4025a45d * Fix vs unit tests and hygiene issue * Fix strict null check issue
This commit is contained in:
@@ -61,11 +61,13 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
||||
//#endregion
|
||||
|
||||
private globalActionBar: ActionBar;
|
||||
private globalActivityIdToActions: { [globalActivityId: string]: GlobalActivityAction; } = Object.create(null);
|
||||
private globalActivityIdToActions: Map<string, GlobalActivityAction> = new Map();
|
||||
|
||||
private cachedViewlets: ICachedViewlet[] = [];
|
||||
|
||||
private compositeBar: CompositeBar;
|
||||
private compositeActions: { [compositeId: string]: { activityAction: ViewletActivityAction, pinnedAction: ToggleCompositePinnedAction } } = Object.create(null);
|
||||
private compositeActions: Map<string, { activityAction: ViewletActivityAction, pinnedAction: ToggleCompositePinnedAction }> = new Map();
|
||||
|
||||
private readonly viewletDisposables: Map<string, IDisposable> = new Map<string, IDisposable>();
|
||||
|
||||
constructor(
|
||||
@@ -169,7 +171,7 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
||||
throw illegalArgument('badge');
|
||||
}
|
||||
|
||||
const action = this.globalActivityIdToActions[globalActivityId];
|
||||
const action = this.globalActivityIdToActions.get(globalActivityId);
|
||||
if (!action) {
|
||||
throw illegalArgument('globalActivityId');
|
||||
}
|
||||
@@ -181,14 +183,15 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
||||
|
||||
createContentArea(parent: HTMLElement): HTMLElement {
|
||||
this.element = parent;
|
||||
|
||||
const content = document.createElement('div');
|
||||
addClass(content, 'content');
|
||||
parent.appendChild(content);
|
||||
|
||||
// Top Actionbar with action items for each viewlet action
|
||||
// Viewlets action bar
|
||||
this.compositeBar.create(content);
|
||||
|
||||
// Top Actionbar with action items for each viewlet action
|
||||
// Global action bar
|
||||
const globalActivities = document.createElement('div');
|
||||
addClass(globalActivities, 'global-activity');
|
||||
content.appendChild(globalActivities);
|
||||
@@ -208,7 +211,7 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
||||
|
||||
const borderColor = this.getColor(ACTIVITY_BAR_BORDER) || this.getColor(contrastBorder);
|
||||
const isPositionLeft = this.layoutService.getSideBarPosition() === SideBarPosition.LEFT;
|
||||
container.style.boxSizing = borderColor && isPositionLeft ? 'border-box' : null;
|
||||
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;
|
||||
@@ -243,13 +246,13 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
||||
}));
|
||||
|
||||
actions.forEach(a => {
|
||||
this.globalActivityIdToActions[a.id] = a;
|
||||
this.globalActivityIdToActions.set(a.id, a);
|
||||
this.globalActionBar.push(a);
|
||||
});
|
||||
}
|
||||
|
||||
private getCompositeActions(compositeId: string): { activityAction: ViewletActivityAction, pinnedAction: ToggleCompositePinnedAction } {
|
||||
let compositeActions = this.compositeActions[compositeId];
|
||||
let compositeActions = this.compositeActions.get(compositeId);
|
||||
if (!compositeActions) {
|
||||
const viewlet = this.viewletService.getViewlet(compositeId);
|
||||
if (viewlet) {
|
||||
@@ -265,7 +268,7 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
||||
};
|
||||
}
|
||||
|
||||
this.compositeActions[compositeId] = compositeActions;
|
||||
this.compositeActions.set(compositeId, compositeActions);
|
||||
}
|
||||
|
||||
return compositeActions;
|
||||
@@ -341,11 +344,11 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
||||
|
||||
private hideComposite(compositeId: string): void {
|
||||
this.compositeBar.hideComposite(compositeId);
|
||||
const compositeActions = this.compositeActions[compositeId];
|
||||
const compositeActions = this.compositeActions.get(compositeId);
|
||||
if (compositeActions) {
|
||||
compositeActions.activityAction.dispose();
|
||||
compositeActions.pinnedAction.dispose();
|
||||
delete this.compositeActions[compositeId];
|
||||
this.compositeActions.delete(compositeId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -427,7 +427,7 @@ export class CompositeBar extends Widget implements ICompositeBar {
|
||||
});
|
||||
}
|
||||
|
||||
private getContextMenuActions(): IAction[] {
|
||||
private getContextMenuActions(): ReadonlyArray<IAction> {
|
||||
const actions: IAction[] = this.model.visibleItems
|
||||
.map(({ id, name, activityAction }) => (<IAction>{
|
||||
id,
|
||||
|
||||
@@ -549,11 +549,11 @@ export class CompositeActionViewItem extends ActivityActionViewItem {
|
||||
}));
|
||||
|
||||
// Activate on drag over to reveal targets
|
||||
[this.badge, this.label].forEach(b => new DelayedDragHandler(b, () => {
|
||||
[this.badge, this.label].forEach(b => this._register(new DelayedDragHandler(b, () => {
|
||||
if (!this.compositeTransfer.hasData(DraggedCompositeIdentifier.prototype) && !this.getAction().checked) {
|
||||
this.getAction().run();
|
||||
}
|
||||
}));
|
||||
})));
|
||||
|
||||
this.updateStyles();
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import 'vs/css!./media/compositepart';
|
||||
import * as nls from 'vs/nls';
|
||||
import { defaultGenerator } from 'vs/base/common/idGenerator';
|
||||
import { IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IDisposable, dispose, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import * as strings from 'vs/base/common/strings';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import * as errors from 'vs/base/common/errors';
|
||||
@@ -18,13 +18,13 @@ import { IAction } from 'vs/base/common/actions';
|
||||
import { Part, IPartOptions } from 'vs/workbench/browser/part';
|
||||
import { Composite, CompositeRegistry } from 'vs/workbench/browser/composite';
|
||||
import { IComposite } from 'vs/workbench/common/composite';
|
||||
import { ScopedProgressService } from 'vs/workbench/services/progress/browser/progressService';
|
||||
import { ScopedProgressService } from 'vs/workbench/services/progress/browser/localProgressService';
|
||||
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
|
||||
import { IProgressService } from 'vs/platform/progress/common/progress';
|
||||
import { ILocalProgressService } from 'vs/platform/progress/common/progress';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
@@ -50,7 +50,7 @@ export interface ICompositeTitleLabel {
|
||||
interface CompositeItem {
|
||||
composite: Composite;
|
||||
disposable: IDisposable;
|
||||
progressService: IProgressService;
|
||||
localProgressService: ILocalProgressService;
|
||||
}
|
||||
|
||||
export abstract class CompositePart<T extends Composite> extends Part {
|
||||
@@ -60,8 +60,8 @@ export abstract class CompositePart<T extends Composite> extends Part {
|
||||
|
||||
protected toolBar: ToolBar;
|
||||
|
||||
private mapCompositeToCompositeContainer: { [compositeId: string]: HTMLElement; };
|
||||
private mapActionsBindingToComposite: { [compositeId: string]: () => void; };
|
||||
private mapCompositeToCompositeContainer = new Map<string, HTMLElement>();
|
||||
private mapActionsBindingToComposite = new Map<string, () => void>();
|
||||
private activeComposite: Composite | null;
|
||||
private lastActiveCompositeId: string;
|
||||
private instantiatedCompositeItems: Map<string, CompositeItem>;
|
||||
@@ -91,8 +91,6 @@ export abstract class CompositePart<T extends Composite> extends Part {
|
||||
) {
|
||||
super(id, options, themeService, storageService, layoutService);
|
||||
|
||||
this.mapCompositeToCompositeContainer = {};
|
||||
this.mapActionsBindingToComposite = {};
|
||||
this.activeComposite = null;
|
||||
this.instantiatedCompositeItems = new Map<string, CompositeItem>();
|
||||
this.lastActiveCompositeId = storageService.get(activeCompositeSettingsKey, StorageScope.WORKSPACE, this.defaultCompositeId);
|
||||
@@ -171,17 +169,17 @@ export abstract class CompositePart<T extends Composite> extends Part {
|
||||
// Instantiate composite from registry otherwise
|
||||
const compositeDescriptor = this.registry.getComposite(id);
|
||||
if (compositeDescriptor) {
|
||||
const progressService = this.instantiationService.createInstance(ScopedProgressService, this.progressBar, compositeDescriptor.id, isActive);
|
||||
const compositeInstantiationService = this.instantiationService.createChild(new ServiceCollection([IProgressService, progressService]));
|
||||
const localProgressService = this.instantiationService.createInstance(ScopedProgressService, this.progressBar, compositeDescriptor.id, isActive);
|
||||
const compositeInstantiationService = this.instantiationService.createChild(new ServiceCollection([ILocalProgressService, localProgressService]));
|
||||
|
||||
const composite = compositeDescriptor.instantiate(compositeInstantiationService);
|
||||
const disposables: IDisposable[] = [];
|
||||
const disposables = new DisposableStore();
|
||||
|
||||
// Remember as Instantiated
|
||||
this.instantiatedCompositeItems.set(id, { composite, disposable: toDisposable(() => dispose(disposables)), progressService });
|
||||
this.instantiatedCompositeItems.set(id, { composite, disposable: disposables, localProgressService });
|
||||
|
||||
// Register to title area update events from the composite
|
||||
composite.onTitleAreaUpdate(() => this.onTitleAreaUpdate(composite.getId()), this, disposables);
|
||||
disposables.add(composite.onTitleAreaUpdate(() => this.onTitleAreaUpdate(composite.getId()), this));
|
||||
|
||||
return composite;
|
||||
}
|
||||
@@ -206,7 +204,7 @@ export abstract class CompositePart<T extends Composite> extends Part {
|
||||
this.lastActiveCompositeId = this.activeComposite.getId();
|
||||
|
||||
// Composites created for the first time
|
||||
let compositeContainer = this.mapCompositeToCompositeContainer[composite.getId()];
|
||||
let compositeContainer = this.mapCompositeToCompositeContainer.get(composite.getId());
|
||||
if (!compositeContainer) {
|
||||
|
||||
// Build Container off-DOM
|
||||
@@ -218,13 +216,7 @@ export abstract class CompositePart<T extends Composite> extends Part {
|
||||
composite.updateStyles();
|
||||
|
||||
// Remember composite container
|
||||
this.mapCompositeToCompositeContainer[composite.getId()] = compositeContainer;
|
||||
}
|
||||
|
||||
// Report progress for slow loading composites (but only if we did not create the composites before already)
|
||||
const compositeItem = this.instantiatedCompositeItems.get(composite.getId());
|
||||
if (compositeItem && !compositeContainer) {
|
||||
compositeItem.progressService.showWhile(Promise.resolve(), this.layoutService.isRestored() ? 800 : 3200 /* less ugly initial startup */);
|
||||
this.mapCompositeToCompositeContainer.set(composite.getId(), compositeContainer);
|
||||
}
|
||||
|
||||
// Fill Content and Actions
|
||||
@@ -250,10 +242,10 @@ export abstract class CompositePart<T extends Composite> extends Part {
|
||||
}
|
||||
|
||||
// Handle Composite Actions
|
||||
let actionsBinding = this.mapActionsBindingToComposite[composite.getId()];
|
||||
let actionsBinding = this.mapActionsBindingToComposite.get(composite.getId());
|
||||
if (!actionsBinding) {
|
||||
actionsBinding = this.collectCompositeActions(composite);
|
||||
this.mapActionsBindingToComposite[composite.getId()] = actionsBinding;
|
||||
this.mapActionsBindingToComposite.set(composite.getId(), actionsBinding);
|
||||
}
|
||||
actionsBinding();
|
||||
|
||||
@@ -306,13 +298,13 @@ export abstract class CompositePart<T extends Composite> extends Part {
|
||||
|
||||
// Actions
|
||||
const actionsBinding = this.collectCompositeActions(this.activeComposite);
|
||||
this.mapActionsBindingToComposite[this.activeComposite.getId()] = actionsBinding;
|
||||
this.mapActionsBindingToComposite.set(this.activeComposite.getId(), actionsBinding);
|
||||
actionsBinding();
|
||||
}
|
||||
|
||||
// Otherwise invalidate actions binding for next time when the composite becomes visible
|
||||
else {
|
||||
delete this.mapActionsBindingToComposite[compositeId];
|
||||
this.mapActionsBindingToComposite.delete(compositeId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -366,14 +358,16 @@ export abstract class CompositePart<T extends Composite> extends Part {
|
||||
const composite = this.activeComposite;
|
||||
this.activeComposite = null;
|
||||
|
||||
const compositeContainer = this.mapCompositeToCompositeContainer[composite.getId()];
|
||||
const compositeContainer = this.mapCompositeToCompositeContainer.get(composite.getId());
|
||||
|
||||
// Indicate to Composite
|
||||
composite.setVisible(false);
|
||||
|
||||
// Take Container Off-DOM and hide
|
||||
compositeContainer.remove();
|
||||
hide(compositeContainer);
|
||||
if (compositeContainer) {
|
||||
compositeContainer.remove();
|
||||
hide(compositeContainer);
|
||||
}
|
||||
|
||||
// Clear any running Progress
|
||||
this.progressBar.stop().hide();
|
||||
@@ -462,17 +456,17 @@ export abstract class CompositePart<T extends Composite> extends Part {
|
||||
return contentContainer;
|
||||
}
|
||||
|
||||
getProgressIndicator(id: string): IProgressService | null {
|
||||
getProgressIndicator(id: string): ILocalProgressService | null {
|
||||
const compositeItem = this.instantiatedCompositeItems.get(id);
|
||||
|
||||
return compositeItem ? compositeItem.progressService : null;
|
||||
return compositeItem ? compositeItem.localProgressService : null;
|
||||
}
|
||||
|
||||
protected getActions(): IAction[] {
|
||||
protected getActions(): ReadonlyArray<IAction> {
|
||||
return [];
|
||||
}
|
||||
|
||||
protected getSecondaryActions(): IAction[] {
|
||||
protected getSecondaryActions(): ReadonlyArray<IAction> {
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -496,8 +490,8 @@ export abstract class CompositePart<T extends Composite> extends Part {
|
||||
return false; // do not remove active composite
|
||||
}
|
||||
|
||||
delete this.mapCompositeToCompositeContainer[compositeId];
|
||||
delete this.mapActionsBindingToComposite[compositeId];
|
||||
this.mapCompositeToCompositeContainer.delete(compositeId);
|
||||
this.mapActionsBindingToComposite.delete(compositeId);
|
||||
const compositeItem = this.instantiatedCompositeItems.get(compositeId);
|
||||
if (compositeItem) {
|
||||
compositeItem.composite.dispose();
|
||||
@@ -509,8 +503,8 @@ export abstract class CompositePart<T extends Composite> extends Part {
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.mapCompositeToCompositeContainer = null!; // StrictNullOverride: nulling out ok in dispose
|
||||
this.mapActionsBindingToComposite = null!; // StrictNullOverride: nulling out ok in dispose
|
||||
this.mapCompositeToCompositeContainer.clear();
|
||||
this.mapActionsBindingToComposite.clear();
|
||||
|
||||
this.instantiatedCompositeItems.forEach(compositeItem => {
|
||||
compositeItem.composite.dispose();
|
||||
|
||||
@@ -15,6 +15,7 @@ import { URI } from 'vs/base/common/uri';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { isEmptyObject } from 'vs/base/common/types';
|
||||
import { DEFAULT_EDITOR_MIN_DIMENSIONS, DEFAULT_EDITOR_MAX_DIMENSIONS } from 'vs/workbench/browser/parts/editor/editor';
|
||||
import { MementoObject } from 'vs/workbench/common/memento';
|
||||
|
||||
/**
|
||||
* The base class of editors in the workbench. Editors register themselves for specific editor inputs.
|
||||
@@ -177,7 +178,7 @@ export class EditorMemento<T> implements IEditorMemento<T> {
|
||||
constructor(
|
||||
private _id: string,
|
||||
private key: string,
|
||||
private memento: object,
|
||||
private memento: MementoObject,
|
||||
private limit: number,
|
||||
private editorGroupService: IEditorGroupsService
|
||||
) { }
|
||||
|
||||
@@ -15,11 +15,11 @@ import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { ResourceViewerContext, ResourceViewer } from 'vs/workbench/browser/parts/editor/resourceViewer';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { Dimension, size, clearNode } from 'vs/base/browser/dom';
|
||||
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
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 { IFileService } from 'vs/platform/files/common/files';
|
||||
|
||||
export interface IOpenCallbacks {
|
||||
openInternal: (input: EditorInput, options: EditorOptions) => Promise<void>;
|
||||
@@ -48,7 +48,7 @@ export abstract class BaseBinaryResourceEditor extends BaseEditor {
|
||||
callbacks: IOpenCallbacks,
|
||||
telemetryService: ITelemetryService,
|
||||
themeService: IThemeService,
|
||||
@ITextFileService private readonly textFileService: ITextFileService,
|
||||
@IFileService private readonly fileService: IFileService,
|
||||
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService,
|
||||
@IStorageService storageService: IStorageService
|
||||
) {
|
||||
@@ -89,7 +89,11 @@ export abstract class BaseBinaryResourceEditor extends BaseEditor {
|
||||
}
|
||||
|
||||
// Render Input
|
||||
this.resourceViewerContext = ResourceViewer.show({ name: model.getName(), resource: model.getResource(), size: model.getSize(), etag: model.getETag(), mime: model.getMime() }, this.textFileService, this.binaryContainer, this.scrollbar, {
|
||||
if (this.resourceViewerContext) {
|
||||
this.resourceViewerContext.dispose();
|
||||
}
|
||||
|
||||
this.resourceViewerContext = ResourceViewer.show({ name: model.getName(), resource: model.getResource(), size: model.getSize(), etag: model.getETag(), mime: model.getMime() }, this.fileService, this.binaryContainer, this.scrollbar, {
|
||||
openInternalClb: () => this.handleOpenInternalCallback(input, options),
|
||||
openExternalClb: this.environmentService.configuration.remoteAuthority ? undefined : resource => this.callbacks.openExternal(resource),
|
||||
metadataClb: meta => this.handleMetadataChanged(meta)
|
||||
|
||||
@@ -122,7 +122,7 @@ Registry.as<IConfigurationRegistry>(Extensions.Configuration).registerConfigurat
|
||||
'breadcrumbs.enabled': {
|
||||
description: localize('enabled', "Enable/disable navigation breadcrumbs."),
|
||||
type: 'boolean',
|
||||
default: false
|
||||
default: true
|
||||
},
|
||||
// 'breadcrumbs.useQuickPick': {
|
||||
// description: localize('useQuickPick', "Use quick pick instead of breadcrumb-pickers."),
|
||||
|
||||
@@ -384,14 +384,14 @@ export class BreadcrumbsControl {
|
||||
this._breadcrumbsPickerShowing = true;
|
||||
this._updateCkBreadcrumbsActive();
|
||||
|
||||
return combinedDisposable([
|
||||
return combinedDisposable(
|
||||
picker,
|
||||
selectListener,
|
||||
focusListener,
|
||||
zoomListener,
|
||||
focusTracker,
|
||||
blurListener
|
||||
]);
|
||||
);
|
||||
},
|
||||
getAnchor: () => {
|
||||
let maxInnerWidth = window.innerWidth - 8 /*a little less the full widget*/;
|
||||
@@ -494,17 +494,17 @@ MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
|
||||
category: localize('cmd.category', "View")
|
||||
}
|
||||
});
|
||||
// {{SQL CARBON EDIT}} - Disable unused menu item
|
||||
// MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, {
|
||||
// group: '5_editor',
|
||||
// order: 99,
|
||||
// command: {
|
||||
// id: 'breadcrumbs.toggle',
|
||||
// title: localize('miToggleBreadcrumbs', "Toggle &&Breadcrumbs"),
|
||||
// toggled: ContextKeyExpr.equals('config.breadcrumbs.enabled', true)
|
||||
// }
|
||||
// });
|
||||
// {{SQL CARBON EDIT}} - End
|
||||
/* {{SQL CARBON EDIT}} - Disable unused menu item
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, {
|
||||
group: '5_editor',
|
||||
order: 3,
|
||||
command: {
|
||||
id: 'breadcrumbs.toggle',
|
||||
title: localize('miShowBreadcrumbs', "Show &&Breadcrumbs"),
|
||||
toggled: ContextKeyExpr.equals('config.breadcrumbs.enabled', true)
|
||||
}
|
||||
});
|
||||
*/
|
||||
CommandsRegistry.registerCommand('breadcrumbs.toggle', accessor => {
|
||||
let config = accessor.get(IConfigurationService);
|
||||
let value = BreadcrumbsConfig.IsEnabled.bindTo(config).getValue();
|
||||
|
||||
@@ -11,7 +11,7 @@ import { IEditorQuickOpenEntry, IQuickOpenRegistry, Extensions as QuickOpenExten
|
||||
import { StatusbarItemDescriptor, IStatusbarRegistry, Extensions as StatusExtensions } from 'vs/workbench/browser/parts/statusbar/statusbar';
|
||||
import { StatusbarAlignment } from 'vs/platform/statusbar/common/statusbar';
|
||||
import { IEditorRegistry, EditorDescriptor, Extensions as EditorExtensions } from 'vs/workbench/browser/editor';
|
||||
import { EditorInput, IEditorInputFactory, SideBySideEditorInput, IEditorInputFactoryRegistry, Extensions as EditorInputExtensions, TextCompareEditorActiveContext } from 'vs/workbench/common/editor';
|
||||
import { EditorInput, IEditorInputFactory, SideBySideEditorInput, IEditorInputFactoryRegistry, Extensions as EditorInputExtensions, TextCompareEditorActiveContext, EditorPinnedContext, EditorGroupEditorsCountContext } from 'vs/workbench/common/editor';
|
||||
import { TextResourceEditor } from 'vs/workbench/browser/parts/editor/textResourceEditor';
|
||||
import { SideBySideEditor } from 'vs/workbench/browser/parts/editor/sideBySideEditor';
|
||||
import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput';
|
||||
@@ -52,6 +52,8 @@ import { OpenWorkspaceButtonContribution } from 'vs/workbench/browser/parts/edit
|
||||
import { ZoomStatusbarItem } from 'vs/workbench/browser/parts/editor/resourceViewer';
|
||||
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
|
||||
import { toLocalResource } from 'vs/base/common/resources';
|
||||
import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions';
|
||||
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
|
||||
|
||||
// Register String Editor
|
||||
Registry.as<IEditorRegistry>(EditorExtensions.Editors).registerEditor(
|
||||
@@ -225,11 +227,17 @@ Registry.as<IEditorInputFactoryRegistry>(EditorInputExtensions.EditorInputFactor
|
||||
registerEditorContribution(OpenWorkspaceButtonContribution);
|
||||
|
||||
// Register Editor Status
|
||||
const statusBar = Registry.as<IStatusbarRegistry>(StatusExtensions.Statusbar);
|
||||
statusBar.registerStatusbarItem(new StatusbarItemDescriptor(EditorStatus, StatusbarAlignment.RIGHT, 100 /* towards the left of the right hand side */));
|
||||
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(EditorStatus, LifecyclePhase.Ready);
|
||||
|
||||
// Register Zoom Status
|
||||
statusBar.registerStatusbarItem(new StatusbarItemDescriptor(ZoomStatusbarItem, StatusbarAlignment.RIGHT, 101 /* to the left of editor status (100) */));
|
||||
const statusBar = Registry.as<IStatusbarRegistry>(StatusExtensions.Statusbar);
|
||||
statusBar.registerStatusbarItem(new StatusbarItemDescriptor(
|
||||
ZoomStatusbarItem,
|
||||
'status.imageZoom',
|
||||
nls.localize('status.imageZoom', "Image Zoom"),
|
||||
StatusbarAlignment.RIGHT,
|
||||
101 /* to the left of editor status (100) */)
|
||||
);
|
||||
|
||||
// Register Status Actions
|
||||
const registry = Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions);
|
||||
@@ -250,7 +258,7 @@ export class QuickOpenActionContributor extends ActionBarContributor {
|
||||
return !!entry;
|
||||
}
|
||||
|
||||
getActions(context: any): IAction[] {
|
||||
getActions(context: any): ReadonlyArray<IAction> {
|
||||
const actions: Action[] = [];
|
||||
|
||||
const entry = this.getEntry(context);
|
||||
@@ -439,11 +447,11 @@ MenuRegistry.appendMenuItem(MenuId.EmptyEditorGroupContext, { command: { id: edi
|
||||
|
||||
// Editor Title Context Menu
|
||||
MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.CLOSE_EDITOR_COMMAND_ID, title: nls.localize('close', "Close") }, group: '1_close', order: 10 });
|
||||
MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.CLOSE_OTHER_EDITORS_IN_GROUP_COMMAND_ID, title: nls.localize('closeOthers', "Close Others") }, group: '1_close', order: 20 });
|
||||
MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.CLOSE_EDITORS_TO_THE_RIGHT_COMMAND_ID, title: nls.localize('closeRight', "Close to the Right") }, group: '1_close', order: 30, when: ContextKeyExpr.has('config.workbench.editor.showTabs') });
|
||||
MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.CLOSE_OTHER_EDITORS_IN_GROUP_COMMAND_ID, title: nls.localize('closeOthers', "Close Others"), precondition: EditorGroupEditorsCountContext.notEqualsTo('1') }, group: '1_close', order: 20 });
|
||||
MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.CLOSE_EDITORS_TO_THE_RIGHT_COMMAND_ID, title: nls.localize('closeRight', "Close to the Right"), precondition: EditorGroupEditorsCountContext.notEqualsTo('1') }, group: '1_close', order: 30, when: ContextKeyExpr.has('config.workbench.editor.showTabs') });
|
||||
MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.CLOSE_SAVED_EDITORS_COMMAND_ID, title: nls.localize('closeAllSaved', "Close Saved") }, group: '1_close', order: 40 });
|
||||
MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.CLOSE_EDITORS_IN_GROUP_COMMAND_ID, title: nls.localize('closeAll', "Close All") }, group: '1_close', order: 50 });
|
||||
MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.KEEP_EDITOR_COMMAND_ID, title: nls.localize('keepOpen', "Keep Open") }, group: '3_preview', order: 10, when: ContextKeyExpr.has('config.workbench.editor.enablePreview') });
|
||||
MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.KEEP_EDITOR_COMMAND_ID, title: nls.localize('keepOpen', "Keep Open"), precondition: EditorPinnedContext.toNegated() }, group: '3_preview', order: 10, when: ContextKeyExpr.has('config.workbench.editor.enablePreview') });
|
||||
MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.SPLIT_EDITOR_UP, title: nls.localize('splitUp', "Split Up") }, group: '5_split', order: 10 });
|
||||
MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.SPLIT_EDITOR_DOWN, title: nls.localize('splitDown', "Split Down") }, group: '5_split', order: 20 });
|
||||
MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.SPLIT_EDITOR_LEFT, title: nls.localize('splitLeft', "Split Left") }, group: '5_split', order: 30 });
|
||||
|
||||
@@ -11,7 +11,7 @@ import { IEditorRegistry, Extensions as EditorExtensions, IEditorDescriptor } fr
|
||||
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
|
||||
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IProgressService, LongRunningOperation } from 'vs/platform/progress/common/progress';
|
||||
import { ILocalProgressService, LongRunningOperation } from 'vs/platform/progress/common/progress';
|
||||
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';
|
||||
@@ -47,11 +47,11 @@ export class EditorControl extends Disposable {
|
||||
private groupView: IEditorGroupView,
|
||||
@IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
@IProgressService progressService: IProgressService
|
||||
@ILocalProgressService localProgressService: ILocalProgressService
|
||||
) {
|
||||
super();
|
||||
|
||||
this.editorOperation = this._register(new LongRunningOperation(progressService));
|
||||
this.editorOperation = this._register(new LongRunningOperation(localProgressService));
|
||||
}
|
||||
|
||||
get activeControl(): IVisibleEditor | null {
|
||||
|
||||
@@ -88,10 +88,10 @@ class DropOverlay extends Themable {
|
||||
|
||||
// Overlay contrast border (if any)
|
||||
const activeContrastBorderColor = this.getColor(activeContrastBorder);
|
||||
this.overlay.style.outlineColor = activeContrastBorderColor;
|
||||
this.overlay.style.outlineOffset = activeContrastBorderColor ? '-2px' : null;
|
||||
this.overlay.style.outlineStyle = activeContrastBorderColor ? 'dashed' : null;
|
||||
this.overlay.style.outlineWidth = activeContrastBorderColor ? '2px' : null;
|
||||
this.overlay.style.outlineColor = activeContrastBorderColor || '';
|
||||
this.overlay.style.outlineOffset = activeContrastBorderColor ? '-2px' : '';
|
||||
this.overlay.style.outlineStyle = activeContrastBorderColor ? 'dashed' : '';
|
||||
this.overlay.style.outlineWidth = activeContrastBorderColor ? '2px' : '';
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import 'vs/css!./media/editorgroupview';
|
||||
|
||||
import { EditorGroup, IEditorOpenOptions, EditorCloseEvent, ISerializedEditorGroup, isSerializedEditorGroup } from 'vs/workbench/common/editor/editorGroup';
|
||||
import { EditorInput, EditorOptions, GroupIdentifier, ConfirmResult, SideBySideEditorInput, CloseDirection, IEditorCloseEvent, EditorGroupActiveEditorDirtyContext, IEditor } from 'vs/workbench/common/editor';
|
||||
import { EditorInput, EditorOptions, GroupIdentifier, ConfirmResult, SideBySideEditorInput, CloseDirection, IEditorCloseEvent, EditorGroupActiveEditorDirtyContext, IEditor, EditorGroupEditorsCountContext } from 'vs/workbench/common/editor';
|
||||
import { Event, Emitter, Relay } from 'vs/base/common/event';
|
||||
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { addClass, addClasses, Dimension, trackFocus, toggleClass, removeClass, addDisposableListener, EventType, EventHelper, findParentWithClass, clearNode, isAncestor } from 'vs/base/browser/dom';
|
||||
@@ -20,8 +20,8 @@ import { Themable, EDITOR_GROUP_HEADER_TABS_BORDER, EDITOR_GROUP_HEADER_TABS_BAC
|
||||
import { IMoveEditorOptions, ICopyEditorOptions, ICloseEditorsFilter, IGroupChangeEvent, GroupChangeKind, EditorsOrder, GroupsOrder, ICloseEditorOptions } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
import { TabsTitleControl } from 'vs/workbench/browser/parts/editor/tabsTitleControl';
|
||||
import { EditorControl } from 'vs/workbench/browser/parts/editor/editorControl';
|
||||
import { IProgressService } from 'vs/platform/progress/common/progress';
|
||||
import { ProgressService } from 'vs/workbench/services/progress/browser/progressService';
|
||||
import { ILocalProgressService } from 'vs/platform/progress/common/progress';
|
||||
import { LocalProgressService } from 'vs/workbench/services/progress/browser/localProgressService';
|
||||
import { localize } from 'vs/nls';
|
||||
import { isPromiseCanceledError } from 'vs/base/common/errors';
|
||||
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
|
||||
@@ -184,7 +184,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
||||
const scopedContextKeyService = this._register(this.contextKeyService.createScoped(this.element));
|
||||
this.scopedInstantiationService = this.instantiationService.createChild(new ServiceCollection(
|
||||
[IContextKeyService, scopedContextKeyService],
|
||||
[IProgressService, new ProgressService(this.progressBar)]
|
||||
[ILocalProgressService, new LocalProgressService(this.progressBar)]
|
||||
));
|
||||
|
||||
// Context keys
|
||||
@@ -220,6 +220,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
||||
|
||||
private handleGroupContextKeys(contextKeyServcie: IContextKeyService): void {
|
||||
const groupActiveEditorDirtyContextKey = EditorGroupActiveEditorDirtyContext.bindTo(contextKeyServcie);
|
||||
const groupEditorsCountContext = EditorGroupEditorsCountContext.bindTo(contextKeyServcie);
|
||||
|
||||
let activeEditorListener: IDisposable;
|
||||
|
||||
@@ -235,12 +236,17 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
||||
}
|
||||
};
|
||||
|
||||
// Track the active editor and update context key that reflects
|
||||
// the dirty state of this editor
|
||||
// Update group contexts based on group changes
|
||||
this._register(this.onDidGroupChange(e => {
|
||||
|
||||
// Track the active editor and update context key that reflects
|
||||
// the dirty state of this editor
|
||||
if (e.kind === GroupChangeKind.EDITOR_ACTIVE) {
|
||||
observeActiveEditor();
|
||||
}
|
||||
|
||||
// Group editors count context
|
||||
groupEditorsCountContext.set(this.count);
|
||||
}));
|
||||
|
||||
observeActiveEditor();
|
||||
@@ -285,7 +291,17 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
||||
});
|
||||
|
||||
// Toolbar actions
|
||||
const removeGroupAction = this._register(new Action(CLOSE_EDITOR_GROUP_COMMAND_ID, localize('closeGroupAction', "Close"), 'close-editor-group', true, () => { this.accessor.removeGroup(this); return Promise.resolve(true); }));
|
||||
const removeGroupAction = this._register(new Action(
|
||||
CLOSE_EDITOR_GROUP_COMMAND_ID,
|
||||
localize('closeGroupAction', "Close"),
|
||||
'close-editor-group',
|
||||
true,
|
||||
() => {
|
||||
this.accessor.removeGroup(this);
|
||||
|
||||
return Promise.resolve(true);
|
||||
}));
|
||||
|
||||
const keybinding = this.keybindingService.lookupKeybinding(removeGroupAction.id);
|
||||
containerToolbar.push(removeGroupAction, { icon: true, label: false, keybinding: keybinding ? keybinding.getLabel() : undefined });
|
||||
}
|
||||
@@ -408,7 +424,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
||||
|
||||
private async restoreEditors(from: IEditorGroupView | ISerializedEditorGroup): Promise<void> {
|
||||
if (this._group.count === 0) {
|
||||
return Promise.resolve(); // nothing to show
|
||||
return; // nothing to show
|
||||
}
|
||||
|
||||
// Determine editor options
|
||||
@@ -421,7 +437,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
||||
|
||||
const activeEditor = this._group.activeEditor;
|
||||
if (!activeEditor) {
|
||||
return Promise.resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
options.pinned = this._group.isPinned(activeEditor); // preserve pinned state
|
||||
@@ -823,30 +839,34 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
||||
private async doShowEditor(editor: EditorInput, active: boolean, options?: EditorOptions): Promise<IEditor | null> {
|
||||
|
||||
// Show in editor control if the active editor changed
|
||||
let openEditor: IEditor | null = null;
|
||||
let openEditorPromise: Promise<IEditor | null>;
|
||||
if (active) {
|
||||
try {
|
||||
const result = await this.editorControl.openEditor(editor, options);
|
||||
openEditorPromise = (async () => {
|
||||
try {
|
||||
const result = await this.editorControl.openEditor(editor, options);
|
||||
|
||||
// Editor change event
|
||||
if (result.editorChanged) {
|
||||
this._onDidGroupChange.fire({ kind: GroupChangeKind.EDITOR_ACTIVE, editor });
|
||||
// Editor change event
|
||||
if (result.editorChanged) {
|
||||
this._onDidGroupChange.fire({ kind: GroupChangeKind.EDITOR_ACTIVE, editor });
|
||||
}
|
||||
|
||||
return result.control;
|
||||
} catch (error) {
|
||||
|
||||
// Handle errors but do not bubble them up
|
||||
this.doHandleOpenEditorError(error, editor, options);
|
||||
|
||||
return null; // error: return NULL as result to signal this
|
||||
}
|
||||
|
||||
openEditor = result.control;
|
||||
} catch (error) {
|
||||
|
||||
// Handle errors but do not bubble them up
|
||||
this.doHandleOpenEditorError(error, editor, options);
|
||||
}
|
||||
})();
|
||||
} else {
|
||||
openEditor = null; // inactive: return NULL as result to signal this
|
||||
openEditorPromise = Promise.resolve(null); // inactive: return NULL as result to signal this
|
||||
}
|
||||
|
||||
// Show in title control after editor control because some actions depend on it
|
||||
this.titleAreaControl.openEditor(editor);
|
||||
|
||||
return openEditor;
|
||||
return openEditorPromise;
|
||||
}
|
||||
|
||||
private doHandleOpenEditorError(error: Error, editor: EditorInput, options?: EditorOptions): void {
|
||||
@@ -892,7 +912,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
||||
|
||||
// Use the first editor as active editor
|
||||
const { editor, options } = editors.shift()!;
|
||||
let firstEditor = await this.openEditor(editor, options);
|
||||
let firstOpenedEditor = await this.openEditor(editor, options);
|
||||
|
||||
// Open the other ones inactive
|
||||
const startingIndex = this.getIndexOfEditor(editor) + 1;
|
||||
@@ -903,12 +923,12 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
||||
adjustedEditorOptions.index = startingIndex + index;
|
||||
|
||||
const openedEditor = await this.openEditor(editor, adjustedEditorOptions);
|
||||
if (!firstEditor) {
|
||||
firstEditor = openedEditor; // only take if the first editor opening failed
|
||||
if (!firstOpenedEditor) {
|
||||
firstOpenedEditor = openedEditor; // only take if the first editor opening failed
|
||||
}
|
||||
}));
|
||||
|
||||
return firstEditor;
|
||||
return firstOpenedEditor;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
@@ -1108,7 +1128,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
||||
|
||||
private async handleDirty(editors: EditorInput[]): Promise<boolean /* veto */> {
|
||||
if (!editors.length) {
|
||||
return Promise.resolve(false); // no veto
|
||||
return false; // no veto
|
||||
}
|
||||
|
||||
const editor = editors.shift()!;
|
||||
@@ -1141,7 +1161,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
||||
this.accessor.groups.some(groupView => groupView !== this && groupView.group.contains(editor, true /* support side by side */)) || // editor is opened in other group
|
||||
editor instanceof SideBySideEditorInput && this.isOpened(editor.master) // side by side editor master is still opened
|
||||
) {
|
||||
return Promise.resolve(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Switch to editor that we want to handle and confirm to save/revert
|
||||
|
||||
@@ -31,6 +31,7 @@ import { IView, orthogonal, LayoutPriority } from 'vs/base/browser/ui/grid/gridv
|
||||
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';
|
||||
|
||||
interface IEditorPartUIState {
|
||||
serializedGrid: ISerializedGrid;
|
||||
@@ -118,8 +119,8 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro
|
||||
|
||||
private _preferredSize: Dimension | undefined;
|
||||
|
||||
private workspaceMemento: object;
|
||||
private globalMemento: object;
|
||||
private readonly workspaceMemento: MementoObject;
|
||||
private readonly globalMemento: MementoObject;
|
||||
|
||||
private _partOptions: IEditorPartOptions;
|
||||
|
||||
|
||||
@@ -5,17 +5,16 @@
|
||||
|
||||
import 'vs/css!./media/editorstatus';
|
||||
import * as nls from 'vs/nls';
|
||||
import { $, append, runAtThisOrScheduleAtNextAnimationFrame } from 'vs/base/browser/dom';
|
||||
import { runAtThisOrScheduleAtNextAnimationFrame } from 'vs/base/browser/dom';
|
||||
import { format } from 'vs/base/common/strings';
|
||||
import { extname, basename } from 'vs/base/common/resources';
|
||||
import { areFunctions, withNullAsUndefined } from 'vs/base/common/types';
|
||||
import { areFunctions, withNullAsUndefined, withUndefinedAsNull } from 'vs/base/common/types';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IStatusbarItem } from 'vs/workbench/browser/parts/statusbar/statusbar';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { Language } from 'vs/base/common/platform';
|
||||
import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
|
||||
import { IFileEditorInput, EncodingMode, IEncodingSupport, toResource, SideBySideEditorInput, IEditor as IBaseEditor, IEditorInput, SideBySideEditor, IModeSupport } from 'vs/workbench/common/editor';
|
||||
import { IDisposable, combinedDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService';
|
||||
import { IEditorAction } from 'vs/editor/common/editorCommon';
|
||||
import { EndOfLineSequence } from 'vs/editor/common/model';
|
||||
@@ -25,7 +24,6 @@ import { IndentUsingSpaces, IndentUsingTabs, DetectIndentation, IndentationToSpa
|
||||
import { BaseBinaryResourceEditor } from 'vs/workbench/browser/parts/editor/binaryEditor';
|
||||
import { BinaryResourceDiffEditor } from 'vs/workbench/browser/parts/editor/binaryDiffEditor';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen';
|
||||
import { IFileService, FILES_ASSOCIATIONS_CONFIG } from 'vs/platform/files/common/files';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IModeService, ILanguageSelection } from 'vs/editor/common/services/modeService';
|
||||
@@ -33,7 +31,7 @@ import { IModelService } from 'vs/editor/common/services/modelService';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { Selection } from 'vs/editor/common/core/selection';
|
||||
import { TabFocus } from 'vs/editor/common/config/commonEditorConfig';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { ICommandService, CommandsRegistry } from 'vs/platform/commands/common/commands';
|
||||
import { IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { ITextFileService, SUPPORTED_ENCODINGS } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { ICursorPositionChangedEvent } from 'vs/editor/common/controller/cursorEvents';
|
||||
@@ -50,6 +48,8 @@ import { timeout } from 'vs/base/common/async';
|
||||
import { INotificationHandle, INotificationService, Severity } from 'vs/platform/notification/common/notification';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility';
|
||||
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
|
||||
import { IStatusbarEntryAccessor, IStatusbarService, StatusbarAlignment, IStatusbarEntry } from 'vs/platform/statusbar/common/statusbar';
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
import { QueryEditorService } from 'sql/workbench/services/queryEditor/browser/queryEditorService';
|
||||
@@ -277,148 +277,286 @@ const nlsMultiSelectionRange = nls.localize('multiSelectionRange', "{0} selectio
|
||||
const nlsMultiSelection = nls.localize('multiSelection', "{0} selections");
|
||||
const nlsEOLLF = nls.localize('endOfLineLineFeed', "LF");
|
||||
const nlsEOLCRLF = nls.localize('endOfLineCarriageReturnLineFeed', "CRLF");
|
||||
const nlsTabFocusMode = nls.localize('tabFocusModeEnabled', "Tab Moves Focus");
|
||||
const nlsScreenReaderDetected = nls.localize('screenReaderDetected', "Screen Reader Optimized");
|
||||
const nlsScreenReaderDetectedTitle = nls.localize('screenReaderDetectedExtra', "If you are not using a Screen Reader, please change the setting `editor.accessibilitySupport` to \"off\".");
|
||||
|
||||
class StatusBarItem {
|
||||
private _showing = true;
|
||||
export class EditorStatus extends Disposable implements IWorkbenchContribution {
|
||||
private tabFocusModeElement: IStatusbarEntryAccessor | null = null;
|
||||
private screenRedearModeElement: IStatusbarEntryAccessor | null = null;
|
||||
private indentationElement: IStatusbarEntryAccessor | null = null;
|
||||
private selectionElement: IStatusbarEntryAccessor | null = null;
|
||||
private encodingElement: IStatusbarEntryAccessor | null = null;
|
||||
private eolElement: IStatusbarEntryAccessor | null = null;
|
||||
private modeElement: IStatusbarEntryAccessor | null = null;
|
||||
private metadataElement: IStatusbarEntryAccessor | null = null;
|
||||
|
||||
constructor(
|
||||
private readonly element: HTMLElement,
|
||||
title: string,
|
||||
) {
|
||||
this.setVisible(false);
|
||||
this.element.title = title;
|
||||
}
|
||||
|
||||
set textContent(value: string) {
|
||||
this.element.textContent = value;
|
||||
}
|
||||
|
||||
set onclick(value: () => void) {
|
||||
this.element.onclick = value;
|
||||
}
|
||||
|
||||
setVisible(shouldShow: boolean): void {
|
||||
if (shouldShow !== this._showing) {
|
||||
this._showing = shouldShow;
|
||||
this.element.style.display = shouldShow ? '' : 'none';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class EditorStatus implements IStatusbarItem {
|
||||
private state: State;
|
||||
private element: HTMLElement;
|
||||
private tabFocusModeElement: StatusBarItem;
|
||||
private screenRedearModeElement: StatusBarItem;
|
||||
private indentationElement: StatusBarItem;
|
||||
private selectionElement: StatusBarItem;
|
||||
private encodingElement: StatusBarItem;
|
||||
private eolElement: StatusBarItem;
|
||||
private modeElement: StatusBarItem;
|
||||
private metadataElement: StatusBarItem;
|
||||
private toDispose: IDisposable[];
|
||||
private activeEditorListeners: IDisposable[];
|
||||
private delayedRender: IDisposable | null;
|
||||
private toRender: StateChange | null;
|
||||
private screenReaderNotification: INotificationHandle | null;
|
||||
private readonly state = new State();
|
||||
private readonly activeEditorListeners: IDisposable[] = [];
|
||||
private delayedRender: IDisposable | null = null;
|
||||
private toRender: StateChange | null = null;
|
||||
private screenReaderNotification: INotificationHandle | null = null;
|
||||
private promptedScreenReader: boolean = false;
|
||||
|
||||
constructor(
|
||||
@IEditorService private readonly editorService: IEditorService,
|
||||
@IQuickOpenService private readonly quickOpenService: IQuickOpenService,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
@IQuickInputService private readonly quickInputService: IQuickInputService,
|
||||
@IUntitledEditorService private readonly untitledEditorService: IUntitledEditorService,
|
||||
@IModeService private readonly modeService: IModeService,
|
||||
@ITextFileService private readonly textFileService: ITextFileService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
@INotificationService private readonly notificationService: INotificationService,
|
||||
@IAccessibilityService private readonly accessibilityService: IAccessibilityService
|
||||
@IAccessibilityService private readonly accessibilityService: IAccessibilityService,
|
||||
@IStatusbarService private readonly statusbarService: IStatusbarService
|
||||
) {
|
||||
this.toDispose = [];
|
||||
this.activeEditorListeners = [];
|
||||
this.state = new State();
|
||||
super();
|
||||
|
||||
this.registerCommands();
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
render(container: HTMLElement): IDisposable {
|
||||
this.element = append(container, $('.editor-statusbar-item'));
|
||||
private registerListeners(): void {
|
||||
this._register(this.editorService.onDidActiveEditorChange(() => this.updateStatusBar()));
|
||||
this._register(this.untitledEditorService.onDidChangeEncoding(r => this.onResourceEncodingChange(r)));
|
||||
this._register(this.textFileService.models.onModelEncodingChanged(e => this.onResourceEncodingChange((e.resource))));
|
||||
this._register(TabFocus.onDidChangeTabFocus(e => this.onTabFocusModeChange()));
|
||||
}
|
||||
|
||||
this.tabFocusModeElement = new StatusBarItem(
|
||||
append(this.element, $('a.editor-status-tabfocusmode.status-bar-info')),
|
||||
nls.localize('disableTabMode', "Disable Accessibility Mode"));
|
||||
this.tabFocusModeElement.onclick = () => this.onTabFocusModeClick();
|
||||
this.tabFocusModeElement.textContent = nlsTabFocusMode;
|
||||
private registerCommands(): void {
|
||||
CommandsRegistry.registerCommand({ id: 'showEditorScreenReaderNotification', handler: () => this.showScreenReaderNotification() });
|
||||
CommandsRegistry.registerCommand({ id: 'changeEditorIndentation', handler: () => this.showIndentationPicker() });
|
||||
}
|
||||
|
||||
this.screenRedearModeElement = new StatusBarItem(
|
||||
append(this.element, $('a.editor-status-screenreadermode.status-bar-info')),
|
||||
nlsScreenReaderDetectedTitle);
|
||||
this.screenRedearModeElement.textContent = nlsScreenReaderDetected;
|
||||
this.screenRedearModeElement.onclick = () => this.onScreenReaderModeClick();
|
||||
private showScreenReaderNotification(): void {
|
||||
if (!this.screenReaderNotification) {
|
||||
this.screenReaderNotification = this.notificationService.prompt(
|
||||
Severity.Info,
|
||||
nls.localize('screenReaderDetectedExplanation.question', "Are you using a screen reader to operate Azure Data Studio? (Certain features like folding, minimap or word wrap are disabled when using a screen reader)"),
|
||||
[{
|
||||
label: nls.localize('screenReaderDetectedExplanation.answerYes', "Yes"),
|
||||
run: () => {
|
||||
this.configurationService.updateValue('editor.accessibilitySupport', 'on', ConfigurationTarget.USER);
|
||||
}
|
||||
}, {
|
||||
label: nls.localize('screenReaderDetectedExplanation.answerNo', "No"),
|
||||
run: () => {
|
||||
this.configurationService.updateValue('editor.accessibilitySupport', 'off', ConfigurationTarget.USER);
|
||||
}
|
||||
}],
|
||||
{ sticky: true }
|
||||
);
|
||||
|
||||
this.selectionElement = new StatusBarItem(
|
||||
append(this.element, $('a.editor-status-selection')),
|
||||
nls.localize('gotoLine', "Go to Line"));
|
||||
this.selectionElement.onclick = () => this.onSelectionClick();
|
||||
Event.once(this.screenReaderNotification.onDidClose)(() => this.screenReaderNotification = null);
|
||||
}
|
||||
}
|
||||
|
||||
this.indentationElement = new StatusBarItem(
|
||||
append(this.element, $('a.editor-status-indentation')),
|
||||
nls.localize('selectIndentation', "Select Indentation"));
|
||||
this.indentationElement.onclick = () => this.onIndentationClick();
|
||||
private async showIndentationPicker(): Promise<unknown> {
|
||||
const activeTextEditorWidget = getCodeEditor(this.editorService.activeTextEditorWidget);
|
||||
if (!activeTextEditorWidget) {
|
||||
return this.quickInputService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]);
|
||||
}
|
||||
|
||||
this.encodingElement = new StatusBarItem(
|
||||
append(this.element, $('a.editor-status-encoding')),
|
||||
nls.localize('selectEncoding', "Select Encoding"));
|
||||
this.encodingElement.onclick = () => this.onEncodingClick();
|
||||
if (!isWritableCodeEditor(activeTextEditorWidget)) {
|
||||
return this.quickInputService.pick([{ label: nls.localize('noWritableCodeEditor', "The active code editor is read-only.") }]);
|
||||
}
|
||||
|
||||
this.eolElement = new StatusBarItem(
|
||||
append(this.element, $('a.editor-status-eol')),
|
||||
nls.localize('selectEOL', "Select End of Line Sequence"));
|
||||
this.eolElement.onclick = () => this.onEOLClick();
|
||||
|
||||
this.modeElement = new StatusBarItem(
|
||||
append(this.element, $('a.editor-status-mode')),
|
||||
nls.localize('selectLanguageMode', "Select Language Mode"));
|
||||
this.modeElement.onclick = () => this.onModeClick();
|
||||
|
||||
this.metadataElement = new StatusBarItem(
|
||||
append(this.element, $('span.editor-status-metadata')),
|
||||
nls.localize('fileInfo', "File Information"));
|
||||
|
||||
this.delayedRender = null;
|
||||
this.toRender = null;
|
||||
|
||||
this.toDispose.push(
|
||||
toDisposable(() => {
|
||||
if (this.delayedRender) {
|
||||
this.delayedRender.dispose();
|
||||
this.delayedRender = null;
|
||||
const picks: QuickPickInput<IQuickPickItem & { run(): void }>[] = [
|
||||
activeTextEditorWidget.getAction(IndentUsingSpaces.ID),
|
||||
activeTextEditorWidget.getAction(IndentUsingTabs.ID),
|
||||
activeTextEditorWidget.getAction(DetectIndentation.ID),
|
||||
activeTextEditorWidget.getAction(IndentationToSpacesAction.ID),
|
||||
activeTextEditorWidget.getAction(IndentationToTabsAction.ID),
|
||||
activeTextEditorWidget.getAction(TrimTrailingWhitespaceAction.ID)
|
||||
].map((a: IEditorAction) => {
|
||||
return {
|
||||
id: a.id,
|
||||
label: a.label,
|
||||
detail: Language.isDefaultVariant() ? undefined : a.alias,
|
||||
run: () => {
|
||||
activeTextEditorWidget.focus();
|
||||
a.run();
|
||||
}
|
||||
}),
|
||||
this.editorService.onDidActiveEditorChange(() => this.updateStatusBar()),
|
||||
this.untitledEditorService.onDidChangeEncoding(r => this.onResourceEncodingChange(r)),
|
||||
this.textFileService.models.onModelEncodingChanged(e => this.onResourceEncodingChange(e.resource)),
|
||||
TabFocus.onDidChangeTabFocus(e => this.onTabFocusModeChange()),
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
return combinedDisposable(this.toDispose);
|
||||
picks.splice(3, 0, { type: 'separator', label: nls.localize('indentConvert', "convert file") });
|
||||
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();
|
||||
}
|
||||
|
||||
private updateTabFocusModeElement(visible: boolean): void {
|
||||
if (visible) {
|
||||
if (!this.tabFocusModeElement) {
|
||||
this.tabFocusModeElement = this.statusbarService.addEntry({
|
||||
text: nls.localize('tabFocusModeEnabled', "Tab Moves Focus"),
|
||||
tooltip: nls.localize('disableTabMode', "Disable Accessibility Mode"),
|
||||
command: 'editor.action.toggleTabFocusMode'
|
||||
}, 'status.editor.tabFocusMode', nls.localize('status.editor.tabFocusMode', "Accessibility Mode"), StatusbarAlignment.RIGHT, 100.7);
|
||||
}
|
||||
} else {
|
||||
if (this.tabFocusModeElement) {
|
||||
this.tabFocusModeElement.dispose();
|
||||
this.tabFocusModeElement = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private updateScreenReaderModeElement(visible: boolean): void {
|
||||
if (visible) {
|
||||
if (!this.screenRedearModeElement) {
|
||||
this.screenRedearModeElement = this.statusbarService.addEntry({
|
||||
text: nls.localize('screenReaderDetected', "Screen Reader Optimized"),
|
||||
tooltip: nls.localize('screenReaderDetectedExtra', "If you are not using a Screen Reader, please change the setting `editor.accessibilitySupport` to \"off\"."),
|
||||
command: 'showEditorScreenReaderNotification'
|
||||
}, 'status.editor.screenReaderMode', nls.localize('status.editor.screenReaderMode', "Screen Reader Mode"), StatusbarAlignment.RIGHT, 100.6);
|
||||
}
|
||||
} else {
|
||||
if (this.screenRedearModeElement) {
|
||||
this.screenRedearModeElement.dispose();
|
||||
this.screenRedearModeElement = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private updateSelectionElement(text: string | undefined): void {
|
||||
if (!text) {
|
||||
if (this.selectionElement) {
|
||||
dispose(this.selectionElement);
|
||||
this.selectionElement = null;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const props = {
|
||||
text,
|
||||
tooltip: nls.localize('gotoLine', "Go to Line"),
|
||||
command: 'workbench.action.gotoLine'
|
||||
};
|
||||
|
||||
this.selectionElement = this.updateElement(this.selectionElement, props, 'status.editor.selection', nls.localize('status.editor.selection', "Editor Selection"), StatusbarAlignment.RIGHT, 100.5);
|
||||
}
|
||||
|
||||
private updateIndentationElement(text: string | undefined): void {
|
||||
if (!text) {
|
||||
if (this.indentationElement) {
|
||||
dispose(this.indentationElement);
|
||||
this.indentationElement = null;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const props = {
|
||||
text,
|
||||
tooltip: nls.localize('selectIndentation', "Select Indentation"),
|
||||
command: 'changeEditorIndentation'
|
||||
};
|
||||
|
||||
this.indentationElement = this.updateElement(this.indentationElement, props, 'status.editor.indentation', nls.localize('status.editor.indentation', "Editor Indentation"), StatusbarAlignment.RIGHT, 100.4);
|
||||
}
|
||||
|
||||
private updateEncodingElement(text: string | undefined): void {
|
||||
if (!text) {
|
||||
if (this.encodingElement) {
|
||||
dispose(this.encodingElement);
|
||||
this.encodingElement = null;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const props = {
|
||||
text,
|
||||
tooltip: nls.localize('selectEncoding', "Select Encoding"),
|
||||
command: 'workbench.action.editor.changeEncoding'
|
||||
};
|
||||
|
||||
this.encodingElement = this.updateElement(this.encodingElement, props, 'status.editor.encoding', nls.localize('status.editor.encoding', "Editor Encoding"), StatusbarAlignment.RIGHT, 100.3);
|
||||
}
|
||||
|
||||
private updateEOLElement(text: string | undefined): void {
|
||||
if (!text) {
|
||||
if (this.eolElement) {
|
||||
dispose(this.eolElement);
|
||||
this.eolElement = null;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const props = {
|
||||
text,
|
||||
tooltip: nls.localize('selectEOL', "Select End of Line Sequence"),
|
||||
command: 'workbench.action.editor.changeEOL'
|
||||
};
|
||||
|
||||
this.eolElement = this.updateElement(this.eolElement, props, 'status.editor.eol', nls.localize('status.editor.eol', "Editor End of Line"), StatusbarAlignment.RIGHT, 100.2);
|
||||
}
|
||||
|
||||
private updateModeElement(text: string | undefined): void {
|
||||
if (!text) {
|
||||
if (this.modeElement) {
|
||||
dispose(this.modeElement);
|
||||
this.modeElement = null;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const props = {
|
||||
text,
|
||||
tooltip: nls.localize('selectLanguageMode', "Select Language Mode"),
|
||||
command: 'workbench.action.editor.changeLanguageMode'
|
||||
};
|
||||
|
||||
this.modeElement = this.updateElement(this.modeElement, props, 'status.editor.mode', nls.localize('status.editor.mode', "Editor Language"), StatusbarAlignment.RIGHT, 100.1);
|
||||
|
||||
}
|
||||
|
||||
private updateMetadataElement(text: string | undefined): void {
|
||||
if (!text) {
|
||||
if (this.metadataElement) {
|
||||
dispose(this.metadataElement);
|
||||
this.metadataElement = null;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const props = {
|
||||
text,
|
||||
tooltip: nls.localize('fileInfo', "File Information")
|
||||
};
|
||||
|
||||
this.metadataElement = this.updateElement(this.metadataElement, props, 'status.editor.info', nls.localize('status.editor.info', "File Information"), StatusbarAlignment.RIGHT, 100);
|
||||
|
||||
}
|
||||
|
||||
private updateElement(element: IStatusbarEntryAccessor | null, props: IStatusbarEntry, id: string, name: string, alignment: StatusbarAlignment, priority: number): IStatusbarEntryAccessor | null {
|
||||
if (!element) {
|
||||
element = this.statusbarService.addEntry(props, id, name, alignment, priority);
|
||||
} else {
|
||||
element.update(props);
|
||||
}
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
private updateState(update: StateDelta): void {
|
||||
const changed = this.state.update(update);
|
||||
if (!changed.hasChanges()) {
|
||||
// Nothing really changed
|
||||
return;
|
||||
return; // Nothing really changed
|
||||
}
|
||||
|
||||
if (!this.toRender) {
|
||||
this.toRender = changed;
|
||||
|
||||
this.delayedRender = runAtThisOrScheduleAtNextAnimationFrame(() => {
|
||||
this.delayedRender = null;
|
||||
const toRender = this.toRender;
|
||||
this.toRender = null;
|
||||
if (toRender) {
|
||||
this._renderNow(toRender);
|
||||
this.doRenderNow(toRender);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
@@ -426,68 +564,15 @@ export class EditorStatus implements IStatusbarItem {
|
||||
}
|
||||
}
|
||||
|
||||
private _renderNow(changed: StateChange): void {
|
||||
if (changed.tabFocusMode) {
|
||||
this.tabFocusModeElement.setVisible(!!this.state.tabFocusMode);
|
||||
}
|
||||
|
||||
if (changed.screenReaderMode) {
|
||||
this.screenRedearModeElement.setVisible(!!this.state.screenReaderMode);
|
||||
}
|
||||
|
||||
if (changed.indentation) {
|
||||
if (this.state.indentation) {
|
||||
this.indentationElement.textContent = this.state.indentation;
|
||||
this.indentationElement.setVisible(true);
|
||||
} else {
|
||||
this.indentationElement.setVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (changed.selectionStatus) {
|
||||
if (this.state.selectionStatus && !this.state.screenReaderMode) {
|
||||
this.selectionElement.textContent = this.state.selectionStatus;
|
||||
this.selectionElement.setVisible(true);
|
||||
} else {
|
||||
this.selectionElement.setVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (changed.encoding) {
|
||||
if (this.state.encoding) {
|
||||
this.encodingElement.textContent = this.state.encoding;
|
||||
this.encodingElement.setVisible(true);
|
||||
} else {
|
||||
this.encodingElement.setVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (changed.EOL) {
|
||||
if (this.state.EOL) {
|
||||
this.eolElement.textContent = this.state.EOL === '\r\n' ? nlsEOLCRLF : nlsEOLLF;
|
||||
this.eolElement.setVisible(true);
|
||||
} else {
|
||||
this.eolElement.setVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (changed.mode) {
|
||||
if (this.state.mode) {
|
||||
this.modeElement.textContent = this.state.mode;
|
||||
this.modeElement.setVisible(true);
|
||||
} else {
|
||||
this.modeElement.setVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (changed.metadata) {
|
||||
if (this.state.metadata) {
|
||||
this.metadataElement.textContent = this.state.metadata;
|
||||
this.metadataElement.setVisible(true);
|
||||
} else {
|
||||
this.metadataElement.setVisible(false);
|
||||
}
|
||||
}
|
||||
private doRenderNow(changed: StateChange): void {
|
||||
this.updateTabFocusModeElement(!!this.state.tabFocusMode);
|
||||
this.updateScreenReaderModeElement(!!this.state.screenReaderMode);
|
||||
this.updateIndentationElement(this.state.indentation);
|
||||
this.updateSelectionElement(this.state.selectionStatus && !this.state.screenReaderMode ? this.state.selectionStatus : undefined);
|
||||
this.updateEncodingElement(this.state.encoding);
|
||||
this.updateEOLElement(this.state.EOL ? this.state.EOL === '\r\n' ? nlsEOLCRLF : nlsEOLLF : undefined);
|
||||
this.updateModeElement(this.state.mode);
|
||||
this.updateMetadataElement(this.state.metadata);
|
||||
}
|
||||
|
||||
private getSelectionLabel(info: IEditorSelectionStatus): string | undefined {
|
||||
@@ -514,66 +599,6 @@ export class EditorStatus implements IStatusbarItem {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private onModeClick(): void {
|
||||
const action = this.instantiationService.createInstance(ChangeModeAction, ChangeModeAction.ID, ChangeModeAction.LABEL);
|
||||
|
||||
action.run();
|
||||
action.dispose();
|
||||
}
|
||||
|
||||
private onIndentationClick(): void {
|
||||
const action = this.instantiationService.createInstance(ChangeIndentationAction, ChangeIndentationAction.ID, ChangeIndentationAction.LABEL);
|
||||
action.run();
|
||||
action.dispose();
|
||||
}
|
||||
|
||||
private onScreenReaderModeClick(): void {
|
||||
if (!this.screenReaderNotification) {
|
||||
this.screenReaderNotification = this.notificationService.prompt(
|
||||
Severity.Info,
|
||||
nls.localize('screenReaderDetectedExplanation.question', "Are you using a screen reader to operate Azure Data Studio? (Certain features like folding, minimap or word wrap are disabled when using a screen reader)"),
|
||||
[{
|
||||
label: nls.localize('screenReaderDetectedExplanation.answerYes', "Yes"),
|
||||
run: () => {
|
||||
this.configurationService.updateValue('editor.accessibilitySupport', 'on', ConfigurationTarget.USER);
|
||||
}
|
||||
}, {
|
||||
label: nls.localize('screenReaderDetectedExplanation.answerNo', "No"),
|
||||
run: () => {
|
||||
this.configurationService.updateValue('editor.accessibilitySupport', 'off', ConfigurationTarget.USER);
|
||||
}
|
||||
}],
|
||||
{ sticky: true }
|
||||
);
|
||||
|
||||
Event.once(this.screenReaderNotification.onDidClose)(() => {
|
||||
this.screenReaderNotification = null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private onSelectionClick(): void {
|
||||
this.quickOpenService.show(':'); // "Go to line"
|
||||
}
|
||||
|
||||
private onEOLClick(): void {
|
||||
const action = this.instantiationService.createInstance(ChangeEOLAction, ChangeEOLAction.ID, ChangeEOLAction.LABEL);
|
||||
|
||||
action.run();
|
||||
action.dispose();
|
||||
}
|
||||
|
||||
private onEncodingClick(): void {
|
||||
const action = this.instantiationService.createInstance(ChangeEncodingAction, ChangeEncodingAction.ID, ChangeEncodingAction.LABEL);
|
||||
|
||||
action.run();
|
||||
action.dispose();
|
||||
}
|
||||
|
||||
private onTabFocusModeClick(): void {
|
||||
TabFocus.setTabFocusMode(false);
|
||||
}
|
||||
|
||||
private updateStatusBar(): void {
|
||||
const activeControl = this.editorService.activeControl;
|
||||
const activeCodeEditor = activeControl ? withNullAsUndefined(getCodeEditor(activeControl.getControl())) : undefined;
|
||||
@@ -667,7 +692,6 @@ export class EditorStatus implements IStatusbarItem {
|
||||
if (editorWidget) {
|
||||
const textModel = editorWidget.getModel();
|
||||
if (textModel) {
|
||||
// Compute mode
|
||||
const modeId = textModel.getLanguageIdentifier().language;
|
||||
info = { mode: this.modeService.getLanguageName(modeId) || undefined };
|
||||
}
|
||||
@@ -704,8 +728,6 @@ export class EditorStatus implements IStatusbarItem {
|
||||
this.updateState(update);
|
||||
}
|
||||
|
||||
private promptedScreenReader: boolean = false;
|
||||
|
||||
private onScreenReaderModeChange(editorWidget: ICodeEditor | undefined): void {
|
||||
let screenReaderMode = false;
|
||||
|
||||
@@ -715,12 +737,9 @@ export class EditorStatus implements IStatusbarItem {
|
||||
if (screenReaderDetected) {
|
||||
const screenReaderConfiguration = this.configurationService.getValue<IEditorOptions>('editor').accessibilitySupport;
|
||||
if (screenReaderConfiguration === 'auto') {
|
||||
// show explanation
|
||||
if (!this.promptedScreenReader) {
|
||||
this.promptedScreenReader = true;
|
||||
setTimeout(() => {
|
||||
this.onScreenReaderModeClick();
|
||||
}, 100);
|
||||
setTimeout(() => this.showScreenReaderNotification(), 100);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -736,7 +755,7 @@ export class EditorStatus implements IStatusbarItem {
|
||||
}
|
||||
|
||||
private onSelectionChange(editorWidget: ICodeEditor | undefined): void {
|
||||
const info: IEditorSelectionStatus = {};
|
||||
const info: IEditorSelectionStatus = Object.create(null);
|
||||
|
||||
// We only support text based editors
|
||||
if (editorWidget) {
|
||||
@@ -830,6 +849,15 @@ export class EditorStatus implements IStatusbarItem {
|
||||
|
||||
return !!activeControl && activeControl === control;
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
super.dispose();
|
||||
|
||||
if (this.delayedRender) {
|
||||
this.delayedRender.dispose();
|
||||
this.delayedRender = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function isWritableCodeEditor(codeEditor: ICodeEditor | undefined): boolean {
|
||||
@@ -899,7 +927,7 @@ export class ChangeModeAction extends Action {
|
||||
|
||||
// Compute mode
|
||||
let currentModeId: string | undefined;
|
||||
let modeId: string;
|
||||
let modeId: string | undefined;
|
||||
if (textModel) {
|
||||
modeId = textModel.getLanguageIdentifier().language;
|
||||
currentModeId = this.modeService.getLanguageName(modeId) || undefined;
|
||||
@@ -939,9 +967,9 @@ export class ChangeModeAction extends Action {
|
||||
}
|
||||
|
||||
// Offer action to configure via settings
|
||||
let configureModeAssociations: IQuickPickItem;
|
||||
let configureModeSettings: IQuickPickItem;
|
||||
let galleryAction: Action;
|
||||
let configureModeAssociations: IQuickPickItem | undefined;
|
||||
let configureModeSettings: IQuickPickItem | undefined;
|
||||
let galleryAction: Action | undefined;
|
||||
if (hasLanguageSupport && resource) {
|
||||
const ext = extname(resource) || basename(resource);
|
||||
|
||||
@@ -965,61 +993,60 @@ export class ChangeModeAction extends Action {
|
||||
picks.unshift(autoDetectMode);
|
||||
}
|
||||
|
||||
return this.quickInputService.pick(picks, { placeHolder: nls.localize('pickLanguage', "Select Language Mode"), matchOnDescription: true }).then(pick => {
|
||||
if (!pick) {
|
||||
return;
|
||||
const pick = await this.quickInputService.pick(picks, { placeHolder: nls.localize('pickLanguage', "Select Language Mode"), matchOnDescription: true });
|
||||
if (!pick) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (pick === galleryAction) {
|
||||
galleryAction.run();
|
||||
return;
|
||||
}
|
||||
|
||||
// User decided to permanently configure associations, return right after
|
||||
if (pick === configureModeAssociations) {
|
||||
if (resource) {
|
||||
this.configureFileAssociation(resource);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (pick === galleryAction) {
|
||||
galleryAction.run();
|
||||
return;
|
||||
}
|
||||
// User decided to configure settings for current language
|
||||
if (pick === configureModeSettings) {
|
||||
this.preferencesService.configureSettingsForLanguage(withUndefinedAsNull(modeId));
|
||||
return;
|
||||
}
|
||||
|
||||
// User decided to permanently configure associations, return right after
|
||||
if (pick === configureModeAssociations) {
|
||||
if (resource) {
|
||||
this.configureFileAssociation(resource);
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Change mode for active editor
|
||||
const activeEditor = this.editorService.activeControl; // {{SQL CARBON EDIT}} @anthonydresser change to activeControl from active editor
|
||||
if (activeEditor) {
|
||||
const modeSupport = toEditorWithModeSupport(activeEditor.input); // {{SQL CARBON EDIT}} @anthonydresser reference input rather than activeeditor directly
|
||||
if (modeSupport) {
|
||||
|
||||
// User decided to configure settings for current language
|
||||
if (pick === configureModeSettings) {
|
||||
this.preferencesService.configureSettingsForLanguage(modeId);
|
||||
return;
|
||||
}
|
||||
|
||||
// Change mode for active editor
|
||||
const activeEditor = this.editorService.activeControl; // {{SQL CARBON EDIT}} @anthonydresser change to activeControl from active editor
|
||||
if (activeEditor) {
|
||||
const modeSupport = toEditorWithModeSupport(activeEditor.input); // {{SQL CARBON EDIT}} @anthonydresser reference input rather than activeeditor directly
|
||||
if (modeSupport) {
|
||||
|
||||
// Find mode
|
||||
let languageSelection: ILanguageSelection | undefined;
|
||||
if (pick === autoDetectMode) {
|
||||
if (textModel) {
|
||||
const resource = toResource(activeEditor.input, { supportSideBySide: SideBySideEditor.MASTER }); // {{SQL CARBON EDIT}} @anthonydresser reference input rather than activeeditor directly
|
||||
if (resource) {
|
||||
languageSelection = this.modeService.createByFilepathOrFirstLine(resource.fsPath, textModel.getLineContent(1));
|
||||
}
|
||||
// Find mode
|
||||
let languageSelection: ILanguageSelection | undefined;
|
||||
if (pick === autoDetectMode) {
|
||||
if (textModel) {
|
||||
const resource = toResource(activeEditor.input, { supportSideBySide: SideBySideEditor.MASTER }); // {{SQL CARBON EDIT}} @anthonydresser reference input rather than activeeditor directly
|
||||
if (resource) {
|
||||
languageSelection = this.modeService.createByFilepathOrFirstLine(resource.fsPath, textModel.getLineContent(1));
|
||||
}
|
||||
} else {
|
||||
languageSelection = this.modeService.createByLanguageName(pick.label);
|
||||
}
|
||||
} else {
|
||||
languageSelection = this.modeService.createByLanguageName(pick.label);
|
||||
}
|
||||
|
||||
// {{SQL CARBON EDIT}} @anthonydresser preform a check before we actuall set the mode
|
||||
// Change mode
|
||||
if (typeof languageSelection !== 'undefined') {
|
||||
QueryEditorService.sqlLanguageModeCheck(textModel, languageSelection, activeEditor).then(newTextModel => {
|
||||
if (newTextModel) {
|
||||
modeSupport.setMode(languageSelection.languageIdentifier.language);
|
||||
}
|
||||
});
|
||||
}
|
||||
// {{SQL CARBON EDIT}} @anthonydresser preform a check before we actuall set the mode
|
||||
// Change mode
|
||||
if (typeof languageSelection !== 'undefined') {
|
||||
QueryEditorService.sqlLanguageModeCheck(textModel, languageSelection, activeEditor).then(newTextModel => {
|
||||
if (newTextModel) {
|
||||
modeSupport.setMode(languageSelection.languageIdentifier.language);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private configureFileAssociation(resource: URI): void {
|
||||
@@ -1070,57 +1097,6 @@ export interface IChangeEOLEntry extends IQuickPickItem {
|
||||
eol: EndOfLineSequence;
|
||||
}
|
||||
|
||||
class ChangeIndentationAction extends Action {
|
||||
|
||||
static readonly ID = 'workbench.action.editor.changeIndentation';
|
||||
static readonly LABEL = nls.localize('changeIndentation', "Change Indentation");
|
||||
|
||||
constructor(
|
||||
actionId: string,
|
||||
actionLabel: string,
|
||||
@IEditorService private readonly editorService: IEditorService,
|
||||
@IQuickInputService private readonly quickInputService: IQuickInputService
|
||||
) {
|
||||
super(actionId, actionLabel);
|
||||
}
|
||||
|
||||
async run(): Promise<any> {
|
||||
const activeTextEditorWidget = getCodeEditor(this.editorService.activeTextEditorWidget);
|
||||
if (!activeTextEditorWidget) {
|
||||
return this.quickInputService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]);
|
||||
}
|
||||
|
||||
if (!isWritableCodeEditor(activeTextEditorWidget)) {
|
||||
return this.quickInputService.pick([{ label: nls.localize('noWritableCodeEditor', "The active code editor is read-only.") }]);
|
||||
}
|
||||
|
||||
const picks: QuickPickInput<IQuickPickItem & { run(): void }>[] = [
|
||||
activeTextEditorWidget.getAction(IndentUsingSpaces.ID),
|
||||
activeTextEditorWidget.getAction(IndentUsingTabs.ID),
|
||||
activeTextEditorWidget.getAction(DetectIndentation.ID),
|
||||
activeTextEditorWidget.getAction(IndentationToSpacesAction.ID),
|
||||
activeTextEditorWidget.getAction(IndentationToTabsAction.ID),
|
||||
activeTextEditorWidget.getAction(TrimTrailingWhitespaceAction.ID)
|
||||
].map((a: IEditorAction) => {
|
||||
return {
|
||||
id: a.id,
|
||||
label: a.label,
|
||||
detail: Language.isDefaultVariant() ? undefined : a.alias,
|
||||
run: () => {
|
||||
activeTextEditorWidget.focus();
|
||||
a.run();
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
picks.splice(3, 0, { type: 'separator', label: nls.localize('indentConvert', "convert file") });
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
export class ChangeEOLAction extends Action {
|
||||
|
||||
static readonly ID = 'workbench.action.editor.changeEOL';
|
||||
@@ -1182,7 +1158,7 @@ export class ChangeEncodingAction extends Action {
|
||||
super(actionId, actionLabel);
|
||||
}
|
||||
|
||||
run(): Promise<any> {
|
||||
async run(): Promise<any> {
|
||||
if (!getCodeEditor(this.editorService.activeTextEditorWidget)) {
|
||||
return this.quickInputService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]);
|
||||
}
|
||||
@@ -1207,93 +1183,88 @@ export class ChangeEncodingAction extends Action {
|
||||
reopenWithEncodingPick = { label: nls.localize('reopenWithEncoding', "Reopen with Encoding"), detail: 'Reopen with Encoding' };
|
||||
}
|
||||
|
||||
let pickActionPromise: Promise<IQuickPickItem>;
|
||||
let action: IQuickPickItem;
|
||||
if (encodingSupport instanceof UntitledEditorInput) {
|
||||
pickActionPromise = Promise.resolve(saveWithEncodingPick);
|
||||
action = saveWithEncodingPick;
|
||||
} else if (!isWritableBaseEditor(activeControl)) {
|
||||
pickActionPromise = Promise.resolve(reopenWithEncodingPick);
|
||||
action = reopenWithEncodingPick;
|
||||
} else {
|
||||
pickActionPromise = this.quickInputService.pick([reopenWithEncodingPick, saveWithEncodingPick], { placeHolder: nls.localize('pickAction', "Select Action"), matchOnDetail: true });
|
||||
action = await this.quickInputService.pick([reopenWithEncodingPick, saveWithEncodingPick], { placeHolder: nls.localize('pickAction', "Select Action"), matchOnDetail: true });
|
||||
}
|
||||
|
||||
return pickActionPromise.then(action => {
|
||||
if (!action) {
|
||||
return undefined;
|
||||
}
|
||||
if (!action) {
|
||||
return;
|
||||
}
|
||||
|
||||
const resource = toResource(activeControl!.input, { supportSideBySide: SideBySideEditor.MASTER });
|
||||
await timeout(50); // quick open is sensitive to being opened so soon after another
|
||||
|
||||
return timeout(50 /* quick open is sensitive to being opened so soon after another */)
|
||||
.then(() => {
|
||||
if (!resource || !this.fileService.canHandleResource(resource)) {
|
||||
return Promise.resolve(null); // encoding detection only possible for resources the file service can handle
|
||||
}
|
||||
const resource = toResource(activeControl!.input, { supportSideBySide: SideBySideEditor.MASTER });
|
||||
if (!resource || !this.fileService.canHandleResource(resource)) {
|
||||
return null; // encoding detection only possible for resources the file service can handle
|
||||
}
|
||||
|
||||
return this.textFileService.read(resource, { autoGuessEncoding: true, acceptTextOnly: true }).then(content => content.encoding, err => null);
|
||||
})
|
||||
.then((guessedEncoding: string) => {
|
||||
const isReopenWithEncoding = (action === reopenWithEncodingPick);
|
||||
const content = await this.textFileService.read(resource, { autoGuessEncoding: true, acceptTextOnly: true });
|
||||
const guessedEncoding = content.encoding;
|
||||
|
||||
const configuredEncoding = this.textResourceConfigurationService.getValue(withNullAsUndefined(resource), 'files.encoding');
|
||||
const isReopenWithEncoding = (action === reopenWithEncodingPick);
|
||||
|
||||
let directMatchIndex: number | undefined;
|
||||
let aliasMatchIndex: number | undefined;
|
||||
const configuredEncoding = this.textResourceConfigurationService.getValue(withNullAsUndefined(resource), 'files.encoding');
|
||||
|
||||
// All encodings are valid picks
|
||||
const picks: QuickPickInput[] = Object.keys(SUPPORTED_ENCODINGS)
|
||||
.sort((k1, k2) => {
|
||||
if (k1 === configuredEncoding) {
|
||||
return -1;
|
||||
} else if (k2 === configuredEncoding) {
|
||||
return 1;
|
||||
}
|
||||
let directMatchIndex: number | undefined;
|
||||
let aliasMatchIndex: number | undefined;
|
||||
|
||||
return SUPPORTED_ENCODINGS[k1].order - SUPPORTED_ENCODINGS[k2].order;
|
||||
})
|
||||
.filter(k => {
|
||||
if (k === guessedEncoding && guessedEncoding !== configuredEncoding) {
|
||||
return false; // do not show encoding if it is the guessed encoding that does not match the configured
|
||||
}
|
||||
// All encodings are valid picks
|
||||
const picks: QuickPickInput[] = Object.keys(SUPPORTED_ENCODINGS)
|
||||
.sort((k1, k2) => {
|
||||
if (k1 === configuredEncoding) {
|
||||
return -1;
|
||||
} else if (k2 === configuredEncoding) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return !isReopenWithEncoding || !SUPPORTED_ENCODINGS[k].encodeOnly; // hide those that can only be used for encoding if we are about to decode
|
||||
})
|
||||
.map((key, index) => {
|
||||
if (key === encodingSupport.getEncoding()) {
|
||||
directMatchIndex = index;
|
||||
} else if (SUPPORTED_ENCODINGS[key].alias === encodingSupport.getEncoding()) {
|
||||
aliasMatchIndex = index;
|
||||
}
|
||||
return SUPPORTED_ENCODINGS[k1].order - SUPPORTED_ENCODINGS[k2].order;
|
||||
})
|
||||
.filter(k => {
|
||||
if (k === guessedEncoding && guessedEncoding !== configuredEncoding) {
|
||||
return false; // do not show encoding if it is the guessed encoding that does not match the configured
|
||||
}
|
||||
|
||||
return { id: key, label: SUPPORTED_ENCODINGS[key].labelLong, description: key };
|
||||
});
|
||||
return !isReopenWithEncoding || !SUPPORTED_ENCODINGS[k].encodeOnly; // hide those that can only be used for encoding if we are about to decode
|
||||
})
|
||||
.map((key, index) => {
|
||||
if (key === encodingSupport.getEncoding()) {
|
||||
directMatchIndex = index;
|
||||
} else if (SUPPORTED_ENCODINGS[key].alias === encodingSupport.getEncoding()) {
|
||||
aliasMatchIndex = index;
|
||||
}
|
||||
|
||||
const items = picks.slice() as IQuickPickItem[];
|
||||
return { id: key, label: SUPPORTED_ENCODINGS[key].labelLong, description: key };
|
||||
});
|
||||
|
||||
// If we have a guessed encoding, show it first unless it matches the configured encoding
|
||||
if (guessedEncoding && configuredEncoding !== guessedEncoding && SUPPORTED_ENCODINGS[guessedEncoding]) {
|
||||
picks.unshift({ type: 'separator' });
|
||||
picks.unshift({ id: guessedEncoding, label: SUPPORTED_ENCODINGS[guessedEncoding].labelLong, description: nls.localize('guessedEncoding', "Guessed from content") });
|
||||
}
|
||||
const items = picks.slice() as IQuickPickItem[];
|
||||
|
||||
return this.quickInputService.pick(picks, {
|
||||
placeHolder: isReopenWithEncoding ? nls.localize('pickEncodingForReopen', "Select File Encoding to Reopen File") : nls.localize('pickEncodingForSave', "Select File Encoding to Save with"),
|
||||
activeItem: items[typeof directMatchIndex === 'number' ? directMatchIndex : typeof aliasMatchIndex === 'number' ? aliasMatchIndex : -1]
|
||||
}).then(encoding => {
|
||||
if (!encoding) {
|
||||
return;
|
||||
}
|
||||
// If we have a guessed encoding, show it first unless it matches the configured encoding
|
||||
if (guessedEncoding && configuredEncoding !== guessedEncoding && SUPPORTED_ENCODINGS[guessedEncoding]) {
|
||||
picks.unshift({ type: 'separator' });
|
||||
picks.unshift({ id: guessedEncoding, label: SUPPORTED_ENCODINGS[guessedEncoding].labelLong, description: nls.localize('guessedEncoding', "Guessed from content") });
|
||||
}
|
||||
|
||||
const activeControl = this.editorService.activeControl;
|
||||
if (!activeControl) {
|
||||
return;
|
||||
}
|
||||
|
||||
const encodingSupport = toEditorWithEncodingSupport(activeControl.input);
|
||||
if (typeof encoding.id !== 'undefined' && encodingSupport && encodingSupport.getEncoding() !== encoding.id) {
|
||||
encodingSupport.setEncoding(encoding.id, isReopenWithEncoding ? EncodingMode.Decode : EncodingMode.Encode); // Set new encoding
|
||||
}
|
||||
});
|
||||
});
|
||||
const encoding = await this.quickInputService.pick(picks, {
|
||||
placeHolder: isReopenWithEncoding ? nls.localize('pickEncodingForReopen', "Select File Encoding to Reopen File") : nls.localize('pickEncodingForSave', "Select File Encoding to Save with"),
|
||||
activeItem: items[typeof directMatchIndex === 'number' ? directMatchIndex : typeof aliasMatchIndex === 'number' ? aliasMatchIndex : -1]
|
||||
});
|
||||
|
||||
if (!encoding) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.editorService.activeControl) {
|
||||
return;
|
||||
}
|
||||
|
||||
const activeEncodingSupport = toEditorWithEncodingSupport(this.editorService.activeControl.input);
|
||||
if (typeof encoding.id !== 'undefined' && activeEncodingSupport && activeEncodingSupport.getEncoding() !== encoding.id) {
|
||||
activeEncodingSupport.setEncoding(encoding.id, isReopenWithEncoding ? EncodingMode.Decode : EncodingMode.Encode); // Set new encoding
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,25 +3,6 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.monaco-workbench .editor-statusbar-item > a:not(:first-child) {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.monaco-workbench .editor-statusbar-item > .editor-status-mode,
|
||||
.monaco-workbench .editor-statusbar-item > .editor-status-encoding,
|
||||
.monaco-workbench .editor-statusbar-item > .editor-status-eol,
|
||||
.monaco-workbench .editor-statusbar-item > .editor-status-selection,
|
||||
.monaco-workbench .editor-statusbar-item > .editor-status-indentation,
|
||||
.monaco-workbench .editor-statusbar-item > .editor-status-metadata,
|
||||
.monaco-workbench .editor-statusbar-item > .editor-status-tabfocusmode,
|
||||
.monaco-workbench .editor-statusbar-item > .editor-status-screenreadermode {
|
||||
padding: 0 5px 0 5px;
|
||||
}
|
||||
|
||||
.monaco-workbench .editor-statusbar-item > .editor-status-metadata {
|
||||
cursor: default !important;
|
||||
}
|
||||
|
||||
.monaco-workbench .screen-reader-detected-explanation {
|
||||
width: 420px;
|
||||
top: 30px;
|
||||
|
||||
@@ -14,19 +14,23 @@
|
||||
|
||||
.monaco-resource-viewer.image {
|
||||
padding: 0;
|
||||
background-position: 0 0, 8px 8px;
|
||||
background-size: 16px 16px;
|
||||
display: flex;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.vs .monaco-resource-viewer.image {
|
||||
.monaco-resource-viewer.image img {
|
||||
padding: 0;
|
||||
background-position: 0 0, 8px 8px;
|
||||
background-size: 16px 16px;
|
||||
}
|
||||
|
||||
.vs .monaco-resource-viewer.image img {
|
||||
background-image:
|
||||
linear-gradient(45deg, rgb(230, 230, 230) 25%, transparent 25%, transparent 75%, rgb(230, 230, 230) 75%, rgb(230, 230, 230)),
|
||||
linear-gradient(45deg, rgb(230, 230, 230) 25%, transparent 25%, transparent 75%, rgb(230, 230, 230) 75%, rgb(230, 230, 230));
|
||||
}
|
||||
|
||||
.vs-dark .monaco-resource-viewer.image {
|
||||
.vs-dark .monaco-resource-viewer.image img {
|
||||
background-image:
|
||||
linear-gradient(45deg, rgb(20, 20, 20) 25%, transparent 25%, transparent 75%, rgb(20, 20, 20) 75%, rgb(20, 20, 20)),
|
||||
linear-gradient(45deg, rgb(20, 20, 20) 25%, transparent 25%, transparent 75%, rgb(20, 20, 20) 75%, rgb(20, 20, 20));
|
||||
@@ -54,6 +58,10 @@
|
||||
cursor: zoom-out;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.statusbar > .items-container > .statusbar-item > a.zoom-statusbar-item {
|
||||
padding: 0 5px 0 5px;
|
||||
}
|
||||
|
||||
.monaco-resource-viewer .embedded-link,
|
||||
.monaco-resource-viewer .embedded-link:hover {
|
||||
cursor: pointer;
|
||||
|
||||
@@ -47,7 +47,7 @@ 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.toDispose.push({ dispose: () => removeClass(this.titleContainer, 'breadcrumbs') }); // import to remove because the container is a shared dom node
|
||||
this._register({ dispose: () => removeClass(this.titleContainer, 'breadcrumbs') }); // import to remove because the container is a shared dom node
|
||||
|
||||
// Right Actions Container
|
||||
const actionsContainer = document.createElement('div');
|
||||
|
||||
@@ -15,13 +15,13 @@ import { clamp } from 'vs/base/common/numbers';
|
||||
import { Themable } from 'vs/workbench/common/theme';
|
||||
import { IStatusbarItem } from 'vs/workbench/browser/parts/statusbar/statusbar';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IDisposable, Disposable, combinedDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IDisposable, Disposable, toDisposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { memoize } from 'vs/base/common/decorators';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
|
||||
export interface IResourceDescriptor {
|
||||
readonly resource: URI;
|
||||
@@ -78,7 +78,7 @@ export class ResourceViewer {
|
||||
|
||||
static show(
|
||||
descriptor: IResourceDescriptor,
|
||||
textFileService: ITextFileService,
|
||||
fileService: IFileService,
|
||||
container: HTMLElement,
|
||||
scrollbar: DomScrollableElement,
|
||||
delegate: ResourceViewerDelegate
|
||||
@@ -89,7 +89,7 @@ export class ResourceViewer {
|
||||
|
||||
// Images
|
||||
if (ResourceViewer.isImageResource(descriptor)) {
|
||||
return ImageView.create(container, descriptor, textFileService, scrollbar, delegate);
|
||||
return ImageView.create(container, descriptor, fileService, scrollbar, delegate);
|
||||
}
|
||||
|
||||
// Large Files
|
||||
@@ -118,12 +118,12 @@ class ImageView {
|
||||
static create(
|
||||
container: HTMLElement,
|
||||
descriptor: IResourceDescriptor,
|
||||
textFileService: ITextFileService,
|
||||
fileService: IFileService,
|
||||
scrollbar: DomScrollableElement,
|
||||
delegate: ResourceViewerDelegate
|
||||
): ResourceViewerContext {
|
||||
if (ImageView.shouldShowImageInline(descriptor)) {
|
||||
return InlineImageView.create(container, descriptor, textFileService, scrollbar, delegate);
|
||||
return InlineImageView.create(container, descriptor, fileService, scrollbar, delegate);
|
||||
}
|
||||
|
||||
return LargeImageView.create(container, descriptor, delegate);
|
||||
@@ -160,7 +160,7 @@ class LargeImageView {
|
||||
|
||||
DOM.clearNode(container);
|
||||
|
||||
const disposables: IDisposable[] = [];
|
||||
const disposables = new DisposableStore();
|
||||
|
||||
const label = document.createElement('p');
|
||||
label.textContent = nls.localize('largeImageError', "The image is not displayed in the editor because it is too large ({0}).", size);
|
||||
@@ -172,10 +172,10 @@ class LargeImageView {
|
||||
link.setAttribute('role', 'button');
|
||||
link.textContent = nls.localize('resourceOpenExternalButton', "Open image using external program?");
|
||||
|
||||
disposables.push(DOM.addDisposableListener(link, DOM.EventType.CLICK, () => openExternal(descriptor.resource)));
|
||||
disposables.add(DOM.addDisposableListener(link, DOM.EventType.CLICK, () => openExternal(descriptor.resource)));
|
||||
}
|
||||
|
||||
return combinedDisposable(disposables);
|
||||
return disposables;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,7 +212,7 @@ class FileSeemsBinaryFileView {
|
||||
|
||||
DOM.clearNode(container);
|
||||
|
||||
const disposables: IDisposable[] = [];
|
||||
const disposables = new DisposableStore();
|
||||
|
||||
const label = document.createElement('p');
|
||||
label.textContent = nls.localize('nativeBinaryError', "The file is not displayed in the editor because it is either binary or uses an unsupported text encoding.");
|
||||
@@ -223,12 +223,12 @@ class FileSeemsBinaryFileView {
|
||||
link.setAttribute('role', 'button');
|
||||
link.textContent = nls.localize('openAsText', "Do you want to open it anyway?");
|
||||
|
||||
disposables.push(DOM.addDisposableListener(link, DOM.EventType.CLICK, () => delegate.openInternalClb(descriptor.resource)));
|
||||
disposables.add(DOM.addDisposableListener(link, DOM.EventType.CLICK, () => delegate.openInternalClb(descriptor.resource)));
|
||||
}
|
||||
|
||||
scrollbar.scanDomNode();
|
||||
|
||||
return combinedDisposable(disposables);
|
||||
return disposables;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -359,15 +359,15 @@ class InlineImageView {
|
||||
static create(
|
||||
container: HTMLElement,
|
||||
descriptor: IResourceDescriptor,
|
||||
textFileService: ITextFileService,
|
||||
fileService: IFileService,
|
||||
scrollbar: DomScrollableElement,
|
||||
delegate: ResourceViewerDelegate
|
||||
) {
|
||||
const disposables: IDisposable[] = [];
|
||||
const disposables = new DisposableStore();
|
||||
|
||||
const context: ResourceViewerContext = {
|
||||
layout(dimension: DOM.Dimension) { },
|
||||
dispose: () => combinedDisposable(disposables).dispose()
|
||||
dispose: () => disposables.dispose()
|
||||
};
|
||||
|
||||
const cacheKey = `${descriptor.resource.toString()}:${descriptor.etag}`;
|
||||
@@ -436,7 +436,7 @@ class InlineImageView {
|
||||
updateScale(scale);
|
||||
}
|
||||
|
||||
disposables.push(DOM.addDisposableListener(window, DOM.EventType.KEY_DOWN, (e: KeyboardEvent) => {
|
||||
disposables.add(DOM.addDisposableListener(window, DOM.EventType.KEY_DOWN, (e: KeyboardEvent) => {
|
||||
if (!image) {
|
||||
return;
|
||||
}
|
||||
@@ -449,7 +449,7 @@ class InlineImageView {
|
||||
}
|
||||
}));
|
||||
|
||||
disposables.push(DOM.addDisposableListener(window, DOM.EventType.KEY_UP, (e: KeyboardEvent) => {
|
||||
disposables.add(DOM.addDisposableListener(window, DOM.EventType.KEY_UP, (e: KeyboardEvent) => {
|
||||
if (!image) {
|
||||
return;
|
||||
}
|
||||
@@ -463,7 +463,7 @@ class InlineImageView {
|
||||
}
|
||||
}));
|
||||
|
||||
disposables.push(DOM.addDisposableListener(container, DOM.EventType.CLICK, (e: MouseEvent) => {
|
||||
disposables.add(DOM.addDisposableListener(container, DOM.EventType.CLICK, (e: MouseEvent) => {
|
||||
if (!image) {
|
||||
return;
|
||||
}
|
||||
@@ -496,7 +496,7 @@ class InlineImageView {
|
||||
}
|
||||
}));
|
||||
|
||||
disposables.push(DOM.addDisposableListener(container, DOM.EventType.WHEEL, (e: WheelEvent) => {
|
||||
disposables.add(DOM.addDisposableListener(container, DOM.EventType.WHEEL, (e: WheelEvent) => {
|
||||
if (!image) {
|
||||
return;
|
||||
}
|
||||
@@ -518,7 +518,7 @@ class InlineImageView {
|
||||
updateScale(scale as number * (1 - delta * InlineImageView.SCALE_PINCH_FACTOR));
|
||||
}));
|
||||
|
||||
disposables.push(DOM.addDisposableListener(container, DOM.EventType.SCROLL, () => {
|
||||
disposables.add(DOM.addDisposableListener(container, DOM.EventType.SCROLL, () => {
|
||||
if (!image || !image.parentElement || scale === 'fit') {
|
||||
return;
|
||||
}
|
||||
@@ -536,7 +536,7 @@ class InlineImageView {
|
||||
image = DOM.append(container, DOM.$<HTMLImageElement>('img.scale-to-fit'));
|
||||
image.style.visibility = 'hidden';
|
||||
|
||||
disposables.push(DOM.addDisposableListener(image, DOM.EventType.LOAD, e => {
|
||||
disposables.add(DOM.addDisposableListener(image, DOM.EventType.LOAD, e => {
|
||||
if (!image) {
|
||||
return;
|
||||
}
|
||||
@@ -557,25 +557,29 @@ class InlineImageView {
|
||||
}
|
||||
}));
|
||||
|
||||
InlineImageView.imageSrc(descriptor, textFileService).then(dataUri => {
|
||||
const imgs = container.getElementsByTagName('img');
|
||||
if (imgs.length) {
|
||||
imgs[0].src = dataUri;
|
||||
InlineImageView.imageSrc(descriptor, fileService).then(src => {
|
||||
const img = container.querySelector('img');
|
||||
if (img) {
|
||||
if (typeof src === 'string') {
|
||||
img.src = src;
|
||||
} else {
|
||||
const url = URL.createObjectURL(src);
|
||||
disposables.add(toDisposable(() => URL.revokeObjectURL(url)));
|
||||
img.src = url;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
private static async imageSrc(descriptor: IResourceDescriptor, textFileService: ITextFileService): Promise<string> {
|
||||
private static async imageSrc(descriptor: IResourceDescriptor, fileService: IFileService): Promise<string | Blob> {
|
||||
if (descriptor.resource.scheme === Schemas.data) {
|
||||
return Promise.resolve(descriptor.resource.toString(true /* skip encoding */));
|
||||
return descriptor.resource.toString(true /* skip encoding */);
|
||||
}
|
||||
|
||||
const data = await textFileService.read(descriptor.resource, { encoding: 'base64' });
|
||||
const mime = getMime(descriptor);
|
||||
|
||||
return `data:${mime};base64,${data.value}`;
|
||||
const { value } = await fileService.readFile(descriptor.resource);
|
||||
return new Blob([value.buffer], { type: getMime(descriptor) });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -169,7 +169,7 @@ export class SideBySideEditor extends BaseEditor {
|
||||
}
|
||||
|
||||
if (!this.detailsEditor || !this.masterEditor) {
|
||||
return Promise.resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
await Promise.all([
|
||||
@@ -213,8 +213,6 @@ export class SideBySideEditor extends BaseEditor {
|
||||
this.detailsEditor.setInput(detailsInput, null, token),
|
||||
this.masterEditor.setInput(masterInput, options, token)]
|
||||
);
|
||||
|
||||
return this.focus();
|
||||
}
|
||||
|
||||
updateStyles(): void {
|
||||
|
||||
@@ -20,13 +20,12 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IMenuService } from 'vs/platform/actions/common/actions';
|
||||
import { TitleControl } from 'vs/workbench/browser/parts/editor/titleControl';
|
||||
import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen';
|
||||
import { IDisposable, dispose, combinedDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IDisposable, dispose, DisposableStore, combinedDisposable } from 'vs/base/common/lifecycle';
|
||||
import { ScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
|
||||
import { ScrollbarVisibility } from 'vs/base/common/scrollable';
|
||||
import { getOrSet } from 'vs/base/common/map';
|
||||
// {{SQL CARBON EDIT}} -- Display the editor's tab color
|
||||
import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector, HIGH_CONTRAST } from 'vs/platform/theme/common/themeService';
|
||||
import { TAB_INACTIVE_BACKGROUND, TAB_ACTIVE_BACKGROUND, TAB_ACTIVE_FOREGROUND, TAB_INACTIVE_FOREGROUND, TAB_BORDER, EDITOR_DRAG_AND_DROP_BACKGROUND, TAB_UNFOCUSED_ACTIVE_FOREGROUND, TAB_UNFOCUSED_INACTIVE_FOREGROUND, TAB_UNFOCUSED_ACTIVE_BORDER, TAB_ACTIVE_BORDER, TAB_HOVER_BACKGROUND, TAB_HOVER_BORDER, TAB_UNFOCUSED_HOVER_BACKGROUND, TAB_UNFOCUSED_HOVER_BORDER, EDITOR_GROUP_HEADER_TABS_BACKGROUND, WORKBENCH_BACKGROUND, TAB_ACTIVE_BORDER_TOP, TAB_UNFOCUSED_ACTIVE_BORDER_TOP, TAB_ACTIVE_MODIFIED_BORDER, TAB_INACTIVE_MODIFIED_BORDER, TAB_UNFOCUSED_ACTIVE_MODIFIED_BORDER, TAB_UNFOCUSED_INACTIVE_MODIFIED_BORDER } from 'vs/workbench/common/theme';
|
||||
import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector, HIGH_CONTRAST } from 'vs/platform/theme/common/themeService'; // {{SQL CARBON EDIT}} -- Display the editor's tab color
|
||||
import { TAB_INACTIVE_BACKGROUND, TAB_ACTIVE_BACKGROUND, TAB_ACTIVE_FOREGROUND, TAB_INACTIVE_FOREGROUND, TAB_BORDER, EDITOR_DRAG_AND_DROP_BACKGROUND, TAB_UNFOCUSED_ACTIVE_FOREGROUND, TAB_UNFOCUSED_INACTIVE_FOREGROUND, TAB_UNFOCUSED_ACTIVE_BACKGROUND, TAB_UNFOCUSED_ACTIVE_BORDER, TAB_ACTIVE_BORDER, TAB_HOVER_BACKGROUND, TAB_HOVER_BORDER, TAB_UNFOCUSED_HOVER_BACKGROUND, TAB_UNFOCUSED_HOVER_BORDER, EDITOR_GROUP_HEADER_TABS_BACKGROUND, WORKBENCH_BACKGROUND, TAB_ACTIVE_BORDER_TOP, TAB_UNFOCUSED_ACTIVE_BORDER_TOP, TAB_ACTIVE_MODIFIED_BORDER, TAB_INACTIVE_MODIFIED_BORDER, TAB_UNFOCUSED_ACTIVE_MODIFIED_BORDER, TAB_UNFOCUSED_INACTIVE_MODIFIED_BORDER } from 'vs/workbench/common/theme';
|
||||
import { activeContrastBorder, contrastBorder, editorBackground, breadcrumbsBackground } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { ResourcesDropHandler, fillResourceDataTransfers, DraggedEditorIdentifier, DraggedEditorGroupIdentifier, DragAndDropObserver } from 'vs/workbench/browser/dnd';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
@@ -118,7 +117,7 @@ export class TabsTitleControl extends TitleControl {
|
||||
this.registerTabsContainerListeners();
|
||||
|
||||
// Tabs Scrollbar
|
||||
this.tabsScrollbar = this.createTabsScrollbar(this.tabsContainer);
|
||||
this.tabsScrollbar = this._register(this.createTabsScrollbar(this.tabsContainer));
|
||||
tabsAndActionsContainer.appendChild(this.tabsScrollbar.getDomNode());
|
||||
|
||||
// Editor Toolbar Container
|
||||
@@ -465,13 +464,13 @@ export class TabsTitleControl extends TitleControl {
|
||||
// Eventing
|
||||
const eventsDisposable = this.registerTabListeners(tabContainer, index);
|
||||
|
||||
this.tabDisposeables.push(combinedDisposable([eventsDisposable, tabActionBar, tabActionRunner, editorLabel]));
|
||||
this.tabDisposeables.push(combinedDisposable(eventsDisposable, tabActionBar, tabActionRunner, editorLabel));
|
||||
|
||||
return tabContainer;
|
||||
}
|
||||
|
||||
private registerTabListeners(tab: HTMLElement, index: number): IDisposable {
|
||||
const disposables: IDisposable[] = [];
|
||||
const disposables = new DisposableStore();
|
||||
|
||||
const handleClickOrTouch = (e: MouseEvent | GestureEvent): void => {
|
||||
tab.blur();
|
||||
@@ -507,16 +506,16 @@ export class TabsTitleControl extends TitleControl {
|
||||
};
|
||||
|
||||
// Open on Click / Touch
|
||||
disposables.push(addDisposableListener(tab, EventType.MOUSE_DOWN, (e: MouseEvent) => handleClickOrTouch(e)));
|
||||
disposables.push(addDisposableListener(tab, TouchEventType.Tap, (e: GestureEvent) => handleClickOrTouch(e)));
|
||||
disposables.add(addDisposableListener(tab, EventType.MOUSE_DOWN, (e: MouseEvent) => handleClickOrTouch(e)));
|
||||
disposables.add(addDisposableListener(tab, TouchEventType.Tap, (e: GestureEvent) => handleClickOrTouch(e)));
|
||||
|
||||
// Touch Scroll Support
|
||||
disposables.push(addDisposableListener(tab, TouchEventType.Change, (e: GestureEvent) => {
|
||||
disposables.add(addDisposableListener(tab, TouchEventType.Change, (e: GestureEvent) => {
|
||||
this.tabsScrollbar.setScrollPosition({ scrollLeft: this.tabsScrollbar.getScrollPosition().scrollLeft - e.translationX });
|
||||
}));
|
||||
|
||||
// Close on mouse middle click
|
||||
disposables.push(addDisposableListener(tab, EventType.MOUSE_UP, (e: MouseEvent) => {
|
||||
disposables.add(addDisposableListener(tab, EventType.MOUSE_UP, (e: MouseEvent) => {
|
||||
EventHelper.stop(e);
|
||||
|
||||
tab.blur();
|
||||
@@ -530,7 +529,7 @@ export class TabsTitleControl extends TitleControl {
|
||||
}));
|
||||
|
||||
// Context menu on Shift+F10
|
||||
disposables.push(addDisposableListener(tab, EventType.KEY_DOWN, (e: KeyboardEvent) => {
|
||||
disposables.add(addDisposableListener(tab, EventType.KEY_DOWN, (e: KeyboardEvent) => {
|
||||
const event = new StandardKeyboardEvent(e);
|
||||
if (event.shiftKey && event.keyCode === KeyCode.F10) {
|
||||
showContextMenu(e);
|
||||
@@ -538,12 +537,12 @@ export class TabsTitleControl extends TitleControl {
|
||||
}));
|
||||
|
||||
// Context menu on touch context menu gesture
|
||||
disposables.push(addDisposableListener(tab, TouchEventType.Contextmenu, (e: GestureEvent) => {
|
||||
disposables.add(addDisposableListener(tab, TouchEventType.Contextmenu, (e: GestureEvent) => {
|
||||
showContextMenu(e);
|
||||
}));
|
||||
|
||||
// Keyboard accessibility
|
||||
disposables.push(addDisposableListener(tab, EventType.KEY_UP, (e: KeyboardEvent) => {
|
||||
disposables.add(addDisposableListener(tab, EventType.KEY_UP, (e: KeyboardEvent) => {
|
||||
const event = new StandardKeyboardEvent(e);
|
||||
let handled = false;
|
||||
|
||||
@@ -588,14 +587,14 @@ export class TabsTitleControl extends TitleControl {
|
||||
}));
|
||||
|
||||
// Pin on double click
|
||||
disposables.push(addDisposableListener(tab, EventType.DBLCLICK, (e: MouseEvent) => {
|
||||
disposables.add(addDisposableListener(tab, EventType.DBLCLICK, (e: MouseEvent) => {
|
||||
EventHelper.stop(e);
|
||||
|
||||
this.group.pinEditor(this.group.getEditor(index) || undefined);
|
||||
}));
|
||||
|
||||
// Context menu
|
||||
disposables.push(addDisposableListener(tab, EventType.CONTEXT_MENU, (e: Event) => {
|
||||
disposables.add(addDisposableListener(tab, EventType.CONTEXT_MENU, (e: Event) => {
|
||||
EventHelper.stop(e, true);
|
||||
|
||||
const input = this.group.getEditor(index);
|
||||
@@ -605,7 +604,7 @@ export class TabsTitleControl extends TitleControl {
|
||||
}, true /* use capture to fix https://github.com/Microsoft/vscode/issues/19145 */));
|
||||
|
||||
// Drag support
|
||||
disposables.push(addDisposableListener(tab, EventType.DRAG_START, (e: DragEvent) => {
|
||||
disposables.add(addDisposableListener(tab, EventType.DRAG_START, (e: DragEvent) => {
|
||||
const editor = this.group.getEditor(index);
|
||||
if (!editor) {
|
||||
return;
|
||||
@@ -627,7 +626,7 @@ export class TabsTitleControl extends TitleControl {
|
||||
}));
|
||||
|
||||
// Drop support
|
||||
disposables.push(new DragAndDropObserver(tab, {
|
||||
disposables.add(new DragAndDropObserver(tab, {
|
||||
onDragEnter: e => {
|
||||
|
||||
// Update class to signal drag operation
|
||||
@@ -680,7 +679,7 @@ export class TabsTitleControl extends TitleControl {
|
||||
}
|
||||
}));
|
||||
|
||||
return combinedDisposable(disposables);
|
||||
return disposables;
|
||||
}
|
||||
|
||||
private isSupportedDropTransfer(e: DragEvent): boolean {
|
||||
@@ -721,10 +720,10 @@ export class TabsTitleControl extends TitleControl {
|
||||
element.style.outlineColor = activeContrastBorderColor;
|
||||
element.style.outlineOffset = isTab ? '-5px' : '-3px';
|
||||
} else {
|
||||
element.style.outlineWidth = null;
|
||||
element.style.outlineStyle = null;
|
||||
element.style.outlineColor = activeContrastBorderColor;
|
||||
element.style.outlineOffset = null;
|
||||
element.style.outlineWidth = '';
|
||||
element.style.outlineStyle = '';
|
||||
element.style.outlineColor = activeContrastBorderColor || '';
|
||||
element.style.outlineOffset = '';
|
||||
}
|
||||
|
||||
// {{SQL CARBON EDIT}} -- Display the editor's tab color
|
||||
@@ -872,7 +871,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.outlineColor = this.getColor(activeContrastBorder);
|
||||
tabContainer.style.outlineColor = this.getColor(activeContrastBorder) || '';
|
||||
|
||||
// Settings
|
||||
const options = this.accessor.partOptions;
|
||||
@@ -930,7 +929,7 @@ export class TabsTitleControl extends TitleControl {
|
||||
// Container
|
||||
addClass(tabContainer, 'active');
|
||||
tabContainer.setAttribute('aria-selected', 'true');
|
||||
tabContainer.style.backgroundColor = this.getColor(TAB_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) {
|
||||
@@ -1296,35 +1295,29 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
|
||||
adjustedTabDragBackground = editorGroupHeaderTabsBackground.flatten(editorBackgroundColor, editorDragAndDropBackground, editorBackgroundColor, workbenchBackground);
|
||||
}
|
||||
|
||||
// Adjust gradient for focused and unfocused hover background
|
||||
const makeTabHoverBackgroundRule = (color: Color, colorDrag: Color, hasFocus = false) => `
|
||||
.monaco-workbench .part.editor > .content:not(.dragged-over) .editor-group-container${hasFocus ? '.active' : ''} > .title .tabs-container > .tab.sizing-shrink:not(.dragged):hover > .tab-label::after {
|
||||
background: linear-gradient(to left, ${color}, transparent) !important;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content.dragged-over .editor-group-container${hasFocus ? '.active' : ''} > .title .tabs-container > .tab.sizing-shrink:not(.dragged):hover > .tab-label::after {
|
||||
background: linear-gradient(to left, ${colorDrag}, transparent) !important;
|
||||
}
|
||||
`;
|
||||
|
||||
// Adjust gradient for (focused) hover background
|
||||
if (tabHoverBackground && adjustedTabBackground && adjustedTabDragBackground) {
|
||||
const adjustedColor = tabHoverBackground.flatten(adjustedTabBackground);
|
||||
const adjustedColorDrag = tabHoverBackground.flatten(adjustedTabDragBackground);
|
||||
collector.addRule(`
|
||||
.monaco-workbench .part.editor > .content:not(.dragged-over) .editor-group-container.active > .title .tabs-container > .tab.sizing-shrink:not(.dragged):hover > .tab-label::after {
|
||||
background: linear-gradient(to left, ${adjustedColor}, transparent) !important;
|
||||
}
|
||||
|
||||
|
||||
.monaco-workbench .part.editor > .content.dragged-over .editor-group-container.active > .title .tabs-container > .tab.sizing-shrink:not(.dragged):hover > .tab-label::after {
|
||||
background: linear-gradient(to left, ${adjustedColorDrag}, transparent) !important;
|
||||
}
|
||||
`);
|
||||
collector.addRule(makeTabHoverBackgroundRule(adjustedColor, adjustedColorDrag, true));
|
||||
}
|
||||
|
||||
// Adjust gradient for unfocused hover background
|
||||
if (tabUnfocusedHoverBackground && adjustedTabBackground && adjustedTabDragBackground) {
|
||||
const adjustedColor = tabUnfocusedHoverBackground.flatten(adjustedTabBackground);
|
||||
const adjustedColorDrag = tabUnfocusedHoverBackground.flatten(adjustedTabDragBackground);
|
||||
collector.addRule(`
|
||||
.monaco-workbench .part.editor > .content:not(.dragged-over) .editor-group-container > .title .tabs-container > .tab.sizing-shrink:not(.dragged):hover > .tab-label::after {
|
||||
background: linear-gradient(to left, ${adjustedColor}, transparent) !important;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content.dragged-over .editor-group-container > .title .tabs-container > .tab.sizing-shrink:not(.dragged):hover > .tab-label::after {
|
||||
background: linear-gradient(to left, ${adjustedColorDrag}, transparent) !important;
|
||||
}
|
||||
`);
|
||||
collector.addRule(makeTabHoverBackgroundRule(adjustedColor, adjustedColorDrag));
|
||||
}
|
||||
|
||||
// Adjust gradient for drag and drop background
|
||||
@@ -1338,20 +1331,31 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
|
||||
`);
|
||||
}
|
||||
|
||||
// Adjust gradient for active tab background
|
||||
// Adjust gradient for active tab background (focused and unfocused editor groups)
|
||||
const makeTabActiveBackgroundRule = (color: Color, colorDrag: Color, hasFocus = false) => `
|
||||
.monaco-workbench .part.editor > .content:not(.dragged-over) .editor-group-container${hasFocus ? '.active' : ':not(.active)'} > .title .tabs-container > .tab.sizing-shrink.active:not(.dragged) > .tab-label::after {
|
||||
background: linear-gradient(to left, ${color}, transparent);
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content.dragged-over .editor-group-container${hasFocus ? '.active' : ':not(.active)'} > .title .tabs-container > .tab.sizing-shrink.active:not(.dragged) > .tab-label::after {
|
||||
background: linear-gradient(to left, ${colorDrag}, transparent);
|
||||
}
|
||||
`;
|
||||
|
||||
// Adjust gradient for unfocused active tab background
|
||||
const tabActiveBackground = theme.getColor(TAB_ACTIVE_BACKGROUND);
|
||||
if (tabActiveBackground && adjustedTabBackground && adjustedTabDragBackground) {
|
||||
const adjustedColor = tabActiveBackground.flatten(adjustedTabBackground);
|
||||
const adjustedColorDrag = tabActiveBackground.flatten(adjustedTabDragBackground);
|
||||
collector.addRule(`
|
||||
.monaco-workbench .part.editor > .content:not(.dragged-over) .editor-group-container > .title .tabs-container > .tab.sizing-shrink.active:not(.dragged) > .tab-label::after {
|
||||
background: linear-gradient(to left, ${adjustedColor}, transparent);
|
||||
}
|
||||
collector.addRule(makeTabActiveBackgroundRule(adjustedColor, adjustedColorDrag, true));
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content.dragged-over .editor-group-container > .title .tabs-container > .tab.sizing-shrink.active:not(.dragged) > .tab-label::after {
|
||||
background: linear-gradient(to left, ${adjustedColorDrag}, transparent);
|
||||
}
|
||||
`);
|
||||
// Adjust gradient for unfocused active tab background
|
||||
const tabUnfocusedActiveBackground = theme.getColor(TAB_UNFOCUSED_ACTIVE_BACKGROUND);
|
||||
if (tabUnfocusedActiveBackground && adjustedTabBackground && adjustedTabDragBackground) {
|
||||
const adjustedColor = tabUnfocusedActiveBackground.flatten(adjustedTabBackground);
|
||||
const adjustedColorDrag = tabUnfocusedActiveBackground.flatten(adjustedTabDragBackground);
|
||||
collector.addRule(makeTabActiveBackgroundRule(adjustedColor, adjustedColorDrag));
|
||||
}
|
||||
|
||||
// Adjust gradient for inactive tab background
|
||||
|
||||
@@ -70,7 +70,7 @@ export class AbstractTextResourceEditor extends BaseTextEditor {
|
||||
|
||||
// Assert Model instance
|
||||
if (!(resolvedModel instanceof BaseTextEditorModel)) {
|
||||
return Promise.reject(new Error('Unable to open file as text'));
|
||||
throw new Error('Unable to open file as text');
|
||||
}
|
||||
|
||||
// Set Editor Model
|
||||
|
||||
@@ -18,7 +18,7 @@ import { localize } from 'vs/nls';
|
||||
import { createActionViewItem, fillInActionBarActions, fillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem';
|
||||
import { ExecuteCommandAction, IMenu, IMenuService, MenuId } from 'vs/platform/actions/common/actions';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
@@ -33,7 +33,7 @@ import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
|
||||
import { BreadcrumbsConfig } from 'vs/workbench/browser/parts/editor/breadcrumbs';
|
||||
import { BreadcrumbsControl, IBreadcrumbsControlOptions } from 'vs/workbench/browser/parts/editor/breadcrumbsControl';
|
||||
import { EDITOR_TITLE_HEIGHT, IEditorGroupsAccessor, IEditorGroupView } from 'vs/workbench/browser/parts/editor/editor';
|
||||
import { EditorCommandsContextActionRunner, IEditorCommandsContext, IEditorInput, toResource, IEditorPartOptions, SideBySideEditor } from 'vs/workbench/common/editor';
|
||||
import { EditorCommandsContextActionRunner, IEditorCommandsContext, IEditorInput, toResource, IEditorPartOptions, SideBySideEditor, EditorPinnedContext } from 'vs/workbench/common/editor';
|
||||
import { ResourceContextKey } from 'vs/workbench/common/resources';
|
||||
import { Themable } from 'vs/workbench/common/theme';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
@@ -59,6 +59,8 @@ export abstract class TitleControl extends Themable {
|
||||
protected editorActionsToolbar: ToolBar;
|
||||
|
||||
private resourceContext: ResourceContextKey;
|
||||
private editorPinnedContext: IContextKey<boolean>;
|
||||
|
||||
private editorToolBarMenuDisposables: IDisposable[] = [];
|
||||
|
||||
private contextMenu: IMenu;
|
||||
@@ -85,6 +87,8 @@ export abstract class TitleControl extends Themable {
|
||||
super(themeService);
|
||||
|
||||
this.resourceContext = this._register(instantiationService.createInstance(ResourceContextKey));
|
||||
this.editorPinnedContext = EditorPinnedContext.bindTo(contextKeyService);
|
||||
|
||||
this.contextMenu = this._register(this.menuService.createMenu(MenuId.EditorTitleContext, this.contextKeyService));
|
||||
|
||||
this.create(parent);
|
||||
@@ -221,8 +225,9 @@ export abstract class TitleControl extends Themable {
|
||||
// Dispose previous listeners
|
||||
this.editorToolBarMenuDisposables = dispose(this.editorToolBarMenuDisposables);
|
||||
|
||||
// Update the resource context
|
||||
// Update contexts
|
||||
this.resourceContext.set(this.group.activeEditor ? withUndefinedAsNull(toResource(this.group.activeEditor, { supportSideBySide: SideBySideEditor.MASTER })) : null);
|
||||
this.editorPinnedContext.set(this.group.activeEditor ? this.group.isPinned(this.group.activeEditor) : false);
|
||||
|
||||
// Editor actions require the editor control to be there, so we retrieve it via service
|
||||
const activeControl = this.group.activeControl;
|
||||
@@ -287,9 +292,11 @@ export abstract class TitleControl extends Themable {
|
||||
|
||||
protected onContextMenu(editor: IEditorInput, e: Event, node: HTMLElement): void {
|
||||
|
||||
// Update the resource context
|
||||
const currentContext = this.resourceContext.get();
|
||||
// Update contexts based on editor picked and remember previous to restore
|
||||
const currentResourceContext = this.resourceContext.get();
|
||||
this.resourceContext.set(withUndefinedAsNull(toResource(editor, { supportSideBySide: SideBySideEditor.MASTER })));
|
||||
const currentPinnedContext = !!this.editorPinnedContext.get();
|
||||
this.editorPinnedContext.set(this.group.isPinned(editor));
|
||||
|
||||
// Find target anchor
|
||||
let anchor: HTMLElement | { x: number, y: number } = node;
|
||||
@@ -310,8 +317,9 @@ export abstract class TitleControl extends Themable {
|
||||
getKeyBinding: (action) => this.getKeybinding(action),
|
||||
onHide: () => {
|
||||
|
||||
// restore previous context
|
||||
this.resourceContext.set(currentContext || null);
|
||||
// restore previous contexts
|
||||
this.resourceContext.set(currentResourceContext || null);
|
||||
this.editorPinnedContext.set(currentPinnedContext);
|
||||
|
||||
// restore focus to active group
|
||||
this.accessor.activeGroup.focus();
|
||||
|
||||
@@ -121,12 +121,12 @@ export class ConfigureNotificationAction extends Action {
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
private _configurationActions: IAction[]
|
||||
private readonly _configurationActions: ReadonlyArray<IAction>
|
||||
) {
|
||||
super(id, label, 'configure-notification-action');
|
||||
}
|
||||
|
||||
get configurationActions(): IAction[] {
|
||||
get configurationActions(): ReadonlyArray<IAction> {
|
||||
return this._configurationActions;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,7 +219,7 @@ export class NotificationsList extends Themable {
|
||||
this.listContainer.style.background = background ? background.toString() : null;
|
||||
|
||||
const outlineColor = this.getColor(contrastBorder);
|
||||
this.listContainer.style.outlineColor = outlineColor ? outlineColor.toString() : null;
|
||||
this.listContainer.style.outlineColor = outlineColor ? outlineColor.toString() : '';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,16 +3,20 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { INotificationsModel, INotificationChangeEvent, NotificationChangeType, INotificationViewItem } from 'vs/workbench/common/notifications';
|
||||
import { INotificationsModel, INotificationChangeEvent, NotificationChangeType, INotificationViewItem, IStatusMessageChangeEvent, StatusMessageChangeType, IStatusMessageViewItem } from 'vs/workbench/common/notifications';
|
||||
import { IStatusbarService, StatusbarAlignment, IStatusbarEntryAccessor, IStatusbarEntry } from 'vs/platform/statusbar/common/statusbar';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { HIDE_NOTIFICATIONS_CENTER, SHOW_NOTIFICATIONS_CENTER } from 'vs/workbench/browser/parts/notifications/notificationsCommands';
|
||||
import { localize } from 'vs/nls';
|
||||
|
||||
export class NotificationsStatus extends Disposable {
|
||||
private statusItem: IStatusbarEntryAccessor;
|
||||
|
||||
private notificationsCenterStatusItem: IStatusbarEntryAccessor;
|
||||
private currentNotifications = new Set<INotificationViewItem>();
|
||||
|
||||
private currentStatusMessage: [IStatusMessageViewItem, IDisposable] | undefined;
|
||||
|
||||
private isNotificationsCenterVisible: boolean;
|
||||
private _counter: Set<INotificationViewItem>;
|
||||
|
||||
constructor(
|
||||
private model: INotificationsModel,
|
||||
@@ -20,29 +24,18 @@ export class NotificationsStatus extends Disposable {
|
||||
) {
|
||||
super();
|
||||
|
||||
this._counter = new Set<INotificationViewItem>();
|
||||
this.updateNotificationsCenterStatusItem();
|
||||
|
||||
this.updateNotificationsStatusItem();
|
||||
if (model.statusMessage) {
|
||||
this.doSetStatusMessage(model.statusMessage);
|
||||
}
|
||||
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
private get count(): number {
|
||||
return this._counter.size;
|
||||
}
|
||||
|
||||
update(isCenterVisible: boolean): void {
|
||||
if (this.isNotificationsCenterVisible !== isCenterVisible) {
|
||||
this.isNotificationsCenterVisible = isCenterVisible;
|
||||
|
||||
// Showing the notification center resets the counter to 0
|
||||
this._counter.clear();
|
||||
this.updateNotificationsStatusItem();
|
||||
}
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
this._register(this.model.onDidNotificationChange(e => this.onDidNotificationChange(e)));
|
||||
this._register(this.model.onDidStatusMessageChange(e => this.onDidStatusMessageChange(e)));
|
||||
}
|
||||
|
||||
private onDidNotificationChange(e: INotificationChangeEvent): void {
|
||||
@@ -52,29 +45,29 @@ export class NotificationsStatus extends Disposable {
|
||||
|
||||
// Notification got Added
|
||||
if (e.kind === NotificationChangeType.ADD) {
|
||||
this._counter.add(e.item);
|
||||
this.currentNotifications.add(e.item);
|
||||
}
|
||||
|
||||
// Notification got Removed
|
||||
else if (e.kind === NotificationChangeType.REMOVE) {
|
||||
this._counter.delete(e.item);
|
||||
this.currentNotifications.delete(e.item);
|
||||
}
|
||||
|
||||
this.updateNotificationsStatusItem();
|
||||
this.updateNotificationsCenterStatusItem();
|
||||
}
|
||||
|
||||
private updateNotificationsStatusItem(): void {
|
||||
private updateNotificationsCenterStatusItem(): void {
|
||||
const statusProperties: IStatusbarEntry = {
|
||||
text: this.count === 0 ? '$(bell)' : `$(bell) ${this.count}`,
|
||||
text: this.currentNotifications.size === 0 ? '$(bell)' : `$(bell) ${this.currentNotifications.size}`,
|
||||
command: this.isNotificationsCenterVisible ? HIDE_NOTIFICATIONS_CENTER : SHOW_NOTIFICATIONS_CENTER,
|
||||
tooltip: this.getTooltip(),
|
||||
showBeak: this.isNotificationsCenterVisible
|
||||
};
|
||||
|
||||
if (!this.statusItem) {
|
||||
this.statusItem = this.statusbarService.addEntry(statusProperties, StatusbarAlignment.RIGHT, -1000 /* towards the far end of the right hand side */);
|
||||
if (!this.notificationsCenterStatusItem) {
|
||||
this.notificationsCenterStatusItem = this.statusbarService.addEntry(statusProperties, 'status.notifications', localize('status.notifications', "Notifications"), StatusbarAlignment.RIGHT, -1000 /* towards the far end of the right hand side */);
|
||||
} else {
|
||||
this.statusItem.update(statusProperties);
|
||||
this.notificationsCenterStatusItem.update(statusProperties);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,14 +80,96 @@ export class NotificationsStatus extends Disposable {
|
||||
return localize('zeroNotifications', "No Notifications");
|
||||
}
|
||||
|
||||
if (this.count === 0) {
|
||||
if (this.currentNotifications.size === 0) {
|
||||
return localize('noNotifications', "No New Notifications");
|
||||
}
|
||||
|
||||
if (this.count === 1) {
|
||||
if (this.currentNotifications.size === 1) {
|
||||
return localize('oneNotification', "1 New Notification");
|
||||
}
|
||||
|
||||
return localize('notifications', "{0} New Notifications", this.count);
|
||||
return localize('notifications', "{0} New Notifications", this.currentNotifications.size);
|
||||
}
|
||||
|
||||
update(isCenterVisible: boolean): void {
|
||||
if (this.isNotificationsCenterVisible !== isCenterVisible) {
|
||||
this.isNotificationsCenterVisible = isCenterVisible;
|
||||
|
||||
// Showing the notification center resets the counter to 0
|
||||
this.currentNotifications.clear();
|
||||
this.updateNotificationsCenterStatusItem();
|
||||
}
|
||||
}
|
||||
|
||||
private onDidStatusMessageChange(e: IStatusMessageChangeEvent): void {
|
||||
const statusItem = e.item;
|
||||
|
||||
switch (e.kind) {
|
||||
|
||||
// Show status notification
|
||||
case StatusMessageChangeType.ADD:
|
||||
this.doSetStatusMessage(statusItem);
|
||||
|
||||
break;
|
||||
|
||||
// Hide status notification (if its still the current one)
|
||||
case StatusMessageChangeType.REMOVE:
|
||||
if (this.currentStatusMessage && this.currentStatusMessage[0] === statusItem) {
|
||||
dispose(this.currentStatusMessage[1]);
|
||||
this.currentStatusMessage = undefined;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private doSetStatusMessage(item: IStatusMessageViewItem): void {
|
||||
const message = item.message;
|
||||
|
||||
const showAfter = item.options && typeof item.options.showAfter === 'number' ? item.options.showAfter : 0;
|
||||
const hideAfter = item.options && typeof item.options.hideAfter === 'number' ? item.options.hideAfter : -1;
|
||||
|
||||
// Dismiss any previous
|
||||
if (this.currentStatusMessage) {
|
||||
dispose(this.currentStatusMessage[1]);
|
||||
}
|
||||
|
||||
// Create new
|
||||
let statusMessageEntry: IStatusbarEntryAccessor;
|
||||
let showHandle: any = setTimeout(() => {
|
||||
statusMessageEntry = this.statusbarService.addEntry(
|
||||
{ text: message },
|
||||
'status.message',
|
||||
localize('status.message', "Status Message"),
|
||||
StatusbarAlignment.LEFT,
|
||||
-Number.MAX_VALUE /* far right on left hand side */
|
||||
);
|
||||
showHandle = null;
|
||||
}, showAfter);
|
||||
|
||||
// Dispose function takes care of timeouts and actual entry
|
||||
let hideHandle: any;
|
||||
const statusMessageDispose = {
|
||||
dispose: () => {
|
||||
if (showHandle) {
|
||||
clearTimeout(showHandle);
|
||||
}
|
||||
|
||||
if (hideHandle) {
|
||||
clearTimeout(hideHandle);
|
||||
}
|
||||
|
||||
if (statusMessageEntry) {
|
||||
statusMessageEntry.dispose();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (hideAfter > 0) {
|
||||
hideHandle = setTimeout(() => statusMessageDispose.dispose(), hideAfter);
|
||||
}
|
||||
|
||||
// Remember as current status message
|
||||
this.currentStatusMessage = [item, statusMessageDispose];
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@ import { IWorkbenchLayoutService, Parts, Position } from 'vs/workbench/services/
|
||||
import { ActivityAction } from 'vs/workbench/browser/parts/compositeBarActions';
|
||||
import { IActivity } from 'vs/workbench/common/activity';
|
||||
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
import { ActivePanelContext, PanelPositionContext } from 'vs/workbench/common/panel';
|
||||
|
||||
export class ClosePanelAction extends Action {
|
||||
|
||||
@@ -92,8 +93,8 @@ export class TogglePanelPositionAction extends Action {
|
||||
static readonly ID = 'workbench.action.togglePanelPosition';
|
||||
static readonly LABEL = nls.localize('toggledPanelPosition', "Toggle Panel Position");
|
||||
|
||||
private static readonly MOVE_TO_RIGHT_LABEL = nls.localize('moveToRight', "Move Panel Right");
|
||||
private static readonly MOVE_TO_BOTTOM_LABEL = nls.localize('moveToBottom', "Move Panel to Bottom");
|
||||
static readonly MOVE_TO_RIGHT_LABEL = nls.localize('moveToRight', "Move Panel Right");
|
||||
static readonly MOVE_TO_BOTTOM_LABEL = nls.localize('moveToBottom', "Move Panel to Bottom");
|
||||
|
||||
private toDispose: IDisposable[];
|
||||
|
||||
@@ -271,16 +272,28 @@ MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, {
|
||||
group: '2_workbench_layout',
|
||||
command: {
|
||||
id: TogglePanelAction.ID,
|
||||
title: nls.localize({ key: 'miTogglePanel', comment: ['&& denotes a mnemonic'] }, "Toggle &&Panel")
|
||||
title: nls.localize({ key: 'miShowPanel', comment: ['&& denotes a mnemonic'] }, "Show &&Panel"),
|
||||
toggled: ActivePanelContext
|
||||
},
|
||||
order: 5
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, {
|
||||
group: '2_workbench_layout',
|
||||
group: '3_workbench_layout_move',
|
||||
command: {
|
||||
id: TogglePanelPositionAction.ID,
|
||||
title: TogglePanelPositionAction.LABEL
|
||||
title: TogglePanelPositionAction.MOVE_TO_RIGHT_LABEL
|
||||
},
|
||||
order: 3
|
||||
when: PanelPositionContext.isEqualTo('bottom'),
|
||||
order: 5
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, {
|
||||
group: '3_workbench_layout_move',
|
||||
command: {
|
||||
id: TogglePanelPositionAction.ID,
|
||||
title: TogglePanelPositionAction.MOVE_TO_BOTTOM_LABEL
|
||||
},
|
||||
when: PanelPositionContext.isEqualTo('right'),
|
||||
order: 5
|
||||
});
|
||||
|
||||
@@ -70,7 +70,7 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
|
||||
private panelFocusContextKey: IContextKey<boolean>;
|
||||
|
||||
private compositeBar: CompositeBar;
|
||||
private compositeActions: { [compositeId: string]: { activityAction: PanelActivityAction, pinnedAction: ToggleCompositePinnedAction } } = Object.create(null);
|
||||
private compositeActions: Map<string, { activityAction: PanelActivityAction, pinnedAction: ToggleCompositePinnedAction }> = new Map();
|
||||
|
||||
private blockOpeningPanel: boolean;
|
||||
private dimension: Dimension;
|
||||
@@ -245,7 +245,7 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
|
||||
.sort((p1, p2) => pinnedCompositeIds.indexOf(p1.id) - pinnedCompositeIds.indexOf(p2.id));
|
||||
}
|
||||
|
||||
protected getActions(): IAction[] {
|
||||
protected getActions(): ReadonlyArray<IAction> {
|
||||
return [
|
||||
this.instantiationService.createInstance(ToggleMaximizedPanelAction, ToggleMaximizedPanelAction.ID, ToggleMaximizedPanelAction.LABEL),
|
||||
this.instantiationService.createInstance(ClosePanelAction, ClosePanelAction.ID, ClosePanelAction.LABEL)
|
||||
@@ -261,6 +261,11 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
|
||||
}
|
||||
|
||||
hideActivePanel(): void {
|
||||
// First check if panel is visible and hide if so
|
||||
if (this.layoutService.isVisible(Parts.PANEL_PART)) {
|
||||
this.layoutService.setPanelHidden(true);
|
||||
}
|
||||
|
||||
this.hideActiveComposite();
|
||||
}
|
||||
|
||||
@@ -311,13 +316,14 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
|
||||
}
|
||||
|
||||
private getCompositeActions(compositeId: string): { activityAction: PanelActivityAction, pinnedAction: ToggleCompositePinnedAction } {
|
||||
let compositeActions = this.compositeActions[compositeId];
|
||||
let compositeActions = this.compositeActions.get(compositeId);
|
||||
if (!compositeActions) {
|
||||
compositeActions = {
|
||||
activityAction: this.instantiationService.createInstance(PanelActivityAction, this.getPanel(compositeId)),
|
||||
pinnedAction: new ToggleCompositePinnedAction(this.getPanel(compositeId), this.compositeBar)
|
||||
};
|
||||
this.compositeActions[compositeId] = compositeActions;
|
||||
|
||||
this.compositeActions.set(compositeId, compositeActions);
|
||||
}
|
||||
|
||||
return compositeActions;
|
||||
@@ -325,11 +331,11 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
|
||||
|
||||
protected removeComposite(compositeId: string): boolean {
|
||||
if (super.removeComposite(compositeId)) {
|
||||
const compositeActions = this.compositeActions[compositeId];
|
||||
const compositeActions = this.compositeActions.get(compositeId);
|
||||
if (compositeActions) {
|
||||
compositeActions.activityAction.dispose();
|
||||
compositeActions.pinnedAction.dispose();
|
||||
delete this.compositeActions[compositeId];
|
||||
this.compositeActions.delete(compositeId);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -27,7 +27,7 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment'
|
||||
import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { Button } from 'vs/base/browser/ui/button/button';
|
||||
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { dispose, Disposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import Severity from 'vs/base/common/severity';
|
||||
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
import { IContextKeyService, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
@@ -97,7 +97,7 @@ type Visibilities = {
|
||||
customButton?: boolean;
|
||||
};
|
||||
|
||||
class QuickInput implements IQuickInput {
|
||||
class QuickInput extends Disposable implements IQuickInput {
|
||||
|
||||
private _title: string;
|
||||
private _steps: number;
|
||||
@@ -109,18 +109,17 @@ class QuickInput implements IQuickInput {
|
||||
private _ignoreFocusOut = false;
|
||||
private _buttons: IQuickInputButton[] = [];
|
||||
private buttonsUpdated = false;
|
||||
private onDidTriggerButtonEmitter = new Emitter<IQuickInputButton>();
|
||||
private onDidHideEmitter = new Emitter<void>();
|
||||
private readonly onDidTriggerButtonEmitter = this._register(new Emitter<IQuickInputButton>());
|
||||
private readonly onDidHideEmitter = this._register(new Emitter<void>());
|
||||
|
||||
protected visibleDisposables: IDisposable[] = [];
|
||||
protected disposables: IDisposable[] = [
|
||||
this.onDidTriggerButtonEmitter,
|
||||
this.onDidHideEmitter,
|
||||
];
|
||||
protected readonly visibleDisposables = this._register(new DisposableStore());
|
||||
|
||||
private busyDelay: TimeoutTimer | null;
|
||||
|
||||
constructor(protected ui: QuickInputUI) {
|
||||
constructor(
|
||||
protected ui: QuickInputUI
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
get title() {
|
||||
@@ -202,7 +201,7 @@ class QuickInput implements IQuickInput {
|
||||
if (this.visible) {
|
||||
return;
|
||||
}
|
||||
this.visibleDisposables.push(
|
||||
this.visibleDisposables.add(
|
||||
this.ui.onDidTriggerButton(button => {
|
||||
if (this.buttons.indexOf(button) !== -1) {
|
||||
this.onDidTriggerButtonEmitter.fire(button);
|
||||
@@ -223,7 +222,7 @@ class QuickInput implements IQuickInput {
|
||||
|
||||
didHide(): void {
|
||||
this.visible = false;
|
||||
this.visibleDisposables = dispose(this.visibleDisposables);
|
||||
this.visibleDisposables.clear();
|
||||
this.onDidHideEmitter.fire();
|
||||
}
|
||||
|
||||
@@ -317,7 +316,7 @@ class QuickInput implements IQuickInput {
|
||||
|
||||
public dispose(): void {
|
||||
this.hide();
|
||||
this.disposables = dispose(this.disposables);
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -327,9 +326,9 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
|
||||
|
||||
private _value = '';
|
||||
private _placeholder: string;
|
||||
private onDidChangeValueEmitter = new Emitter<string>();
|
||||
private onDidAcceptEmitter = new Emitter<void>();
|
||||
private onDidCustomEmitter = new Emitter<void>();
|
||||
private readonly onDidChangeValueEmitter = this._register(new Emitter<string>());
|
||||
private readonly onDidAcceptEmitter = this._register(new Emitter<void>());
|
||||
private readonly onDidCustomEmitter = this._register(new Emitter<void>());
|
||||
private _items: Array<T | IQuickPickSeparator> = [];
|
||||
private itemsUpdated = false;
|
||||
private _canSelectMany = false;
|
||||
@@ -340,12 +339,12 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
|
||||
private _activeItems: T[] = [];
|
||||
private activeItemsUpdated = false;
|
||||
private activeItemsToConfirm: T[] | null = [];
|
||||
private onDidChangeActiveEmitter = new Emitter<T[]>();
|
||||
private readonly onDidChangeActiveEmitter = this._register(new Emitter<T[]>());
|
||||
private _selectedItems: T[] = [];
|
||||
private selectedItemsUpdated = false;
|
||||
private selectedItemsToConfirm: T[] | null = [];
|
||||
private onDidChangeSelectionEmitter = new Emitter<T[]>();
|
||||
private onDidTriggerItemButtonEmitter = new Emitter<IQuickPickItemButtonEvent<T>>();
|
||||
private readonly onDidChangeSelectionEmitter = this._register(new Emitter<T[]>());
|
||||
private readonly onDidTriggerItemButtonEmitter = this._register(new Emitter<IQuickPickItemButtonEvent<T>>());
|
||||
private _valueSelection: Readonly<[number, number]>;
|
||||
private valueSelectionUpdated = true;
|
||||
private _validationMessage: string;
|
||||
@@ -356,17 +355,6 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
|
||||
|
||||
quickNavigate: IQuickNavigateConfiguration;
|
||||
|
||||
constructor(ui: QuickInputUI) {
|
||||
super(ui);
|
||||
this.disposables.push(
|
||||
this.onDidChangeValueEmitter,
|
||||
this.onDidAcceptEmitter,
|
||||
this.onDidCustomEmitter,
|
||||
this.onDidChangeActiveEmitter,
|
||||
this.onDidChangeSelectionEmitter,
|
||||
this.onDidTriggerItemButtonEmitter,
|
||||
);
|
||||
}
|
||||
|
||||
get value() {
|
||||
return this._value;
|
||||
@@ -542,7 +530,7 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
|
||||
|
||||
show() {
|
||||
if (!this.visible) {
|
||||
this.visibleDisposables.push(
|
||||
this.visibleDisposables.add(
|
||||
this.ui.inputBox.onDidChange(value => {
|
||||
if (value === this.value) {
|
||||
return;
|
||||
@@ -551,105 +539,104 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
|
||||
this.ui.list.filter(this.ui.inputBox.value);
|
||||
this.trySelectFirst();
|
||||
this.onDidChangeValueEmitter.fire(value);
|
||||
}),
|
||||
this.ui.inputBox.onMouseDown(event => {
|
||||
if (!this.autoFocusOnList) {
|
||||
this.ui.list.clearFocus();
|
||||
}
|
||||
}),
|
||||
this.ui.inputBox.onKeyDown(event => {
|
||||
switch (event.keyCode) {
|
||||
case KeyCode.DownArrow:
|
||||
this.ui.list.focus('Next');
|
||||
if (this.canSelectMany) {
|
||||
this.ui.list.domFocus();
|
||||
}
|
||||
event.preventDefault();
|
||||
break;
|
||||
case KeyCode.UpArrow:
|
||||
if (this.ui.list.getFocusedElements().length) {
|
||||
this.ui.list.focus('Previous');
|
||||
} else {
|
||||
this.ui.list.focus('Last');
|
||||
}
|
||||
if (this.canSelectMany) {
|
||||
this.ui.list.domFocus();
|
||||
}
|
||||
event.preventDefault();
|
||||
break;
|
||||
case KeyCode.PageDown:
|
||||
if (this.ui.list.getFocusedElements().length) {
|
||||
this.ui.list.focus('NextPage');
|
||||
} else {
|
||||
this.ui.list.focus('First');
|
||||
}
|
||||
if (this.canSelectMany) {
|
||||
this.ui.list.domFocus();
|
||||
}
|
||||
event.preventDefault();
|
||||
break;
|
||||
case KeyCode.PageUp:
|
||||
if (this.ui.list.getFocusedElements().length) {
|
||||
this.ui.list.focus('PreviousPage');
|
||||
} else {
|
||||
this.ui.list.focus('Last');
|
||||
}
|
||||
if (this.canSelectMany) {
|
||||
this.ui.list.domFocus();
|
||||
}
|
||||
event.preventDefault();
|
||||
break;
|
||||
}
|
||||
}),
|
||||
this.ui.onDidAccept(() => {
|
||||
if (!this.canSelectMany && this.activeItems[0]) {
|
||||
this._selectedItems = [this.activeItems[0]];
|
||||
this.onDidChangeSelectionEmitter.fire(this.selectedItems);
|
||||
}
|
||||
this.onDidAcceptEmitter.fire(undefined);
|
||||
}),
|
||||
this.ui.onDidCustom(() => {
|
||||
this.onDidCustomEmitter.fire(undefined);
|
||||
}),
|
||||
this.ui.list.onDidChangeFocus(focusedItems => {
|
||||
if (this.activeItemsUpdated) {
|
||||
return; // Expect another event.
|
||||
}
|
||||
if (this.activeItemsToConfirm !== this._activeItems && equals(focusedItems, this._activeItems, (a, b) => a === b)) {
|
||||
return;
|
||||
}
|
||||
this._activeItems = focusedItems as T[];
|
||||
this.onDidChangeActiveEmitter.fire(focusedItems as T[]);
|
||||
}),
|
||||
this.ui.list.onDidChangeSelection(selectedItems => {
|
||||
if (this.canSelectMany) {
|
||||
if (selectedItems.length) {
|
||||
this.ui.list.setSelectedElements([]);
|
||||
}));
|
||||
this.visibleDisposables.add(this.ui.inputBox.onMouseDown(event => {
|
||||
if (!this.autoFocusOnList) {
|
||||
this.ui.list.clearFocus();
|
||||
}
|
||||
}));
|
||||
this.visibleDisposables.add(this.ui.inputBox.onKeyDown(event => {
|
||||
switch (event.keyCode) {
|
||||
case KeyCode.DownArrow:
|
||||
this.ui.list.focus('Next');
|
||||
if (this.canSelectMany) {
|
||||
this.ui.list.domFocus();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (this.selectedItemsToConfirm !== this._selectedItems && equals(selectedItems, this._selectedItems, (a, b) => a === b)) {
|
||||
return;
|
||||
}
|
||||
this._selectedItems = selectedItems as T[];
|
||||
this.onDidChangeSelectionEmitter.fire(selectedItems as T[]);
|
||||
event.preventDefault();
|
||||
break;
|
||||
case KeyCode.UpArrow:
|
||||
if (this.ui.list.getFocusedElements().length) {
|
||||
this.ui.list.focus('Previous');
|
||||
} else {
|
||||
this.ui.list.focus('Last');
|
||||
}
|
||||
if (this.canSelectMany) {
|
||||
this.ui.list.domFocus();
|
||||
}
|
||||
event.preventDefault();
|
||||
break;
|
||||
case KeyCode.PageDown:
|
||||
if (this.ui.list.getFocusedElements().length) {
|
||||
this.ui.list.focus('NextPage');
|
||||
} else {
|
||||
this.ui.list.focus('First');
|
||||
}
|
||||
if (this.canSelectMany) {
|
||||
this.ui.list.domFocus();
|
||||
}
|
||||
event.preventDefault();
|
||||
break;
|
||||
case KeyCode.PageUp:
|
||||
if (this.ui.list.getFocusedElements().length) {
|
||||
this.ui.list.focus('PreviousPage');
|
||||
} else {
|
||||
this.ui.list.focus('Last');
|
||||
}
|
||||
if (this.canSelectMany) {
|
||||
this.ui.list.domFocus();
|
||||
}
|
||||
event.preventDefault();
|
||||
break;
|
||||
}
|
||||
}));
|
||||
this.visibleDisposables.add(this.ui.onDidAccept(() => {
|
||||
if (!this.canSelectMany && this.activeItems[0]) {
|
||||
this._selectedItems = [this.activeItems[0]];
|
||||
this.onDidChangeSelectionEmitter.fire(this.selectedItems);
|
||||
}
|
||||
this.onDidAcceptEmitter.fire(undefined);
|
||||
}));
|
||||
this.visibleDisposables.add(this.ui.onDidCustom(() => {
|
||||
this.onDidCustomEmitter.fire(undefined);
|
||||
}));
|
||||
this.visibleDisposables.add(this.ui.list.onDidChangeFocus(focusedItems => {
|
||||
if (this.activeItemsUpdated) {
|
||||
return; // Expect another event.
|
||||
}
|
||||
if (this.activeItemsToConfirm !== this._activeItems && equals(focusedItems, this._activeItems, (a, b) => a === b)) {
|
||||
return;
|
||||
}
|
||||
this._activeItems = focusedItems as T[];
|
||||
this.onDidChangeActiveEmitter.fire(focusedItems as T[]);
|
||||
}));
|
||||
this.visibleDisposables.add(this.ui.list.onDidChangeSelection(selectedItems => {
|
||||
if (this.canSelectMany) {
|
||||
if (selectedItems.length) {
|
||||
this.onDidAcceptEmitter.fire(undefined);
|
||||
this.ui.list.setSelectedElements([]);
|
||||
}
|
||||
}),
|
||||
this.ui.list.onChangedCheckedElements(checkedItems => {
|
||||
if (!this.canSelectMany) {
|
||||
return;
|
||||
}
|
||||
if (this.selectedItemsToConfirm !== this._selectedItems && equals(checkedItems, this._selectedItems, (a, b) => a === b)) {
|
||||
return;
|
||||
}
|
||||
this._selectedItems = checkedItems as T[];
|
||||
this.onDidChangeSelectionEmitter.fire(checkedItems as T[]);
|
||||
}),
|
||||
this.ui.list.onButtonTriggered(event => this.onDidTriggerItemButtonEmitter.fire(event as IQuickPickItemButtonEvent<T>)),
|
||||
this.registerQuickNavigation()
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (this.selectedItemsToConfirm !== this._selectedItems && equals(selectedItems, this._selectedItems, (a, b) => a === b)) {
|
||||
return;
|
||||
}
|
||||
this._selectedItems = selectedItems as T[];
|
||||
this.onDidChangeSelectionEmitter.fire(selectedItems as T[]);
|
||||
if (selectedItems.length) {
|
||||
this.onDidAcceptEmitter.fire(undefined);
|
||||
}
|
||||
}));
|
||||
this.visibleDisposables.add(this.ui.list.onChangedCheckedElements(checkedItems => {
|
||||
if (!this.canSelectMany) {
|
||||
return;
|
||||
}
|
||||
if (this.selectedItemsToConfirm !== this._selectedItems && equals(checkedItems, this._selectedItems, (a, b) => a === b)) {
|
||||
return;
|
||||
}
|
||||
this._selectedItems = checkedItems as T[];
|
||||
this.onDidChangeSelectionEmitter.fire(checkedItems as T[]);
|
||||
}));
|
||||
this.visibleDisposables.add(this.ui.list.onButtonTriggered(event => this.onDidTriggerItemButtonEmitter.fire(event as IQuickPickItemButtonEvent<T>)));
|
||||
this.visibleDisposables.add(this.registerQuickNavigation());
|
||||
this.valueSelectionUpdated = true;
|
||||
}
|
||||
super.show(); // TODO: Why have show() bubble up while update() trickles down? (Could move setComboboxAccessibility() here.)
|
||||
@@ -784,16 +771,8 @@ class InputBox extends QuickInput implements IInputBox {
|
||||
private _prompt: string;
|
||||
private noValidationMessage = InputBox.noPromptMessage;
|
||||
private _validationMessage: string;
|
||||
private onDidValueChangeEmitter = new Emitter<string>();
|
||||
private onDidAcceptEmitter = new Emitter<void>();
|
||||
|
||||
constructor(ui: QuickInputUI) {
|
||||
super(ui);
|
||||
this.disposables.push(
|
||||
this.onDidValueChangeEmitter,
|
||||
this.onDidAcceptEmitter,
|
||||
);
|
||||
}
|
||||
private readonly onDidValueChangeEmitter = this._register(new Emitter<string>());
|
||||
private readonly onDidAcceptEmitter = this._register(new Emitter<void>());
|
||||
|
||||
get value() {
|
||||
return this._value;
|
||||
@@ -849,22 +828,21 @@ class InputBox extends QuickInput implements IInputBox {
|
||||
this.update();
|
||||
}
|
||||
|
||||
onDidChangeValue = this.onDidValueChangeEmitter.event;
|
||||
readonly onDidChangeValue = this.onDidValueChangeEmitter.event;
|
||||
|
||||
onDidAccept = this.onDidAcceptEmitter.event;
|
||||
readonly onDidAccept = this.onDidAcceptEmitter.event;
|
||||
|
||||
show() {
|
||||
if (!this.visible) {
|
||||
this.visibleDisposables.push(
|
||||
this.visibleDisposables.add(
|
||||
this.ui.inputBox.onDidChange(value => {
|
||||
if (value === this.value) {
|
||||
return;
|
||||
}
|
||||
this._value = value;
|
||||
this.onDidValueChangeEmitter.fire(value);
|
||||
}),
|
||||
this.ui.onDidAccept(() => this.onDidAcceptEmitter.fire(undefined)),
|
||||
);
|
||||
}));
|
||||
this.visibleDisposables.add(this.ui.onDidAccept(() => this.onDidAcceptEmitter.fire(undefined)));
|
||||
this.valueSelectionUpdated = true;
|
||||
}
|
||||
super.show();
|
||||
@@ -920,10 +898,10 @@ export class QuickInputService extends Component implements IQuickInputService {
|
||||
private enabled = true;
|
||||
private inQuickOpenWidgets: Record<string, boolean> = {};
|
||||
private inQuickOpenContext: IContextKey<boolean>;
|
||||
private contexts: { [id: string]: IContextKey<boolean>; } = Object.create(null);
|
||||
private onDidAcceptEmitter = this._register(new Emitter<void>());
|
||||
private onDidCustomEmitter = this._register(new Emitter<void>());
|
||||
private onDidTriggerButtonEmitter = this._register(new Emitter<IQuickInputButton>());
|
||||
private contexts: Map<string, IContextKey<boolean>> = new Map();
|
||||
private readonly onDidAcceptEmitter = this._register(new Emitter<void>());
|
||||
private readonly onDidCustomEmitter = this._register(new Emitter<void>());
|
||||
private readonly onDidTriggerButtonEmitter = this._register(new Emitter<IQuickInputButton>());
|
||||
private keyMods: Writeable<IKeyMods> = { ctrlCmd: false, alt: false };
|
||||
|
||||
private controller: QuickInput | null = null;
|
||||
@@ -969,11 +947,11 @@ export class QuickInputService extends Component implements IQuickInputService {
|
||||
private setContextKey(id?: string) {
|
||||
let key: IContextKey<boolean> | undefined;
|
||||
if (id) {
|
||||
key = this.contexts[id];
|
||||
key = this.contexts.get(id);
|
||||
if (!key) {
|
||||
key = new RawContextKey<boolean>(id, false)
|
||||
.bindTo(this.contextKeyService);
|
||||
this.contexts[id] = key;
|
||||
this.contexts.set(id, key);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -989,11 +967,11 @@ export class QuickInputService extends Component implements IQuickInputService {
|
||||
}
|
||||
|
||||
private resetContextKeys() {
|
||||
for (const key in this.contexts) {
|
||||
if (this.contexts[key].get()) {
|
||||
this.contexts[key].reset();
|
||||
this.contexts.forEach(context => {
|
||||
if (context.get()) {
|
||||
context.reset();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private registerKeyModsListeners() {
|
||||
@@ -1128,6 +1106,9 @@ export class QuickInputService extends Component implements IQuickInputService {
|
||||
this.hide(true);
|
||||
}
|
||||
}));
|
||||
this._register(dom.addDisposableListener(container, dom.EventType.FOCUS, (e: FocusEvent) => {
|
||||
inputBox.setFocus();
|
||||
}));
|
||||
this._register(dom.addDisposableListener(container, dom.EventType.KEY_DOWN, (e: KeyboardEvent) => {
|
||||
const event = new StandardKeyboardEvent(e);
|
||||
switch (event.keyCode) {
|
||||
|
||||
@@ -8,25 +8,24 @@ import * as dom from 'vs/base/browser/dom';
|
||||
import { InputBox, IRange, MessageType } from 'vs/base/browser/ui/inputbox/inputBox';
|
||||
import { inputBackground, inputForeground, inputBorder, inputValidationInfoBackground, inputValidationInfoForeground, inputValidationInfoBorder, inputValidationWarningBackground, inputValidationWarningForeground, inputValidationWarningBorder, inputValidationErrorBackground, inputValidationErrorForeground, inputValidationErrorBorder } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { ITheme } from 'vs/platform/theme/common/themeService';
|
||||
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IDisposable, Disposable } from 'vs/base/common/lifecycle';
|
||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import Severity from 'vs/base/common/severity';
|
||||
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||
|
||||
const $ = dom.$;
|
||||
|
||||
export class QuickInputBox {
|
||||
export class QuickInputBox extends Disposable {
|
||||
|
||||
private container: HTMLElement;
|
||||
private inputBox: InputBox;
|
||||
private disposables: IDisposable[] = [];
|
||||
|
||||
constructor(
|
||||
private parent: HTMLElement
|
||||
) {
|
||||
super();
|
||||
this.container = dom.append(this.parent, $('.quick-input-box'));
|
||||
this.inputBox = new InputBox(this.container, undefined);
|
||||
this.disposables.push(this.inputBox);
|
||||
this.inputBox = this._register(new InputBox(this.container, undefined));
|
||||
}
|
||||
|
||||
onKeyDown = (handler: (event: StandardKeyboardEvent) => void): IDisposable => {
|
||||
@@ -129,8 +128,4 @@ export class QuickInputBox {
|
||||
inputValidationErrorBorder: theme.getColor(inputValidationErrorBorder),
|
||||
});
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this.disposables = dispose(this.disposables);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,9 +74,9 @@ export class QuickOpenController extends Component implements IQuickOpenService
|
||||
private lastInputValue: string;
|
||||
private lastSubmittedInputValue: string;
|
||||
private quickOpenWidget: QuickOpenWidget;
|
||||
private mapResolvedHandlersToPrefix: { [prefix: string]: Promise<QuickOpenHandler>; } = Object.create(null);
|
||||
private mapContextKeyToContext: { [id: string]: IContextKey<boolean>; } = Object.create(null);
|
||||
private handlerOnOpenCalled: { [prefix: string]: boolean; } = Object.create(null);
|
||||
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 actionProvider = new ContributableActionProvider();
|
||||
@@ -258,13 +258,13 @@ export class QuickOpenController extends Component implements IQuickOpenService
|
||||
this.cancelPendingGetResultsInvocation();
|
||||
|
||||
// Pass to handlers
|
||||
for (let prefix in this.mapResolvedHandlersToPrefix) {
|
||||
this.mapResolvedHandlersToPrefix[prefix].then(handler => {
|
||||
this.handlerOnOpenCalled[prefix] = false;
|
||||
this.mapResolvedHandlersToPrefix.forEach((promise, prefix) => {
|
||||
promise.then(handler => {
|
||||
this.handlerOnOpenCalled.delete(prefix);
|
||||
|
||||
handler.onClose(reason === HideReason.CANCELED); // Don't check if onOpen was called to preserve old behaviour for now
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Complete promises that are waiting
|
||||
while (this.promisesToCompleteOnHide.length) {
|
||||
@@ -294,16 +294,16 @@ export class QuickOpenController extends Component implements IQuickOpenService
|
||||
}
|
||||
|
||||
private resetQuickOpenContextKeys(): void {
|
||||
Object.keys(this.mapContextKeyToContext).forEach(k => this.mapContextKeyToContext[k].reset());
|
||||
this.mapContextKeyToContext.forEach(context => context.reset());
|
||||
}
|
||||
|
||||
private setQuickOpenContextKey(id?: string): void {
|
||||
let key: IContextKey<boolean> | undefined;
|
||||
if (id) {
|
||||
key = this.mapContextKeyToContext[id];
|
||||
key = this.mapContextKeyToContext.get(id);
|
||||
if (!key) {
|
||||
key = new RawContextKey<boolean>(id, false).bindTo(this.contextKeyService);
|
||||
this.mapContextKeyToContext[id] = key;
|
||||
this.mapContextKeyToContext.set(id, key);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -454,14 +454,16 @@ export class QuickOpenController extends Component implements IQuickOpenService
|
||||
const previousInput = this.quickOpenWidget.getInput();
|
||||
const wasShowingHistory = previousInput && previousInput.entries && previousInput.entries.some(e => e instanceof EditorHistoryEntry || e instanceof EditorHistoryEntryGroup);
|
||||
if (wasShowingHistory || matchingHistoryEntries.length > 0) {
|
||||
if (resolvedHandler.hasShortResponseTime()) {
|
||||
await timeout(QuickOpenController.MAX_SHORT_RESPONSE_TIME);
|
||||
}
|
||||
(async () => {
|
||||
if (resolvedHandler.hasShortResponseTime()) {
|
||||
await timeout(QuickOpenController.MAX_SHORT_RESPONSE_TIME);
|
||||
}
|
||||
|
||||
if (!token.isCancellationRequested && !inputSet) {
|
||||
this.quickOpenWidget.setInput(quickOpenModel, { autoFocusFirstEntry: true });
|
||||
inputSet = true;
|
||||
}
|
||||
if (!token.isCancellationRequested && !inputSet) {
|
||||
this.quickOpenWidget.setInput(quickOpenModel, { autoFocusFirstEntry: true });
|
||||
inputSet = true;
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
// Get results
|
||||
@@ -523,7 +525,7 @@ export class QuickOpenController extends Component implements IQuickOpenService
|
||||
const model = new QuickOpenModel([new PlaceholderQuickOpenEntry(placeHolderLabel)], this.actionProvider);
|
||||
this.showModel(model, resolvedHandler.getAutoFocus(value, { model, quickNavigateConfiguration: this.quickOpenWidget.getQuickNavigateConfiguration() }), types.withNullAsUndefined(resolvedHandler.getAriaLabel()));
|
||||
|
||||
return Promise.resolve(undefined);
|
||||
return;
|
||||
}
|
||||
|
||||
// Support extra class from handler
|
||||
@@ -579,38 +581,45 @@ export class QuickOpenController extends Component implements IQuickOpenService
|
||||
return mapEntryToPath;
|
||||
}
|
||||
|
||||
private resolveHandler(handler: QuickOpenHandlerDescriptor): Promise<QuickOpenHandler> {
|
||||
let result = this._resolveHandler(handler);
|
||||
private async resolveHandler(handler: QuickOpenHandlerDescriptor): Promise<QuickOpenHandler> {
|
||||
let result = this.doResolveHandler(handler);
|
||||
|
||||
const id = handler.getId();
|
||||
if (!this.handlerOnOpenCalled[id]) {
|
||||
if (!this.handlerOnOpenCalled.has(id)) {
|
||||
const original = result;
|
||||
this.handlerOnOpenCalled[id] = true;
|
||||
result = this.mapResolvedHandlersToPrefix[id] = original.then(resolved => {
|
||||
this.mapResolvedHandlersToPrefix[id] = original;
|
||||
this.handlerOnOpenCalled.add(id);
|
||||
result = original.then(resolved => {
|
||||
this.mapResolvedHandlersToPrefix.set(id, original);
|
||||
resolved.onOpen();
|
||||
|
||||
return resolved;
|
||||
});
|
||||
|
||||
this.mapResolvedHandlersToPrefix.set(id, result);
|
||||
}
|
||||
|
||||
return result.then<QuickOpenHandler>(null, (error) => {
|
||||
delete this.mapResolvedHandlersToPrefix[id];
|
||||
try {
|
||||
return await result;
|
||||
} catch (error) {
|
||||
this.mapResolvedHandlersToPrefix.delete(id);
|
||||
|
||||
return Promise.reject(new Error(`Unable to instantiate quick open handler ${handler.getId()}: ${JSON.stringify(error)}`));
|
||||
});
|
||||
throw new Error(`Unable to instantiate quick open handler ${handler.getId()}: ${JSON.stringify(error)}`);
|
||||
}
|
||||
}
|
||||
|
||||
private _resolveHandler(handler: QuickOpenHandlerDescriptor): Promise<QuickOpenHandler> {
|
||||
private doResolveHandler(handler: QuickOpenHandlerDescriptor): Promise<QuickOpenHandler> {
|
||||
const id = handler.getId();
|
||||
|
||||
// Return Cached
|
||||
if (this.mapResolvedHandlersToPrefix[id]) {
|
||||
return this.mapResolvedHandlersToPrefix[id];
|
||||
if (this.mapResolvedHandlersToPrefix.has(id)) {
|
||||
return this.mapResolvedHandlersToPrefix.get(id)!;
|
||||
}
|
||||
|
||||
// Otherwise load and create
|
||||
return this.mapResolvedHandlersToPrefix[id] = Promise.resolve(handler.instantiate(this.instantiationService));
|
||||
const result = Promise.resolve(handler.instantiate(this.instantiationService));
|
||||
this.mapResolvedHandlersToPrefix.set(id, result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
layout(dimension: Dimension): void {
|
||||
|
||||
@@ -191,7 +191,7 @@ export class SidebarPart extends CompositePart<Viewlet> implements IViewletServi
|
||||
|
||||
async openViewlet(id: string | undefined, focus?: boolean): Promise<IViewlet | null> {
|
||||
if (typeof id === 'string' && this.getViewlet(id)) {
|
||||
return Promise.resolve(this.doOpenViewlet(id, focus));
|
||||
return this.doOpenViewlet(id, focus);
|
||||
}
|
||||
|
||||
await this.extensionService.whenInstalledExtensionsRegistered();
|
||||
|
||||
@@ -9,20 +9,35 @@
|
||||
width: 100%;
|
||||
height: 22px;
|
||||
font-size: 12px;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.statusbar > .statusbar-item {
|
||||
.monaco-workbench .part.statusbar > .left-items,
|
||||
.monaco-workbench .part.statusbar > .right-items {
|
||||
display: flex;
|
||||
flex-wrap: wrap; /* individual entries should not shrink since we do not control their content */
|
||||
}
|
||||
|
||||
.monaco-workbench .part.statusbar > .right-items {
|
||||
flex-direction: row-reverse; /* ensures that the most right elements wrap last when space is little */
|
||||
}
|
||||
|
||||
.monaco-workbench .part.statusbar > .left-items {
|
||||
flex-grow: 1; /* left items push right items to the far right end */
|
||||
}
|
||||
|
||||
.monaco-workbench .part.statusbar > .items-container > .statusbar-item {
|
||||
display: inline-block;
|
||||
line-height: 22px;
|
||||
height: 100%;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.statusbar > .statusbar-item.has-beak {
|
||||
.monaco-workbench .part.statusbar > .items-container > .statusbar-item.has-beak {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.statusbar > .statusbar-item.has-beak:before {
|
||||
.monaco-workbench .part.statusbar > .items-container > .statusbar-item.has-beak:before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 11px;
|
||||
@@ -33,66 +48,54 @@
|
||||
border-right: 5px solid transparent;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.statusbar > .statusbar-item.left > :first-child,
|
||||
.monaco-workbench .part.statusbar > .statusbar-item.right > :first-child
|
||||
{
|
||||
.monaco-workbench .part.statusbar > .items-container > .statusbar-item > :first-child {
|
||||
margin-right: 3px;
|
||||
margin-left: 3px;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.statusbar > .statusbar-item.right {
|
||||
float: right;
|
||||
.monaco-workbench .part.statusbar > .items-container > .statusbar-item.left.first-visible-item {
|
||||
padding-left: 7px; /* Add padding to the most left status bar item */
|
||||
}
|
||||
|
||||
/* adding padding to the most left status bar item */
|
||||
.monaco-workbench .part.statusbar > .statusbar-item.left:first-child,
|
||||
.monaco-workbench .part.statusbar > .statusbar-item.right + .statusbar-item.left {
|
||||
padding-left: 7px;
|
||||
}
|
||||
|
||||
/* adding padding to the most right status bar item */
|
||||
.monaco-workbench .part.statusbar > .statusbar-item.right:first-child {
|
||||
padding-right: 7px;
|
||||
.monaco-workbench .part.statusbar > .items-container > .statusbar-item.right.last-visible-item {
|
||||
padding-right: 7px; /* Add padding to the most right status bar item */
|
||||
}
|
||||
|
||||
/* tweak appearance for items with background to improve hover feedback */
|
||||
.monaco-workbench .part.statusbar > .statusbar-item.has-background-color.left:first-child,
|
||||
.monaco-workbench .part.statusbar > .statusbar-item.right + .statusbar-item.has-background-color.left,
|
||||
.monaco-workbench .part.statusbar > .statusbar-item.has-background-color.right:first-child {
|
||||
.monaco-workbench .part.statusbar > .items-container > .statusbar-item.has-background-color.left.first-visible-item,
|
||||
.monaco-workbench .part.statusbar > .items-container > .statusbar-item.has-background-color.right.last-visible-item {
|
||||
padding-right: 0;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.statusbar > .statusbar-item.has-background-color.left > :first-child,
|
||||
.monaco-workbench .part.statusbar > .statusbar-item.has-background-color.right > :first-child
|
||||
{
|
||||
.monaco-workbench .part.statusbar > .items-container > .statusbar-item.has-background-color > :first-child {
|
||||
margin-right: 0;
|
||||
margin-left: 0;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.statusbar > .statusbar-item a {
|
||||
.monaco-workbench .part.statusbar > .items-container > .statusbar-item a {
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.statusbar > .statusbar-entry > span {
|
||||
.monaco-workbench .part.statusbar > .items-container > .statusbar-entry > span {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.statusbar > .statusbar-entry > span,
|
||||
.monaco-workbench .part.statusbar > .statusbar-entry > a {
|
||||
.monaco-workbench .part.statusbar > .items-container > .statusbar-entry > span,
|
||||
.monaco-workbench .part.statusbar > .items-container > .statusbar-entry > a {
|
||||
padding: 0 5px 0 5px;
|
||||
white-space: pre; /* gives some degree of styling */
|
||||
}
|
||||
|
||||
.monaco-workbench .part.statusbar > .statusbar-entry span.octicon {
|
||||
.monaco-workbench .part.statusbar > .items-container > .statusbar-entry span.octicon {
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.statusbar > .statusbar-item a:hover {
|
||||
.monaco-workbench .part.statusbar > .items-container > .statusbar-item a:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
@@ -14,11 +14,21 @@ export interface IStatusbarItem {
|
||||
}
|
||||
|
||||
export class StatusbarItemDescriptor {
|
||||
syncDescriptor: SyncDescriptor0<IStatusbarItem>;
|
||||
alignment: StatusbarAlignment;
|
||||
priority: number;
|
||||
readonly syncDescriptor: SyncDescriptor0<IStatusbarItem>;
|
||||
readonly id: string;
|
||||
readonly name: string;
|
||||
readonly alignment: StatusbarAlignment;
|
||||
readonly priority: number;
|
||||
|
||||
constructor(ctor: IConstructorSignature0<IStatusbarItem>, alignment?: StatusbarAlignment, priority?: number) {
|
||||
constructor(
|
||||
ctor: IConstructorSignature0<IStatusbarItem>,
|
||||
id: string,
|
||||
name: string,
|
||||
alignment?: StatusbarAlignment,
|
||||
priority?: number
|
||||
) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.syncDescriptor = createSyncDescriptor(ctor);
|
||||
this.alignment = alignment || StatusbarAlignment.LEFT;
|
||||
this.priority = priority || 0;
|
||||
@@ -26,21 +36,16 @@ export class StatusbarItemDescriptor {
|
||||
}
|
||||
|
||||
export interface IStatusbarRegistry {
|
||||
|
||||
readonly items: StatusbarItemDescriptor[];
|
||||
|
||||
registerStatusbarItem(descriptor: StatusbarItemDescriptor): void;
|
||||
items: StatusbarItemDescriptor[];
|
||||
}
|
||||
|
||||
class StatusbarRegistry implements IStatusbarRegistry {
|
||||
|
||||
private _items: StatusbarItemDescriptor[];
|
||||
|
||||
constructor() {
|
||||
this._items = [];
|
||||
}
|
||||
|
||||
get items(): StatusbarItemDescriptor[] {
|
||||
return this._items;
|
||||
}
|
||||
private readonly _items: StatusbarItemDescriptor[] = [];
|
||||
get items(): StatusbarItemDescriptor[] { return this._items; }
|
||||
|
||||
registerStatusbarItem(descriptor: StatusbarItemDescriptor): void {
|
||||
this._items.push(descriptor);
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import 'vs/css!./media/statusbarpart';
|
||||
import * as nls from 'vs/nls';
|
||||
import { toErrorMessage } from 'vs/base/common/errorMessage';
|
||||
import { dispose, IDisposable, Disposable } from 'vs/base/common/lifecycle';
|
||||
import { dispose, IDisposable, Disposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { OcticonLabel } from 'vs/base/browser/ui/octiconLabel/octiconLabel';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
@@ -17,28 +17,308 @@ import { IInstantiationService, ServiceIdentifier } from 'vs/platform/instantiat
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { StatusbarAlignment, IStatusbarService, IStatusbarEntry, IStatusbarEntryAccessor } from 'vs/platform/statusbar/common/statusbar';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { Action, IAction } from 'vs/base/common/actions';
|
||||
import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector, ThemeColor } from 'vs/platform/theme/common/themeService';
|
||||
import { STATUS_BAR_BACKGROUND, STATUS_BAR_FOREGROUND, STATUS_BAR_NO_FOLDER_BACKGROUND, STATUS_BAR_ITEM_HOVER_BACKGROUND, STATUS_BAR_ITEM_ACTIVE_BACKGROUND, STATUS_BAR_PROMINENT_ITEM_FOREGROUND, STATUS_BAR_PROMINENT_ITEM_BACKGROUND, STATUS_BAR_PROMINENT_ITEM_HOVER_BACKGROUND, STATUS_BAR_BORDER, STATUS_BAR_NO_FOLDER_FOREGROUND, STATUS_BAR_NO_FOLDER_BORDER } from 'vs/workbench/common/theme';
|
||||
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
|
||||
import { contrastBorder } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { isThemeColor } from 'vs/editor/common/editorCommon';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
import { addClass, EventHelper, createStyleSheet, addDisposableListener, addClasses, clearNode, removeClass } from 'vs/base/browser/dom';
|
||||
import { addClass, EventHelper, createStyleSheet, addDisposableListener, addClasses, clearNode, removeClass, EventType, hide, show, removeClasses } from 'vs/base/browser/dom';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
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 { 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';
|
||||
|
||||
interface PendingEntry { entry: IStatusbarEntry; alignment: StatusbarAlignment; priority: number; accessor?: IStatusbarEntryAccessor; }
|
||||
interface IPendingStatusbarEntry {
|
||||
id: string;
|
||||
name: string;
|
||||
entry: IStatusbarEntry;
|
||||
alignment: StatusbarAlignment;
|
||||
priority: number;
|
||||
accessor?: IStatusbarEntryAccessor;
|
||||
}
|
||||
|
||||
interface IStatusbarViewModelEntry {
|
||||
id: string;
|
||||
name: string;
|
||||
alignment: StatusbarAlignment;
|
||||
priority: number;
|
||||
container: HTMLElement;
|
||||
}
|
||||
|
||||
class StatusbarViewModel extends Disposable {
|
||||
|
||||
private static readonly HIDDEN_ENTRIES_KEY = 'workbench.statusbar.hidden';
|
||||
|
||||
private readonly _entries: IStatusbarViewModelEntry[] = [];
|
||||
get entries(): IStatusbarViewModelEntry[] { return this._entries; }
|
||||
|
||||
private hidden: Set<string>;
|
||||
|
||||
constructor(private storageService: IStorageService) {
|
||||
super();
|
||||
|
||||
this.restoreState();
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
private restoreState(): void {
|
||||
const hiddenRaw = this.storageService.get(StatusbarViewModel.HIDDEN_ENTRIES_KEY, StorageScope.GLOBAL);
|
||||
if (hiddenRaw) {
|
||||
try {
|
||||
const hiddenArray: string[] = JSON.parse(hiddenRaw);
|
||||
this.hidden = new Set(hiddenArray);
|
||||
} catch (error) {
|
||||
// ignore parsing errors
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.hidden) {
|
||||
this.hidden = new Set<string>();
|
||||
}
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
this._register(this.storageService.onDidChangeStorage(e => this.onDidStorageChange(e)));
|
||||
}
|
||||
|
||||
private onDidStorageChange(event: IWorkspaceStorageChangeEvent): void {
|
||||
if (event.key === StatusbarViewModel.HIDDEN_ENTRIES_KEY && event.scope === StorageScope.GLOBAL) {
|
||||
|
||||
// Keep current hidden entries
|
||||
const currentlyHidden = new Set(this.hidden);
|
||||
|
||||
// Load latest state of hidden entries
|
||||
this.hidden.clear();
|
||||
this.restoreState();
|
||||
|
||||
const changed = new Set<string>();
|
||||
|
||||
// Check for each entry that is now visible
|
||||
currentlyHidden.forEach(id => {
|
||||
if (!this.hidden.has(id)) {
|
||||
changed.add(id);
|
||||
}
|
||||
});
|
||||
|
||||
// Check for each entry that is now hidden
|
||||
this.hidden.forEach(id => {
|
||||
if (!currentlyHidden.has(id)) {
|
||||
changed.add(id);
|
||||
}
|
||||
});
|
||||
|
||||
// Update visibility for entries have changed
|
||||
if (changed.size > 0) {
|
||||
this._entries.forEach(entry => {
|
||||
if (changed.has(entry.id)) {
|
||||
this.updateVisibility(entry.id);
|
||||
|
||||
changed.delete(entry.id);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
add(entry: IStatusbarViewModelEntry): IDisposable {
|
||||
this._entries.push(entry); // intentionally not using a map here since multiple entries can have the same ID!
|
||||
|
||||
// Update visibility directly
|
||||
this.updateVisibility(entry);
|
||||
|
||||
// Sort according to priority
|
||||
this.sort();
|
||||
|
||||
// Mark first/last visible entry
|
||||
this.markFirstLastVisibleEntry();
|
||||
|
||||
return toDisposable(() => this.remove(entry));
|
||||
}
|
||||
|
||||
private remove(entry: IStatusbarViewModelEntry): void {
|
||||
const index = this._entries.indexOf(entry);
|
||||
if (index >= 0) {
|
||||
this._entries.splice(index, 1);
|
||||
|
||||
// Mark first/last visible entry
|
||||
this.markFirstLastVisibleEntry();
|
||||
}
|
||||
}
|
||||
|
||||
isHidden(id: string): boolean {
|
||||
return this.hidden.has(id);
|
||||
}
|
||||
|
||||
hide(id: string): void {
|
||||
if (!this.hidden.has(id)) {
|
||||
this.hidden.add(id);
|
||||
|
||||
this.updateVisibility(id);
|
||||
|
||||
this.saveState();
|
||||
}
|
||||
}
|
||||
|
||||
show(id: string): void {
|
||||
if (this.hidden.has(id)) {
|
||||
this.hidden.delete(id);
|
||||
|
||||
this.updateVisibility(id);
|
||||
|
||||
this.saveState();
|
||||
}
|
||||
}
|
||||
|
||||
findEntry(container: HTMLElement): IStatusbarViewModelEntry | undefined {
|
||||
for (const entry of this._entries) {
|
||||
if (entry.container === container) {
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
getEntries(alignment: StatusbarAlignment): IStatusbarViewModelEntry[] {
|
||||
return this._entries.filter(entry => entry.alignment === alignment);
|
||||
}
|
||||
|
||||
private updateVisibility(id: string): void;
|
||||
private updateVisibility(entry: IStatusbarViewModelEntry): void;
|
||||
private updateVisibility(arg1: string | IStatusbarViewModelEntry): void {
|
||||
|
||||
// By identifier
|
||||
if (typeof arg1 === 'string') {
|
||||
const id = arg1;
|
||||
|
||||
for (const entry of this._entries) {
|
||||
if (entry.id === id) {
|
||||
this.updateVisibility(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// By entry
|
||||
else {
|
||||
const entry = arg1;
|
||||
const isHidden = this.isHidden(entry.id);
|
||||
|
||||
// Use CSS to show/hide item container
|
||||
if (isHidden) {
|
||||
hide(entry.container);
|
||||
} else {
|
||||
show(entry.container);
|
||||
}
|
||||
|
||||
// Mark first/last visible entry
|
||||
this.markFirstLastVisibleEntry();
|
||||
}
|
||||
}
|
||||
|
||||
private saveState(): void {
|
||||
if (this.hidden.size > 0) {
|
||||
this.storageService.store(StatusbarViewModel.HIDDEN_ENTRIES_KEY, JSON.stringify(values(this.hidden)), StorageScope.GLOBAL);
|
||||
} else {
|
||||
this.storageService.remove(StatusbarViewModel.HIDDEN_ENTRIES_KEY, StorageScope.GLOBAL);
|
||||
}
|
||||
}
|
||||
|
||||
private sort(): void {
|
||||
this._entries.sort((entryA, entryB) => {
|
||||
if (entryA.alignment === entryB.alignment) {
|
||||
return entryB.priority - entryA.priority; // higher priority towards the left
|
||||
}
|
||||
|
||||
if (entryA.alignment === StatusbarAlignment.LEFT) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (entryB.alignment === StatusbarAlignment.LEFT) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
|
||||
private markFirstLastVisibleEntry(): void {
|
||||
this.doMarkFirstLastVisibleStatusbarItem(this.getEntries(StatusbarAlignment.LEFT));
|
||||
this.doMarkFirstLastVisibleStatusbarItem(this.getEntries(StatusbarAlignment.RIGHT));
|
||||
}
|
||||
|
||||
private doMarkFirstLastVisibleStatusbarItem(entries: IStatusbarViewModelEntry[]): void {
|
||||
let firstVisibleItem: IStatusbarViewModelEntry | undefined;
|
||||
let lastVisibleItem: IStatusbarViewModelEntry | undefined;
|
||||
|
||||
for (const entry of entries) {
|
||||
|
||||
// Clear previous first
|
||||
removeClasses(entry.container, 'first-visible-item', 'last-visible-item');
|
||||
|
||||
const isVisible = !this.isHidden(entry.id);
|
||||
if (isVisible) {
|
||||
if (!firstVisibleItem) {
|
||||
firstVisibleItem = entry;
|
||||
}
|
||||
|
||||
lastVisibleItem = entry;
|
||||
}
|
||||
}
|
||||
|
||||
// Mark: first visible item
|
||||
if (firstVisibleItem) {
|
||||
addClass(firstVisibleItem.container, 'first-visible-item');
|
||||
}
|
||||
|
||||
// Mark: last visible item
|
||||
if (lastVisibleItem) {
|
||||
addClass(lastVisibleItem.container, 'last-visible-item');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ToggleStatusbarEntryVisibilityAction extends Action {
|
||||
|
||||
constructor(id: string, label: string, private model: StatusbarViewModel) {
|
||||
super(id, label, undefined, true);
|
||||
|
||||
this.checked = !model.isHidden(id);
|
||||
}
|
||||
|
||||
run(): Promise<any> {
|
||||
if (this.model.isHidden(this.id)) {
|
||||
this.model.show(this.id);
|
||||
} else {
|
||||
this.model.hide(this.id);
|
||||
}
|
||||
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
|
||||
class HideStatusbarEntryAction extends Action {
|
||||
|
||||
constructor(id: string, private model: StatusbarViewModel) {
|
||||
super(id, nls.localize('hide', "Hide"), undefined, true);
|
||||
}
|
||||
|
||||
run(): Promise<any> {
|
||||
this.model.hide(this.id);
|
||||
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
|
||||
export class StatusbarPart extends Part implements IStatusbarService {
|
||||
|
||||
_serviceBrand: ServiceIdentifier<any>;
|
||||
|
||||
private static readonly PRIORITY_PROP = 'statusbar-entry-priority';
|
||||
private static readonly ALIGNMENT_PROP = 'statusbar-entry-alignment';
|
||||
_serviceBrand: ServiceIdentifier<IStatusbarService>;
|
||||
|
||||
//#region IView
|
||||
|
||||
@@ -49,20 +329,27 @@ export class StatusbarPart extends Part implements IStatusbarService {
|
||||
|
||||
//#endregion
|
||||
|
||||
private statusMessageDispose: IDisposable;
|
||||
private styleElement: HTMLStyleElement;
|
||||
|
||||
private pendingEntries: PendingEntry[] = [];
|
||||
private pendingEntries: IPendingStatusbarEntry[] = [];
|
||||
|
||||
private readonly viewModel: StatusbarViewModel;
|
||||
|
||||
private leftItemsContainer: HTMLElement;
|
||||
private rightItemsContainer: HTMLElement;
|
||||
|
||||
constructor(
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
|
||||
@IStorageService storageService: IStorageService,
|
||||
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService
|
||||
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,
|
||||
@IContextMenuService private contextMenuService: IContextMenuService
|
||||
) {
|
||||
super(Parts.STATUSBAR_PART, { hasTitle: false }, themeService, storageService, layoutService);
|
||||
|
||||
this.viewModel = this._register(new StatusbarViewModel(storageService));
|
||||
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
@@ -70,133 +357,233 @@ export class StatusbarPart extends Part implements IStatusbarService {
|
||||
this._register(this.contextService.onDidChangeWorkbenchState(() => this.updateStyles()));
|
||||
}
|
||||
|
||||
addEntry(entry: IStatusbarEntry, alignment: StatusbarAlignment, priority: number = 0): IStatusbarEntryAccessor {
|
||||
addEntry(entry: IStatusbarEntry, id: string, name: string, alignment: StatusbarAlignment, priority: number = 0): IStatusbarEntryAccessor {
|
||||
|
||||
// As long as we have not been created into a container yet, record all entries
|
||||
// that are pending so that they can get created at a later point
|
||||
if (!this.element) {
|
||||
const pendingEntry: PendingEntry = {
|
||||
entry, alignment, priority
|
||||
};
|
||||
this.pendingEntries.push(pendingEntry);
|
||||
return this.doAddPendingEntry(entry, id, name, alignment, priority);
|
||||
}
|
||||
|
||||
const accessor: IStatusbarEntryAccessor = {
|
||||
update: (entry: IStatusbarEntry) => {
|
||||
if (pendingEntry.accessor) {
|
||||
pendingEntry.accessor.update(entry);
|
||||
} else {
|
||||
pendingEntry.entry = entry;
|
||||
}
|
||||
},
|
||||
dispose: () => {
|
||||
if (pendingEntry.accessor) {
|
||||
pendingEntry.accessor.dispose();
|
||||
} else {
|
||||
this.pendingEntries = this.pendingEntries.filter(entry => entry !== pendingEntry);
|
||||
}
|
||||
// Otherwise add to view
|
||||
return this.doAddEntry(entry, id, name, alignment, priority);
|
||||
}
|
||||
|
||||
private doAddPendingEntry(entry: IStatusbarEntry, id: string, name: string, alignment: StatusbarAlignment, priority: number): IStatusbarEntryAccessor {
|
||||
const pendingEntry: IPendingStatusbarEntry = { entry, id, name, alignment, priority };
|
||||
this.pendingEntries.push(pendingEntry);
|
||||
|
||||
const accessor: IStatusbarEntryAccessor = {
|
||||
update: (entry: IStatusbarEntry) => {
|
||||
if (pendingEntry.accessor) {
|
||||
pendingEntry.accessor.update(entry);
|
||||
} else {
|
||||
pendingEntry.entry = entry;
|
||||
}
|
||||
};
|
||||
return accessor;
|
||||
}
|
||||
},
|
||||
|
||||
// Render entry in status bar
|
||||
const el = this.doCreateStatusItem(alignment, priority, ...coalesce(['statusbar-entry', entry.showBeak ? 'has-beak' : undefined]));
|
||||
const item = this.instantiationService.createInstance(StatusBarEntryItem, el, entry);
|
||||
|
||||
// Insert according to priority
|
||||
const container = this.element;
|
||||
const neighbours = this.getEntries(alignment);
|
||||
let inserted = false;
|
||||
for (const neighbour of neighbours) {
|
||||
const nPriority = Number(neighbour.getAttribute(StatusbarPart.PRIORITY_PROP));
|
||||
if (
|
||||
alignment === StatusbarAlignment.LEFT && nPriority < priority ||
|
||||
alignment === StatusbarAlignment.RIGHT && nPriority > priority
|
||||
) {
|
||||
container.insertBefore(el, neighbour);
|
||||
inserted = true;
|
||||
break;
|
||||
dispose: () => {
|
||||
if (pendingEntry.accessor) {
|
||||
pendingEntry.accessor.dispose();
|
||||
} else {
|
||||
this.pendingEntries = this.pendingEntries.filter(entry => entry !== pendingEntry);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (!inserted) {
|
||||
container.appendChild(el);
|
||||
}
|
||||
return accessor;
|
||||
}
|
||||
|
||||
private doAddEntry(entry: IStatusbarEntry, id: string, name: string, alignment: StatusbarAlignment, priority: number): IStatusbarEntryAccessor {
|
||||
|
||||
// Create item
|
||||
const itemContainer = this.doCreateStatusItem(id, name, alignment, priority, ...coalesce(['statusbar-entry', entry.showBeak ? 'has-beak' : undefined]));
|
||||
const item = this.instantiationService.createInstance(StatusbarEntryItem, itemContainer, entry);
|
||||
|
||||
// Append to parent
|
||||
this.appendOneStatusbarEntry(itemContainer, alignment, priority);
|
||||
|
||||
// Add to view model
|
||||
const viewModelEntry: IStatusbarViewModelEntry = { id, name, alignment, priority, container: itemContainer };
|
||||
const viewModelEntryDispose = this.viewModel.add(viewModelEntry);
|
||||
|
||||
return {
|
||||
update: entry => {
|
||||
|
||||
// Update beak
|
||||
if (entry.showBeak) {
|
||||
addClass(el, 'has-beak');
|
||||
addClass(itemContainer, 'has-beak');
|
||||
} else {
|
||||
removeClass(el, 'has-beak');
|
||||
removeClass(itemContainer, 'has-beak');
|
||||
}
|
||||
|
||||
// Update entry
|
||||
item.update(entry);
|
||||
},
|
||||
dispose: () => {
|
||||
el.remove();
|
||||
dispose(viewModelEntryDispose);
|
||||
itemContainer.remove();
|
||||
dispose(item);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private getEntries(alignment: StatusbarAlignment): HTMLElement[] {
|
||||
const entries: HTMLElement[] = [];
|
||||
|
||||
const container = this.element;
|
||||
const children = container.children;
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
const childElement = <HTMLElement>children.item(i);
|
||||
if (Number(childElement.getAttribute(StatusbarPart.ALIGNMENT_PROP)) === alignment) {
|
||||
entries.push(childElement);
|
||||
}
|
||||
updateEntryVisibility(id: string, visible: boolean): void {
|
||||
if (visible) {
|
||||
this.viewModel.show(id);
|
||||
} else {
|
||||
this.viewModel.hide(id);
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
createContentArea(parent: HTMLElement): HTMLElement {
|
||||
this.element = parent;
|
||||
|
||||
// Fill in initial items that were contributed from the registry
|
||||
// Left items container
|
||||
this.leftItemsContainer = document.createElement('div');
|
||||
addClasses(this.leftItemsContainer, 'left-items', 'items-container');
|
||||
this.element.appendChild(this.leftItemsContainer);
|
||||
|
||||
// Right items container
|
||||
this.rightItemsContainer = document.createElement('div');
|
||||
addClasses(this.rightItemsContainer, 'right-items', 'items-container');
|
||||
this.element.appendChild(this.rightItemsContainer);
|
||||
|
||||
// Context menu support
|
||||
this._register(addDisposableListener(parent, EventType.CONTEXT_MENU, e => this.showContextMenu(e)));
|
||||
|
||||
// Initial status bar entries
|
||||
this.createInitialStatusbarEntries();
|
||||
|
||||
return this.element;
|
||||
}
|
||||
|
||||
private createInitialStatusbarEntries(): void {
|
||||
const registry = Registry.as<IStatusbarRegistry>(Extensions.Statusbar);
|
||||
|
||||
const descriptors = registry.items.slice().sort((a, b) => {
|
||||
if (a.alignment === b.alignment) {
|
||||
if (a.alignment === StatusbarAlignment.LEFT) {
|
||||
return b.priority - a.priority;
|
||||
} else {
|
||||
return a.priority - b.priority;
|
||||
}
|
||||
} else if (a.alignment === StatusbarAlignment.LEFT) {
|
||||
return 1;
|
||||
} else if (a.alignment === StatusbarAlignment.RIGHT) {
|
||||
return -1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
// Create initial items that were contributed from the registry
|
||||
for (const { id, name, alignment, priority, syncDescriptor } of registry.items) {
|
||||
|
||||
for (const descriptor of descriptors) {
|
||||
const item = this.instantiationService.createInstance(descriptor.syncDescriptor);
|
||||
const el = this.doCreateStatusItem(descriptor.alignment, descriptor.priority);
|
||||
// Create item
|
||||
const item = this.instantiationService.createInstance(syncDescriptor);
|
||||
const itemContainer = this.doCreateStatusItem(id, name, alignment, priority);
|
||||
this._register(item.render(itemContainer));
|
||||
|
||||
this._register(item.render(el));
|
||||
this.element.appendChild(el);
|
||||
// Add to view model
|
||||
const viewModelEntry: IStatusbarViewModelEntry = { id, name, alignment, priority, container: itemContainer };
|
||||
this.viewModel.add(viewModelEntry);
|
||||
}
|
||||
|
||||
// Add items in order according to alignment
|
||||
this.appendAllStatusbarEntries();
|
||||
|
||||
// Fill in pending entries if any
|
||||
while (this.pendingEntries.length) {
|
||||
const entry = this.pendingEntries.shift();
|
||||
if (entry) {
|
||||
entry.accessor = this.addEntry(entry.entry, entry.alignment, entry.priority);
|
||||
const pending = this.pendingEntries.shift();
|
||||
if (pending) {
|
||||
pending.accessor = this.addEntry(pending.entry, pending.id, pending.name, pending.alignment, pending.priority);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private appendAllStatusbarEntries(): void {
|
||||
|
||||
// Append in order of priority
|
||||
[
|
||||
...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;
|
||||
|
||||
target.appendChild(entry.container);
|
||||
});
|
||||
}
|
||||
|
||||
private appendOneStatusbarEntry(itemContainer: HTMLElement, alignment: StatusbarAlignment, priority: number): void {
|
||||
const entries = this.viewModel.getEntries(alignment);
|
||||
|
||||
if (alignment === StatusbarAlignment.RIGHT) {
|
||||
entries.reverse(); // reversing due to flex: row-reverse
|
||||
}
|
||||
|
||||
const target = 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
|
||||
let appended = false;
|
||||
for (const entry of entries) {
|
||||
if (
|
||||
alignment === StatusbarAlignment.LEFT && entry.priority < priority ||
|
||||
alignment === StatusbarAlignment.RIGHT && entry.priority > priority // reversing due to flex: row-reverse
|
||||
) {
|
||||
target.insertBefore(itemContainer, entry.container);
|
||||
appended = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return this.element;
|
||||
// Fallback to just appending otherwise
|
||||
if (!appended) {
|
||||
target.appendChild(itemContainer);
|
||||
}
|
||||
}
|
||||
|
||||
private showContextMenu(e: MouseEvent): void {
|
||||
EventHelper.stop(e, true);
|
||||
|
||||
const event = new StandardMouseEvent(e);
|
||||
|
||||
let actions: IAction[] | undefined = undefined;
|
||||
this.contextMenuService.showContextMenu({
|
||||
getAnchor: () => ({ x: event.posx, y: event.posy }),
|
||||
getActions: () => {
|
||||
actions = this.getContextMenuActions(event);
|
||||
|
||||
return actions;
|
||||
},
|
||||
onHide: () => {
|
||||
if (actions) {
|
||||
dispose(actions);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private getContextMenuActions(event: StandardMouseEvent): IAction[] {
|
||||
const actions: Action[] = [];
|
||||
|
||||
// Figure out if mouse is over an entry
|
||||
let statusEntryUnderMouse: IStatusbarViewModelEntry | undefined = undefined;
|
||||
for (let element: HTMLElement | null = event.target; element; element = element.parentElement) {
|
||||
const entry = this.viewModel.findEntry(element);
|
||||
if (entry) {
|
||||
statusEntryUnderMouse = entry;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (statusEntryUnderMouse) {
|
||||
actions.push(new HideStatusbarEntryAction(statusEntryUnderMouse.id, this.viewModel));
|
||||
actions.push(new Separator());
|
||||
}
|
||||
|
||||
// Show an entry per known status entry
|
||||
// Note: even though entries have an identifier, there can be multiple entries
|
||||
// having the same identifier (e.g. from extensions). So we make sure to only
|
||||
// show a single entry per identifier we handled.
|
||||
const handledEntries = new Set<string>();
|
||||
this.viewModel.entries.forEach(entry => {
|
||||
if (!handledEntries.has(entry.id)) {
|
||||
actions.push(new ToggleStatusbarEntryVisibilityAction(entry.id, entry.name, this.viewModel));
|
||||
handledEntries.add(entry.id);
|
||||
}
|
||||
});
|
||||
|
||||
// Provide an action to hide the status bar at last
|
||||
actions.push(new Separator());
|
||||
actions.push(this.instantiationService.createInstance(ToggleStatusbarVisibilityAction, ToggleStatusbarVisibilityAction.ID, nls.localize('hideStatusBar', "Hide Status Bar")));
|
||||
|
||||
return actions;
|
||||
}
|
||||
|
||||
updateStyles(): void {
|
||||
@@ -220,64 +607,25 @@ export class StatusbarPart extends Part implements IStatusbarService {
|
||||
this.styleElement = createStyleSheet(container);
|
||||
}
|
||||
|
||||
this.styleElement.innerHTML = `.monaco-workbench .part.statusbar > .statusbar-item.has-beak:before { border-bottom-color: ${backgroundColor}; }`;
|
||||
this.styleElement.innerHTML = `.monaco-workbench .part.statusbar > .items-container > .statusbar-item.has-beak:before { border-bottom-color: ${backgroundColor}; }`;
|
||||
}
|
||||
|
||||
private doCreateStatusItem(alignment: StatusbarAlignment, priority: number = 0, ...extraClasses: string[]): HTMLElement {
|
||||
const el = document.createElement('div');
|
||||
addClass(el, 'statusbar-item');
|
||||
private doCreateStatusItem(id: string, name: string, alignment: StatusbarAlignment, priority: number = 0, ...extraClasses: string[]): HTMLElement {
|
||||
const itemContainer = document.createElement('div');
|
||||
itemContainer.title = name;
|
||||
|
||||
addClass(itemContainer, 'statusbar-item');
|
||||
if (extraClasses) {
|
||||
addClasses(el, ...extraClasses);
|
||||
addClasses(itemContainer, ...extraClasses);
|
||||
}
|
||||
|
||||
if (alignment === StatusbarAlignment.RIGHT) {
|
||||
addClass(el, 'right');
|
||||
addClass(itemContainer, 'right');
|
||||
} else {
|
||||
addClass(el, 'left');
|
||||
addClass(itemContainer, 'left');
|
||||
}
|
||||
|
||||
el.setAttribute(StatusbarPart.PRIORITY_PROP, String(priority));
|
||||
el.setAttribute(StatusbarPart.ALIGNMENT_PROP, String(alignment));
|
||||
|
||||
return el;
|
||||
}
|
||||
|
||||
setStatusMessage(message: string, autoDisposeAfter: number = -1, delayBy: number = 0): IDisposable {
|
||||
|
||||
// Dismiss any previous
|
||||
dispose(this.statusMessageDispose);
|
||||
|
||||
// Create new
|
||||
let statusMessageEntry: IStatusbarEntryAccessor;
|
||||
let showHandle: any = setTimeout(() => {
|
||||
statusMessageEntry = this.addEntry({ text: message }, StatusbarAlignment.LEFT, -Number.MAX_VALUE /* far right on left hand side */);
|
||||
showHandle = null;
|
||||
}, delayBy);
|
||||
let hideHandle: any;
|
||||
|
||||
// Dispose function takes care of timeouts and actual entry
|
||||
const statusMessageDispose = {
|
||||
dispose: () => {
|
||||
if (showHandle) {
|
||||
clearTimeout(showHandle);
|
||||
}
|
||||
|
||||
if (hideHandle) {
|
||||
clearTimeout(hideHandle);
|
||||
}
|
||||
|
||||
if (statusMessageEntry) {
|
||||
statusMessageEntry.dispose();
|
||||
}
|
||||
}
|
||||
};
|
||||
this.statusMessageDispose = statusMessageDispose;
|
||||
|
||||
if (typeof autoDisposeAfter === 'number' && autoDisposeAfter > 0) {
|
||||
hideHandle = setTimeout(() => statusMessageDispose.dispose(), autoDisposeAfter);
|
||||
}
|
||||
|
||||
return statusMessageDispose;
|
||||
return itemContainer;
|
||||
}
|
||||
|
||||
layout(width: number, height: number): void {
|
||||
@@ -291,27 +639,20 @@ export class StatusbarPart extends Part implements IStatusbarService {
|
||||
}
|
||||
}
|
||||
|
||||
let manageExtensionAction: ManageExtensionAction;
|
||||
class StatusBarEntryItem extends Disposable {
|
||||
class StatusbarEntryItem extends Disposable {
|
||||
private entryDisposables: IDisposable[] = [];
|
||||
|
||||
constructor(
|
||||
private container: HTMLElement,
|
||||
entry: IStatusbarEntry,
|
||||
@ICommandService private readonly commandService: ICommandService,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
@INotificationService private readonly notificationService: INotificationService,
|
||||
@ITelemetryService private readonly telemetryService: ITelemetryService,
|
||||
@IContextMenuService private readonly contextMenuService: IContextMenuService,
|
||||
@IEditorService private readonly editorService: IEditorService,
|
||||
@IThemeService private readonly themeService: IThemeService
|
||||
) {
|
||||
super();
|
||||
|
||||
if (!manageExtensionAction) {
|
||||
manageExtensionAction = this.instantiationService.createInstance(ManageExtensionAction);
|
||||
}
|
||||
|
||||
this.render(entry);
|
||||
}
|
||||
|
||||
@@ -351,19 +692,6 @@ class StatusBarEntryItem extends Disposable {
|
||||
addClass(this.container, 'has-background-color');
|
||||
}
|
||||
|
||||
// Context Menu
|
||||
if (entry.extensionId) {
|
||||
this.entryDisposables.push((addDisposableListener(textContainer, 'contextmenu', e => {
|
||||
EventHelper.stop(e, true);
|
||||
|
||||
this.contextMenuService.showContextMenu({
|
||||
getAnchor: () => this.container,
|
||||
getActionsContext: () => entry.extensionId!.value,
|
||||
getActions: () => [manageExtensionAction]
|
||||
});
|
||||
})));
|
||||
}
|
||||
|
||||
this.container.appendChild(textContainer);
|
||||
}
|
||||
|
||||
@@ -412,43 +740,30 @@ class StatusBarEntryItem extends Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
class ManageExtensionAction extends Action {
|
||||
|
||||
constructor(
|
||||
@ICommandService private readonly commandService: ICommandService
|
||||
) {
|
||||
super('statusbar.manage.extension', nls.localize('manageExtension', "Manage Extension"));
|
||||
}
|
||||
|
||||
run(extensionId: string): Promise<any> {
|
||||
return this.commandService.executeCommand('_extensions.manage', extensionId);
|
||||
}
|
||||
}
|
||||
|
||||
registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
|
||||
const statusBarItemHoverBackground = theme.getColor(STATUS_BAR_ITEM_HOVER_BACKGROUND);
|
||||
if (statusBarItemHoverBackground) {
|
||||
collector.addRule(`.monaco-workbench .part.statusbar > .statusbar-item a:hover { background-color: ${statusBarItemHoverBackground}; }`);
|
||||
collector.addRule(`.monaco-workbench .part.statusbar > .items-container > .statusbar-item a:hover { background-color: ${statusBarItemHoverBackground}; }`);
|
||||
}
|
||||
|
||||
const statusBarItemActiveBackground = theme.getColor(STATUS_BAR_ITEM_ACTIVE_BACKGROUND);
|
||||
if (statusBarItemActiveBackground) {
|
||||
collector.addRule(`.monaco-workbench .part.statusbar > .statusbar-item a:active { background-color: ${statusBarItemActiveBackground}; }`);
|
||||
collector.addRule(`.monaco-workbench .part.statusbar > .items-container > .statusbar-item a:active { background-color: ${statusBarItemActiveBackground}; }`);
|
||||
}
|
||||
|
||||
const statusBarProminentItemForeground = theme.getColor(STATUS_BAR_PROMINENT_ITEM_FOREGROUND);
|
||||
if (statusBarProminentItemForeground) {
|
||||
collector.addRule(`.monaco-workbench .part.statusbar > .statusbar-item .status-bar-info { color: ${statusBarProminentItemForeground}; }`);
|
||||
collector.addRule(`.monaco-workbench .part.statusbar > .items-container > .statusbar-item .status-bar-info { color: ${statusBarProminentItemForeground}; }`);
|
||||
}
|
||||
|
||||
const statusBarProminentItemBackground = theme.getColor(STATUS_BAR_PROMINENT_ITEM_BACKGROUND);
|
||||
if (statusBarProminentItemBackground) {
|
||||
collector.addRule(`.monaco-workbench .part.statusbar > .statusbar-item .status-bar-info { background-color: ${statusBarProminentItemBackground}; }`);
|
||||
collector.addRule(`.monaco-workbench .part.statusbar > .items-container > .statusbar-item .status-bar-info { background-color: ${statusBarProminentItemBackground}; }`);
|
||||
}
|
||||
|
||||
const statusBarProminentItemHoverBackground = theme.getColor(STATUS_BAR_PROMINENT_ITEM_HOVER_BACKGROUND);
|
||||
if (statusBarProminentItemHoverBackground) {
|
||||
collector.addRule(`.monaco-workbench .part.statusbar > .statusbar-item a.status-bar-info:hover { background-color: ${statusBarProminentItemHoverBackground}; }`);
|
||||
collector.addRule(`.monaco-workbench .part.statusbar > .items-container > .statusbar-item a.status-bar-info:hover { background-color: ${statusBarProminentItemHoverBackground}; }`);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
}
|
||||
|
||||
/* Windows/Linux: Rules for custom title (icon, window controls) */
|
||||
|
||||
.monaco-workbench.web .part.titlebar,
|
||||
.monaco-workbench.windows .part.titlebar,
|
||||
.monaco-workbench.linux .part.titlebar {
|
||||
padding: 0;
|
||||
@@ -51,6 +51,7 @@
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.monaco-workbench.web .part.titlebar > .window-title,
|
||||
.monaco-workbench.windows .part.titlebar > .window-title,
|
||||
.monaco-workbench.linux .part.titlebar > .window-title {
|
||||
cursor: default;
|
||||
|
||||
@@ -13,7 +13,7 @@ import { IAction, Action } from 'vs/base/common/actions';
|
||||
import { Separator } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { isMacintosh, isLinux } from 'vs/base/common/platform';
|
||||
import { isMacintosh, isWeb } from 'vs/base/common/platform';
|
||||
import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
@@ -34,22 +34,19 @@ import { assign } from 'vs/base/common/objects';
|
||||
import { mnemonicMenuLabel, unmnemonicLabel } from 'vs/base/common/labels';
|
||||
import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility';
|
||||
import { withNullAsUndefined } from 'vs/base/common/types';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ServicesAccessor } from 'vs/editor/browser/editorExtensions';
|
||||
|
||||
export class MenubarControl extends Disposable {
|
||||
|
||||
private keys = [
|
||||
'files.autoSave',
|
||||
'window.menuBarVisibility',
|
||||
'editor.multiCursorModifier',
|
||||
'workbench.sideBar.location',
|
||||
'workbench.statusBar.visible',
|
||||
'workbench.activityBar.visible',
|
||||
'window.enableMenuBarMnemonics',
|
||||
'window.nativeTabs'
|
||||
];
|
||||
|
||||
// {{SQL CARBON EDIT}} - Disable unusued menus
|
||||
private topLevelMenus: {
|
||||
private menus: {
|
||||
'File': IMenu;
|
||||
'Edit': IMenu;
|
||||
// 'Selection': IMenu;
|
||||
@@ -79,15 +76,17 @@ export class MenubarControl extends Disposable {
|
||||
private container: HTMLElement;
|
||||
private recentlyOpened: IRecentlyOpened;
|
||||
private alwaysOnMnemonics: boolean;
|
||||
private isNative: boolean;
|
||||
|
||||
private readonly _onVisibilityChange: Emitter<boolean>;
|
||||
private readonly _onFocusStateChange: Emitter<boolean>;
|
||||
|
||||
private menubarService: IMenubarService;
|
||||
|
||||
private static MAX_MENU_RECENT_ENTRIES = 10;
|
||||
|
||||
constructor(
|
||||
@IThemeService private readonly themeService: IThemeService,
|
||||
@IMenubarService private readonly menubarService: IMenubarService,
|
||||
@IMenuService private readonly menuService: IMenuService,
|
||||
@IWindowService private readonly windowService: IWindowService,
|
||||
@IWindowsService private readonly windowsService: IWindowsService,
|
||||
@@ -100,13 +99,22 @@ export class MenubarControl extends Disposable {
|
||||
@INotificationService private readonly notificationService: INotificationService,
|
||||
@IPreferencesService private readonly preferencesService: IPreferencesService,
|
||||
@IEnvironmentService private readonly environmentService: IEnvironmentService,
|
||||
@IAccessibilityService private readonly accessibilityService: IAccessibilityService
|
||||
@IAccessibilityService private readonly accessibilityService: IAccessibilityService,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService
|
||||
) {
|
||||
|
||||
super();
|
||||
|
||||
// {{SQL CARBON EDIT}} - Disable unused menus
|
||||
this.topLevelMenus = {
|
||||
this.isNative = !isWeb && (isMacintosh || this.currentTitlebarStyleSetting !== 'custom');
|
||||
|
||||
this.instantiationService.invokeFunction((accessor: ServicesAccessor) => {
|
||||
if (this.isNative) {
|
||||
this.menubarService = accessor.get(IMenubarService);
|
||||
}
|
||||
});
|
||||
|
||||
// {{SQL CARBON EDIT}} - Disable unusued menus
|
||||
this.menus = {
|
||||
'File': this._register(this.menuService.createMenu(MenuId.MenubarFileMenu, this.contextKeyService)),
|
||||
'Edit': this._register(this.menuService.createMenu(MenuId.MenubarEditMenu, this.contextKeyService)),
|
||||
// 'Selection': this._register(this.menuService.createMenu(MenuId.MenubarSelectionMenu, this.contextKeyService)),
|
||||
@@ -117,8 +125,9 @@ export class MenubarControl extends Disposable {
|
||||
'Help': this._register(this.menuService.createMenu(MenuId.MenubarHelpMenu, this.contextKeyService))
|
||||
};
|
||||
|
||||
if (isMacintosh) {
|
||||
this.topLevelMenus['Preferences'] = this._register(this.menuService.createMenu(MenuId.MenubarPreferencesMenu, this.contextKeyService));
|
||||
if (isMacintosh && this.isNative) {
|
||||
this.menus['Preferences'] = this._register(this.menuService.createMenu(MenuId.MenubarPreferencesMenu, this.contextKeyService));
|
||||
this.topLevelTitles['Preferences'] = nls.localize('mPreferences', "Preferences");
|
||||
}
|
||||
|
||||
this.menuUpdater = this._register(new RunOnceScheduler(() => this.doUpdateMenubar(false), 200));
|
||||
@@ -126,9 +135,9 @@ export class MenubarControl extends Disposable {
|
||||
this._onVisibilityChange = this._register(new Emitter<boolean>());
|
||||
this._onFocusStateChange = this._register(new Emitter<boolean>());
|
||||
|
||||
if (isMacintosh || this.currentTitlebarStyleSetting !== 'custom') {
|
||||
for (const topLevelMenuName of Object.keys(this.topLevelMenus)) {
|
||||
const menu = this.topLevelMenus[topLevelMenuName];
|
||||
if (this.isNative) {
|
||||
for (const topLevelMenuName of Object.keys(this.topLevelTitles)) {
|
||||
const menu = this.menus[topLevelMenuName];
|
||||
if (menu) {
|
||||
this._register(menu.onDidChange(() => this.updateMenubar()));
|
||||
}
|
||||
@@ -138,13 +147,11 @@ export class MenubarControl extends Disposable {
|
||||
this.windowService.getRecentlyOpened().then((recentlyOpened) => {
|
||||
this.recentlyOpened = recentlyOpened;
|
||||
|
||||
if (isMacintosh || this.currentTitlebarStyleSetting !== 'custom') {
|
||||
if (this.isNative) {
|
||||
this.doUpdateMenubar(true);
|
||||
}
|
||||
});
|
||||
|
||||
this.notifyExistingLinuxUser();
|
||||
|
||||
this.notifyUserOfCustomMenubarAccessibility();
|
||||
|
||||
this.registerListeners();
|
||||
@@ -159,28 +166,6 @@ export class MenubarControl extends Disposable {
|
||||
return enableMenuBarMnemonics;
|
||||
}
|
||||
|
||||
private get currentSidebarPosition(): string {
|
||||
return this.configurationService.getValue<string>('workbench.sideBar.location');
|
||||
}
|
||||
|
||||
private get currentStatusBarVisibility(): boolean {
|
||||
let setting = this.configurationService.getValue<boolean>('workbench.statusBar.visible');
|
||||
if (typeof setting !== 'boolean') {
|
||||
setting = true;
|
||||
}
|
||||
|
||||
return setting;
|
||||
}
|
||||
|
||||
private get currentActivityBarVisibility(): boolean {
|
||||
let setting = this.configurationService.getValue<boolean>('workbench.activityBar.visible');
|
||||
if (typeof setting !== 'boolean') {
|
||||
setting = true;
|
||||
}
|
||||
|
||||
return setting;
|
||||
}
|
||||
|
||||
private get currentMenubarVisibility(): MenuBarVisibility {
|
||||
return this.configurationService.getValue<MenuBarVisibility>('window.menuBarVisibility');
|
||||
}
|
||||
@@ -217,38 +202,8 @@ export class MenubarControl extends Disposable {
|
||||
});
|
||||
}
|
||||
|
||||
// TODO@sbatten remove after feb19
|
||||
private notifyExistingLinuxUser(): void {
|
||||
if (!isLinux) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isNewUser = !this.storageService.get('telemetry.lastSessionDate', StorageScope.GLOBAL);
|
||||
const hasBeenNotified = this.storageService.getBoolean('menubar/linuxTitlebarRevertNotified', StorageScope.GLOBAL, false);
|
||||
const titleBarConfiguration = this.configurationService.inspect('window.titleBarStyle');
|
||||
const customShown = getTitleBarStyle(this.configurationService, this.environmentService) === 'custom';
|
||||
|
||||
if (!hasBeenNotified) {
|
||||
this.storageService.store('menubar/linuxTitlebarRevertNotified', true, StorageScope.GLOBAL);
|
||||
}
|
||||
|
||||
if (isNewUser || hasBeenNotified || (titleBarConfiguration && titleBarConfiguration.user) || customShown) {
|
||||
return;
|
||||
}
|
||||
|
||||
const message = nls.localize('menubar.linuxTitlebarRevertNotification', "We have updated the default title bar on Linux to use the native setting. If you prefer, you can go back to the custom setting. More information is available in our [online documentation](https://go.microsoft.com/fwlink/?linkid=2074137).");
|
||||
this.notificationService.prompt(Severity.Info, message, [
|
||||
{
|
||||
label: nls.localize('goToSetting', "Open Settings"),
|
||||
run: () => {
|
||||
return this.preferencesService.openGlobalSettings(undefined, { query: 'window.titleBarStyle' });
|
||||
}
|
||||
}
|
||||
]);
|
||||
}
|
||||
|
||||
private notifyUserOfCustomMenubarAccessibility(): void {
|
||||
if (isMacintosh) {
|
||||
if (isWeb || isMacintosh) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -288,7 +243,7 @@ export class MenubarControl extends Disposable {
|
||||
this._register(this.keybindingService.onDidUpdateKeybindings(() => this.updateMenubar()));
|
||||
|
||||
// These listeners only apply when the custom menubar is being used
|
||||
if (!isMacintosh && this.currentTitlebarStyleSetting === 'custom') {
|
||||
if (!this.isNative) {
|
||||
// Listen for window focus changes
|
||||
this._register(this.windowService.onDidChangeFocus(e => this.onDidChangeWindowFocus(e)));
|
||||
|
||||
@@ -304,7 +259,7 @@ export class MenubarControl extends Disposable {
|
||||
}
|
||||
|
||||
private doUpdateMenubar(firstTime: boolean): void {
|
||||
if (!isMacintosh && this.currentTitlebarStyleSetting === 'custom') {
|
||||
if (!this.isNative) {
|
||||
this.setupCustomMenubar(firstTime);
|
||||
} else {
|
||||
// Send menus to main process to be rendered by Electron
|
||||
@@ -322,30 +277,6 @@ export class MenubarControl extends Disposable {
|
||||
private calculateActionLabel(action: IAction | IMenubarMenuItemAction): string {
|
||||
let label = action.label;
|
||||
switch (action.id) {
|
||||
case 'workbench.action.toggleSidebarPosition':
|
||||
if (this.currentSidebarPosition !== 'right') {
|
||||
label = nls.localize({ key: 'miMoveSidebarRight', comment: ['&& denotes a mnemonic'] }, "&&Move Side Bar Right");
|
||||
} else {
|
||||
label = nls.localize({ key: 'miMoveSidebarLeft', comment: ['&& denotes a mnemonic'] }, "&&Move Side Bar Left");
|
||||
}
|
||||
break;
|
||||
|
||||
case 'workbench.action.toggleStatusbarVisibility':
|
||||
if (this.currentStatusBarVisibility) {
|
||||
label = nls.localize({ key: 'miHideStatusbar', comment: ['&& denotes a mnemonic'] }, "&&Hide Status Bar");
|
||||
} else {
|
||||
label = nls.localize({ key: 'miShowStatusbar', comment: ['&& denotes a mnemonic'] }, "&&Show Status Bar");
|
||||
}
|
||||
break;
|
||||
|
||||
case 'workbench.action.toggleActivityBarVisibility':
|
||||
if (this.currentActivityBarVisibility) {
|
||||
label = nls.localize({ key: 'miHideActivityBar', comment: ['&& denotes a mnemonic'] }, "Hide &&Activity Bar");
|
||||
} else {
|
||||
label = nls.localize({ key: 'miShowActivityBar', comment: ['&& denotes a mnemonic'] }, "Show &&Activity Bar");
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -472,7 +403,7 @@ export class MenubarControl extends Disposable {
|
||||
break;
|
||||
|
||||
case 'workbench.action.showAboutDialog':
|
||||
if (!isMacintosh) {
|
||||
if (!isMacintosh && !isWeb) {
|
||||
const updateAction = this.getUpdateAction();
|
||||
if (updateAction) {
|
||||
updateAction.label = mnemonicMenuLabel(updateAction.label);
|
||||
@@ -512,7 +443,7 @@ export class MenubarControl extends Disposable {
|
||||
}
|
||||
|
||||
// Update the menu actions
|
||||
const updateActions = (menu: IMenu, target: IAction[]) => {
|
||||
const updateActions = (menu: IMenu, target: IAction[], topLevelTitle: string) => {
|
||||
target.splice(0);
|
||||
let groups = menu.getActions();
|
||||
for (let group of groups) {
|
||||
@@ -521,11 +452,20 @@ export class MenubarControl extends Disposable {
|
||||
for (let action of actions) {
|
||||
this.insertActionsBefore(action, target);
|
||||
if (action instanceof SubmenuItemAction) {
|
||||
const submenu = this.menuService.createMenu(action.item.submenu, this.contextKeyService);
|
||||
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(() => {
|
||||
const actions: IAction[] = [];
|
||||
updateActions(menu, actions, topLevelTitle);
|
||||
this.menubar.updateMenu({ actions: actions, label: mnemonicMenuLabel(this.topLevelTitles[topLevelTitle]) });
|
||||
}, this));
|
||||
}
|
||||
|
||||
const submenu = this.menus[action.item.submenu]!;
|
||||
const submenuActions: SubmenuAction[] = [];
|
||||
updateActions(submenu, submenuActions);
|
||||
updateActions(submenu, submenuActions, topLevelTitle);
|
||||
target.push(new SubmenuAction(mnemonicMenuLabel(action.label), submenuActions));
|
||||
submenu.dispose();
|
||||
} else {
|
||||
action.label = mnemonicMenuLabel(this.calculateActionLabel(action));
|
||||
target.push(action);
|
||||
@@ -538,19 +478,19 @@ export class MenubarControl extends Disposable {
|
||||
target.pop();
|
||||
};
|
||||
|
||||
for (const title of Object.keys(this.topLevelMenus)) {
|
||||
const menu = this.topLevelMenus[title];
|
||||
for (const title of Object.keys(this.topLevelTitles)) {
|
||||
const menu = this.menus[title];
|
||||
if (firstTime && menu) {
|
||||
this._register(menu.onDidChange(() => {
|
||||
const actions: IAction[] = [];
|
||||
updateActions(menu, actions);
|
||||
updateActions(menu, actions, title);
|
||||
this.menubar.updateMenu({ actions: actions, label: mnemonicMenuLabel(this.topLevelTitles[title]) });
|
||||
}));
|
||||
}
|
||||
|
||||
const actions: IAction[] = [];
|
||||
if (menu) {
|
||||
updateActions(menu, actions);
|
||||
updateActions(menu, actions, title);
|
||||
}
|
||||
|
||||
if (!firstTime) {
|
||||
@@ -591,6 +531,12 @@ export class MenubarControl extends Disposable {
|
||||
|
||||
if (menuItem instanceof SubmenuItemAction) {
|
||||
const submenu = { items: [] };
|
||||
|
||||
if (!this.menus[menuItem.item.submenu]) {
|
||||
this.menus[menuItem.item.submenu] = this.menuService.createMenu(menuItem.item.submenu, this.contextKeyService);
|
||||
this._register(this.menus[menuItem.item.submenu]!.onDidChange(() => this.updateMenubar()));
|
||||
}
|
||||
|
||||
const menuToDispose = this.menuService.createMenu(menuItem.item.submenu, this.contextKeyService);
|
||||
this.populateMenuItems(menuToDispose, submenu, keybindings);
|
||||
|
||||
@@ -637,7 +583,7 @@ export class MenubarControl extends Disposable {
|
||||
|
||||
private getAdditionalKeybindings(): { [id: string]: IMenubarKeybinding } {
|
||||
const keybindings = {};
|
||||
if (isMacintosh) {
|
||||
if (isMacintosh && this.isNative) {
|
||||
keybindings['workbench.action.quit'] = (this.getMenubarKeybinding('workbench.action.quit'));
|
||||
}
|
||||
|
||||
@@ -650,8 +596,8 @@ export class MenubarControl extends Disposable {
|
||||
}
|
||||
|
||||
menubarData.keybindings = this.getAdditionalKeybindings();
|
||||
for (const topLevelMenuName of Object.keys(this.topLevelMenus)) {
|
||||
const menu = this.topLevelMenus[topLevelMenuName];
|
||||
for (const topLevelMenuName of Object.keys(this.topLevelTitles)) {
|
||||
const menu = this.menus[topLevelMenuName];
|
||||
if (menu) {
|
||||
const menubarMenu: IMenubarMenu = { items: [] };
|
||||
this.populateMenuItems(menu, menubarMenu, menubarData.keybindings);
|
||||
@@ -698,7 +644,7 @@ export class MenubarControl extends Disposable {
|
||||
// Build the menubar
|
||||
if (this.container) {
|
||||
|
||||
if (!isMacintosh && this.currentTitlebarStyleSetting === 'custom') {
|
||||
if (!this.isNative) {
|
||||
this.doUpdateMenubar(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/
|
||||
import { IWorkspaceContextService, WorkbenchState } 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 } from 'vs/base/common/platform';
|
||||
import { isMacintosh, isWindows, isLinux, isWeb } from 'vs/base/common/platform';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
import { trim } from 'vs/base/common/strings';
|
||||
@@ -50,8 +50,8 @@ export class TitlebarPart extends Part implements ITitleService {
|
||||
|
||||
readonly minimumWidth: number = 0;
|
||||
readonly maximumWidth: number = Number.POSITIVE_INFINITY;
|
||||
get minimumHeight(): number { return isMacintosh ? 22 / getZoomFactor() : (30 / (this.configurationService.getValue<MenuBarVisibility>('window.menuBarVisibility') === 'hidden' ? getZoomFactor() : 1)); }
|
||||
get maximumHeight(): number { return isMacintosh ? 22 / getZoomFactor() : (30 / (this.configurationService.getValue<MenuBarVisibility>('window.menuBarVisibility') === 'hidden' ? getZoomFactor() : 1)); }
|
||||
get minimumHeight(): number { return isMacintosh && !isWeb ? 22 / getZoomFactor() : (30 / (this.configurationService.getValue<MenuBarVisibility>('window.menuBarVisibility') === 'hidden' ? getZoomFactor() : 1)); }
|
||||
get maximumHeight(): number { return isMacintosh && !isWeb ? 22 / getZoomFactor() : (30 / (this.configurationService.getValue<MenuBarVisibility>('window.menuBarVisibility') === 'hidden' ? getZoomFactor() : 1)); }
|
||||
|
||||
//#endregion
|
||||
|
||||
@@ -135,9 +135,9 @@ export class TitlebarPart extends Part implements ITitleService {
|
||||
}
|
||||
|
||||
private onMenubarVisibilityChanged(visible: boolean) {
|
||||
if (isWindows || isLinux) {
|
||||
if (isWeb || isWindows || isLinux) {
|
||||
// Hide title when toggling menu bar
|
||||
if (this.configurationService.getValue<MenuBarVisibility>('window.menuBarVisibility') === 'toggle' && visible) {
|
||||
if (!isWeb && this.configurationService.getValue<MenuBarVisibility>('window.menuBarVisibility') === 'toggle' && visible) {
|
||||
// Hack to fix issue #52522 with layered webkit-app-region elements appearing under cursor
|
||||
hide(this.dragRegion);
|
||||
setTimeout(() => show(this.dragRegion), 50);
|
||||
@@ -150,7 +150,7 @@ export class TitlebarPart extends Part implements ITitleService {
|
||||
}
|
||||
|
||||
private onMenubarFocusChanged(focused: boolean) {
|
||||
if (isWindows || isLinux) {
|
||||
if (!isWeb && (isWindows || isLinux)) {
|
||||
if (focused) {
|
||||
hide(this.dragRegion);
|
||||
} else {
|
||||
@@ -207,7 +207,7 @@ export class TitlebarPart extends Part implements ITitleService {
|
||||
this.pendingTitle = title;
|
||||
}
|
||||
|
||||
if ((isWindows || isLinux) && this.title) {
|
||||
if ((isWeb || isWindows || isLinux) && this.title) {
|
||||
if (this.lastLayoutDimensions) {
|
||||
this.updateLayout(this.lastLayoutDimensions);
|
||||
}
|
||||
@@ -322,10 +322,12 @@ export class TitlebarPart extends Part implements ITitleService {
|
||||
this.element = parent;
|
||||
|
||||
// Draggable region that we can manipulate for #52522
|
||||
this.dragRegion = append(this.element, $('div.titlebar-drag-region'));
|
||||
if (!isWeb) {
|
||||
this.dragRegion = append(this.element, $('div.titlebar-drag-region'));
|
||||
}
|
||||
|
||||
// App Icon (Windows/Linux)
|
||||
if (!isMacintosh) {
|
||||
// App Icon (Native Windows/Linux)
|
||||
if (!isMacintosh && !isWeb) {
|
||||
this.appIcon = append(this.element, $('div.window-appicon'));
|
||||
this.onUpdateAppIconDragBehavior();
|
||||
|
||||
@@ -341,7 +343,7 @@ export class TitlebarPart extends Part implements ITitleService {
|
||||
|
||||
this.menubarPart.create(this.menubar);
|
||||
|
||||
if (!isMacintosh) {
|
||||
if (!isMacintosh || isWeb) {
|
||||
this._register(this.menubarPart.onVisibilityChange(e => this.onMenubarVisibilityChanged(e)));
|
||||
this._register(this.menubarPart.onFocusStateChange(e => this.onMenubarFocusChanged(e)));
|
||||
}
|
||||
@@ -355,7 +357,7 @@ export class TitlebarPart extends Part implements ITitleService {
|
||||
}
|
||||
|
||||
// Maximize/Restore on doubleclick
|
||||
if (isMacintosh) {
|
||||
if (isMacintosh && !isWeb) {
|
||||
this._register(addDisposableListener(this.element, EventType.DBLCLICK, e => {
|
||||
EventHelper.stop(e);
|
||||
|
||||
@@ -374,8 +376,8 @@ export class TitlebarPart extends Part implements ITitleService {
|
||||
}));
|
||||
});
|
||||
|
||||
// Window Controls (Windows/Linux)
|
||||
if (!isMacintosh) {
|
||||
// Window Controls (Native Windows/Linux)
|
||||
if (!isMacintosh && !isWeb) {
|
||||
this.windowControls = append(this.element, $('div.window-controls-container'));
|
||||
|
||||
|
||||
@@ -546,17 +548,24 @@ export class TitlebarPart extends Part implements ITitleService {
|
||||
}
|
||||
|
||||
private adjustTitleMarginToCenter(): void {
|
||||
if (!isMacintosh &&
|
||||
(this.appIcon.clientWidth + this.menubar.clientWidth + 10 > (this.element.clientWidth - this.title.clientWidth) / 2 ||
|
||||
this.element.clientWidth - this.windowControls.clientWidth - 10 < (this.element.clientWidth + this.title.clientWidth) / 2)) {
|
||||
this.title.style.position = null;
|
||||
this.title.style.left = null;
|
||||
this.title.style.transform = null;
|
||||
} else {
|
||||
this.title.style.position = 'absolute';
|
||||
this.title.style.left = '50%';
|
||||
this.title.style.transform = 'translate(-50%, 0)';
|
||||
if (!isMacintosh || isWeb) {
|
||||
const leftMarker = (this.appIcon ? this.appIcon.clientWidth : 0) + this.menubar.clientWidth + 10;
|
||||
const rightMarker = this.element.clientWidth - (this.windowControls ? this.windowControls.clientWidth : 0) - 10;
|
||||
|
||||
// Not enough space to center the titlebar within window,
|
||||
// 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.transform = null;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.title.style.position = 'absolute';
|
||||
this.title.style.left = '50%';
|
||||
this.title.style.transform = 'translate(-50%, 0)';
|
||||
}
|
||||
|
||||
updateLayout(dimension: Dimension): void {
|
||||
@@ -564,15 +573,15 @@ export class TitlebarPart extends Part implements ITitleService {
|
||||
|
||||
if (getTitleBarStyle(this.configurationService, this.environmentService) === 'custom') {
|
||||
// Only prevent zooming behavior on macOS or when the menubar is not visible
|
||||
if (isMacintosh || this.configurationService.getValue<MenuBarVisibility>('window.menuBarVisibility') === 'hidden') {
|
||||
if ((!isWeb && isMacintosh) || this.configurationService.getValue<MenuBarVisibility>('window.menuBarVisibility') === 'hidden') {
|
||||
this.title.style.zoom = `${1 / getZoomFactor()}`;
|
||||
if (isWindows || isLinux) {
|
||||
if (!isWeb && (isWindows || isLinux)) {
|
||||
this.appIcon.style.zoom = `${1 / getZoomFactor()}`;
|
||||
this.windowControls.style.zoom = `${1 / getZoomFactor()}`;
|
||||
}
|
||||
} else {
|
||||
this.title.style.zoom = null;
|
||||
if (isWindows || isLinux) {
|
||||
if (!isWeb && (isWindows || isLinux)) {
|
||||
this.appIcon.style.zoom = null;
|
||||
this.windowControls.style.zoom = null;
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ import { IViewsService, ITreeView, ITreeItem, TreeItemCollapsibleState, ITreeVie
|
||||
import { IViewletViewOptions, FileIconThemableWorkbenchTree } from 'vs/workbench/browser/parts/views/viewsViewlet';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { IProgressService2 } from 'vs/platform/progress/common/progress';
|
||||
import { IProgressService } from 'vs/platform/progress/common/progress';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
@@ -60,9 +60,9 @@ export class CustomTreeViewPanel extends ViewletPanel {
|
||||
super({ ...(options as IViewletPanelOptions), ariaHeaderLabel: options.title }, keybindingService, contextMenuService, configurationService);
|
||||
const { treeView } = (<ITreeViewDescriptor>Registry.as<IViewsRegistry>(Extensions.ViewsRegistry).getView(options.id));
|
||||
this.treeView = treeView;
|
||||
this.treeView.onDidChangeActions(() => this.updateActions(), this, this.disposables);
|
||||
this.disposables.push(toDisposable(() => this.treeView.setVisibility(false)));
|
||||
this.disposables.push(this.onDidChangeBodyVisibility(() => this.updateTreeVisibility()));
|
||||
this._register(this.treeView.onDidChangeActions(() => this.updateActions(), this));
|
||||
this._register(toDisposable(() => this.treeView.setVisibility(false)));
|
||||
this._register(this.onDidChangeBodyVisibility(() => this.updateTreeVisibility()));
|
||||
this.updateTreeVisibility();
|
||||
}
|
||||
|
||||
@@ -98,11 +98,6 @@ export class CustomTreeViewPanel extends ViewletPanel {
|
||||
private updateTreeVisibility(): void {
|
||||
this.treeView.setVisibility(this.isBodyVisible());
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
dispose(this.disposables);
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
class TitleMenus implements IDisposable {
|
||||
@@ -216,7 +211,7 @@ export class CustomTreeView extends Disposable implements ITreeView {
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
@ICommandService private readonly commandService: ICommandService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
@IProgressService2 private readonly progressService: IProgressService2
|
||||
@IProgressService private readonly progressService: IProgressService
|
||||
) {
|
||||
super();
|
||||
this.root = new Root();
|
||||
|
||||
@@ -46,16 +46,16 @@ export abstract class ViewletPanel extends Panel implements IView {
|
||||
|
||||
private static AlwaysShowActionsConfig = 'workbench.view.alwaysShowHeaderActions';
|
||||
|
||||
private _onDidFocus = new Emitter<void>();
|
||||
private _onDidFocus = this._register(new Emitter<void>());
|
||||
readonly onDidFocus: Event<void> = this._onDidFocus.event;
|
||||
|
||||
private _onDidBlur = new Emitter<void>();
|
||||
private _onDidBlur = this._register(new Emitter<void>());
|
||||
readonly onDidBlur: Event<void> = this._onDidBlur.event;
|
||||
|
||||
private _onDidChangeBodyVisibility = new Emitter<boolean>();
|
||||
private _onDidChangeBodyVisibility = this._register(new Emitter<boolean>());
|
||||
readonly onDidChangeBodyVisibility: Event<boolean> = this._onDidChangeBodyVisibility.event;
|
||||
|
||||
protected _onDidChangeTitleArea = new Emitter<void>();
|
||||
protected _onDidChangeTitleArea = this._register(new Emitter<void>());
|
||||
readonly onDidChangeTitleArea: Event<void> = this._onDidChangeTitleArea.event;
|
||||
|
||||
private _isVisible: boolean = false;
|
||||
@@ -78,8 +78,6 @@ export abstract class ViewletPanel extends Panel implements IView {
|
||||
this.id = options.id;
|
||||
this.title = options.title;
|
||||
this.actionRunner = options.actionRunner;
|
||||
|
||||
this.disposables.push(this._onDidFocus, this._onDidBlur, this._onDidChangeBodyVisibility, this._onDidChangeTitleArea);
|
||||
}
|
||||
|
||||
setVisible(visible: boolean): void {
|
||||
@@ -113,9 +111,9 @@ export abstract class ViewletPanel extends Panel implements IView {
|
||||
super.render();
|
||||
|
||||
const focusTracker = trackFocus(this.element);
|
||||
this.disposables.push(focusTracker);
|
||||
this.disposables.push(focusTracker.onDidFocus(() => this._onDidFocus.fire()));
|
||||
this.disposables.push(focusTracker.onDidBlur(() => this._onDidBlur.fire()));
|
||||
this._register(focusTracker);
|
||||
this._register(focusTracker.onDidFocus(() => this._onDidFocus.fire()));
|
||||
this._register(focusTracker.onDidBlur(() => this._onDidBlur.fire()));
|
||||
}
|
||||
|
||||
protected renderHeader(container: HTMLElement): void {
|
||||
@@ -132,11 +130,11 @@ export abstract class ViewletPanel extends Panel implements IView {
|
||||
actionRunner: this.actionRunner
|
||||
});
|
||||
|
||||
this.disposables.push(this.toolbar);
|
||||
this._register(this.toolbar);
|
||||
this.setActions();
|
||||
|
||||
const onDidRelevantConfigurationChange = Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration(ViewletPanel.AlwaysShowActionsConfig));
|
||||
onDidRelevantConfigurationChange(this.updateActionsVisibility, this, this.disposables);
|
||||
this._register(onDidRelevantConfigurationChange(this.updateActionsVisibility, this));
|
||||
this.updateActionsVisibility();
|
||||
}
|
||||
|
||||
@@ -355,7 +353,7 @@ export class PanelViewlet extends Viewlet {
|
||||
headerBorder: SIDE_BAR_SECTION_HEADER_BORDER,
|
||||
dropBackground: SIDE_BAR_DRAG_AND_DROP_BACKGROUND
|
||||
}, panel);
|
||||
const disposable = combinedDisposable([onDidFocus, onDidChangeTitleArea, panelStyler, onDidChange]);
|
||||
const disposable = combinedDisposable(onDidFocus, onDidChangeTitleArea, panelStyler, onDidChange);
|
||||
const panelItem: IViewletPanelItem = { panel, disposable };
|
||||
|
||||
this.panelItems.splice(index, 0, panelItem);
|
||||
|
||||
@@ -29,14 +29,15 @@ import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/la
|
||||
import { localize } from 'vs/nls';
|
||||
import { IAddedViewDescriptorRef, IViewDescriptorRef, PersistentContributableViewsModel } from 'vs/workbench/browser/parts/views/views';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { MementoObject } from 'vs/workbench/common/memento';
|
||||
|
||||
export interface IViewletViewOptions extends IViewletPanelOptions {
|
||||
viewletState: object;
|
||||
viewletState: MementoObject;
|
||||
}
|
||||
|
||||
export abstract class ViewContainerViewlet extends PanelViewlet implements IViewsViewlet {
|
||||
|
||||
private readonly viewletState: object;
|
||||
private readonly viewletState: MementoObject;
|
||||
private didLayout = false;
|
||||
private dimension: DOM.Dimension;
|
||||
private areExtensionsReady: boolean = false;
|
||||
@@ -207,7 +208,7 @@ export abstract class ViewContainerViewlet extends PanelViewlet implements IView
|
||||
this.viewsModel.setCollapsed(viewDescriptor.id, collapsed);
|
||||
});
|
||||
|
||||
this.viewDisposables.splice(index, 0, combinedDisposable([contextMenuDisposable, collapseDisposable]));
|
||||
this.viewDisposables.splice(index, 0, combinedDisposable(contextMenuDisposable, collapseDisposable));
|
||||
panelsToAdd.push({ panel, size: size || panel.minimumSize, index });
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user