/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { Event } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { IContextKeyService, IContextKey, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { InputFocusedContext, IsMacContext, IsLinuxContext, IsWindowsContext, IsWebContext, IsMacNativeContext, IsDevelopmentContext } from 'vs/platform/contextkey/common/contextkeys'; import { ActiveEditorContext, EditorsVisibleContext, TextCompareEditorVisibleContext, TextCompareEditorActiveContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, TEXT_DIFF_EDITOR_ID, SplitEditorsVertically, InEditorZenModeContext, IsCenteredLayoutContext, ActiveEditorGroupIndexContext, ActiveEditorGroupLastContext, ActiveEditorReadonlyContext, EditorAreaVisibleContext, ActiveEditorAvailableEditorIdsContext } from 'vs/workbench/common/editor'; import { trackFocus, addDisposableListener, EventType, WebFileSystemAccess } from 'vs/base/browser/dom'; import { preferredSideBySideGroupDirection, GroupDirection, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { WorkbenchState, IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { SideBarVisibleContext } from 'vs/workbench/common/viewlet'; import { IWorkbenchLayoutService, Parts, positionToString } from 'vs/workbench/services/layout/browser/layoutService'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { PanelPositionContext } from 'vs/workbench/common/panel'; import { getRemoteName } from 'vs/platform/remote/common/remoteHosts'; import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { isNative } from 'vs/base/common/platform'; export const WorkbenchStateContext = new RawContextKey('workbenchState', undefined); export const WorkspaceFolderCountContext = new RawContextKey('workspaceFolderCount', 0); export const EmptyWorkspaceSupportContext = new RawContextKey('emptyWorkspaceSupport', true); export const DirtyWorkingCopiesContext = new RawContextKey('dirtyWorkingCopies', false); export const RemoteNameContext = new RawContextKey('remoteName', ''); export const IsFullscreenContext = new RawContextKey('isFullscreen', false); // Support for FileSystemAccess web APIs (https://wicg.github.io/file-system-access) export const HasWebFileSystemAccess = new RawContextKey('hasWebFileSystemAccess', false); export class WorkbenchContextKeysHandler extends Disposable { private inputFocusedContext: IContextKey; private dirtyWorkingCopiesContext: IContextKey; private activeEditorContext: IContextKey; private activeEditorIsReadonly: IContextKey; private activeEditorAvailableEditorIds: IContextKey; private activeEditorGroupEmpty: IContextKey; private activeEditorGroupIndex: IContextKey; private activeEditorGroupLast: IContextKey; private multipleEditorGroupsContext: IContextKey; private editorsVisibleContext: IContextKey; private textCompareEditorVisibleContext: IContextKey; private textCompareEditorActiveContext: IContextKey; private splitEditorsVerticallyContext: IContextKey; private workbenchStateContext: IContextKey; private workspaceFolderCountContext: IContextKey; private emptyWorkspaceSupportContext: IContextKey; private inZenModeContext: IContextKey; private isFullscreenContext: IContextKey; private isCenteredLayoutContext: IContextKey; private sideBarVisibleContext: IContextKey; private editorAreaVisibleContext: IContextKey; private panelPositionContext: IContextKey; constructor( @IContextKeyService private readonly contextKeyService: IContextKeyService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @IConfigurationService private readonly configurationService: IConfigurationService, @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, @IEditorService private readonly editorService: IEditorService, @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, @IViewletService private readonly viewletService: IViewletService, @IWorkingCopyService private readonly workingCopyService: IWorkingCopyService ) { super(); // Platform IsMacContext.bindTo(this.contextKeyService); IsLinuxContext.bindTo(this.contextKeyService); IsWindowsContext.bindTo(this.contextKeyService); IsWebContext.bindTo(this.contextKeyService); IsMacNativeContext.bindTo(this.contextKeyService); RemoteNameContext.bindTo(this.contextKeyService).set(getRemoteName(this.environmentService.remoteAuthority) || ''); // Capabilities HasWebFileSystemAccess.bindTo(this.contextKeyService).set(WebFileSystemAccess.supported(window)); // Development IsDevelopmentContext.bindTo(this.contextKeyService).set(!this.environmentService.isBuilt || this.environmentService.isExtensionDevelopment); // Editors this.activeEditorContext = ActiveEditorContext.bindTo(this.contextKeyService); this.activeEditorIsReadonly = ActiveEditorReadonlyContext.bindTo(this.contextKeyService); this.activeEditorAvailableEditorIds = ActiveEditorAvailableEditorIdsContext.bindTo(this.contextKeyService); this.editorsVisibleContext = EditorsVisibleContext.bindTo(this.contextKeyService); this.textCompareEditorVisibleContext = TextCompareEditorVisibleContext.bindTo(this.contextKeyService); this.textCompareEditorActiveContext = TextCompareEditorActiveContext.bindTo(this.contextKeyService); this.activeEditorGroupEmpty = ActiveEditorGroupEmptyContext.bindTo(this.contextKeyService); this.activeEditorGroupIndex = ActiveEditorGroupIndexContext.bindTo(this.contextKeyService); this.activeEditorGroupLast = ActiveEditorGroupLastContext.bindTo(this.contextKeyService); this.multipleEditorGroupsContext = MultipleEditorGroupsContext.bindTo(this.contextKeyService); // Working Copies this.dirtyWorkingCopiesContext = DirtyWorkingCopiesContext.bindTo(this.contextKeyService); this.dirtyWorkingCopiesContext.set(this.workingCopyService.hasDirty); // Inputs this.inputFocusedContext = InputFocusedContext.bindTo(this.contextKeyService); // Workbench State this.workbenchStateContext = WorkbenchStateContext.bindTo(this.contextKeyService); this.updateWorkbenchStateContextKey(); // Workspace Folder Count this.workspaceFolderCountContext = WorkspaceFolderCountContext.bindTo(this.contextKeyService); this.updateWorkspaceFolderCountContextKey(); // Empty workspace support: empty workspaces require a default "local" file // system to operate with. We always have one when running natively or when // we have a remote connection. this.emptyWorkspaceSupportContext = EmptyWorkspaceSupportContext.bindTo(this.contextKeyService); this.emptyWorkspaceSupportContext.set(isNative || typeof this.environmentService.remoteAuthority === 'string'); // Editor Layout this.splitEditorsVerticallyContext = SplitEditorsVertically.bindTo(this.contextKeyService); this.updateSplitEditorsVerticallyContext(); // Fullscreen this.isFullscreenContext = IsFullscreenContext.bindTo(this.contextKeyService); // Zen Mode this.inZenModeContext = InEditorZenModeContext.bindTo(this.contextKeyService); // Centered Layout this.isCenteredLayoutContext = IsCenteredLayoutContext.bindTo(this.contextKeyService); // Editor Area this.editorAreaVisibleContext = EditorAreaVisibleContext.bindTo(this.contextKeyService); // Sidebar this.sideBarVisibleContext = SideBarVisibleContext.bindTo(this.contextKeyService); // Panel Position this.panelPositionContext = PanelPositionContext.bindTo(this.contextKeyService); this.panelPositionContext.set(positionToString(this.layoutService.getPanelPosition())); this.registerListeners(); } private registerListeners(): void { this.editorGroupService.whenRestored.then(() => this.updateEditorContextKeys()); this._register(this.editorService.onDidActiveEditorChange(() => this.updateEditorContextKeys())); this._register(this.editorService.onDidVisibleEditorsChange(() => this.updateEditorContextKeys())); this._register(this.editorGroupService.onDidAddGroup(() => this.updateEditorContextKeys())); this._register(this.editorGroupService.onDidRemoveGroup(() => this.updateEditorContextKeys())); this._register(this.editorGroupService.onDidGroupIndexChange(() => this.updateEditorContextKeys())); this._register(addDisposableListener(window, EventType.FOCUS_IN, () => this.updateInputContextKeys(), true)); this._register(this.contextService.onDidChangeWorkbenchState(() => this.updateWorkbenchStateContextKey())); this._register(this.contextService.onDidChangeWorkspaceFolders(() => this.updateWorkspaceFolderCountContextKey())); this._register(this.configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration('workbench.editor.openSideBySideDirection')) { this.updateSplitEditorsVerticallyContext(); } })); this._register(this.layoutService.onZenModeChange(enabled => this.inZenModeContext.set(enabled))); this._register(this.layoutService.onFullscreenChange(fullscreen => this.isFullscreenContext.set(fullscreen))); this._register(this.layoutService.onCenteredLayoutChange(centered => this.isCenteredLayoutContext.set(centered))); this._register(this.layoutService.onPanelPositionChange(position => this.panelPositionContext.set(position))); this._register(this.viewletService.onDidViewletClose(() => this.updateSideBarContextKeys())); this._register(this.viewletService.onDidViewletOpen(() => this.updateSideBarContextKeys())); this._register(this.layoutService.onPartVisibilityChange(() => this.editorAreaVisibleContext.set(this.layoutService.isVisible(Parts.EDITOR_PART)))); this._register(this.workingCopyService.onDidChangeDirty(workingCopy => this.dirtyWorkingCopiesContext.set(workingCopy.isDirty() || this.workingCopyService.hasDirty))); } private updateEditorContextKeys(): void { const activeGroup = this.editorGroupService.activeGroup; const activeEditorPane = this.editorService.activeEditorPane; const visibleEditorPanes = this.editorService.visibleEditorPanes; this.textCompareEditorActiveContext.set(activeEditorPane?.getId() === TEXT_DIFF_EDITOR_ID); this.textCompareEditorVisibleContext.set(visibleEditorPanes.some(editorPane => editorPane.getId() === TEXT_DIFF_EDITOR_ID)); if (visibleEditorPanes.length > 0) { this.editorsVisibleContext.set(true); } else { this.editorsVisibleContext.reset(); } if (!this.editorService.activeEditor) { this.activeEditorGroupEmpty.set(true); } else { this.activeEditorGroupEmpty.reset(); } const groupCount = this.editorGroupService.count; if (groupCount > 1) { this.multipleEditorGroupsContext.set(true); } else { this.multipleEditorGroupsContext.reset(); } this.activeEditorGroupIndex.set(activeGroup.index + 1); // not zero-indexed this.activeEditorGroupLast.set(activeGroup.index === groupCount - 1); if (activeEditorPane) { this.activeEditorContext.set(activeEditorPane.getId()); this.activeEditorIsReadonly.set(activeEditorPane.input.isReadonly()); const activeEditorResource = activeEditorPane.input.resource; const editors = activeEditorResource ? this.editorService.getEditorOverrides(activeEditorResource, undefined, activeGroup) : []; this.activeEditorAvailableEditorIds.set(editors.map(([_, entry]) => entry.id).join(',')); } else { this.activeEditorContext.reset(); this.activeEditorIsReadonly.reset(); this.activeEditorAvailableEditorIds.reset(); } } private updateInputContextKeys(): void { function activeElementIsInput(): boolean { return !!document.activeElement && (document.activeElement.tagName === 'INPUT' || document.activeElement.tagName === 'TEXTAREA'); } const isInputFocused = activeElementIsInput(); this.inputFocusedContext.set(isInputFocused); if (isInputFocused) { const tracker = trackFocus(document.activeElement as HTMLElement); Event.once(tracker.onDidBlur)(() => { this.inputFocusedContext.set(activeElementIsInput()); tracker.dispose(); }); } } private updateWorkbenchStateContextKey(): void { this.workbenchStateContext.set(this.getWorkbenchStateString()); } private updateWorkspaceFolderCountContextKey(): void { this.workspaceFolderCountContext.set(this.contextService.getWorkspace().folders.length); } private updateSplitEditorsVerticallyContext(): void { const direction = preferredSideBySideGroupDirection(this.configurationService); this.splitEditorsVerticallyContext.set(direction === GroupDirection.DOWN); } private getWorkbenchStateString(): string { switch (this.contextService.getWorkbenchState()) { case WorkbenchState.EMPTY: return 'empty'; case WorkbenchState.FOLDER: return 'folder'; case WorkbenchState.WORKSPACE: return 'workspace'; } } private updateSideBarContextKeys(): void { this.sideBarVisibleContext.set(this.layoutService.isVisible(Parts.SIDEBAR_PART)); } }