Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 52 additions & 5 deletions src/vs/workbench/contrib/files/browser/views/explorerView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { IFilesConfiguration, ExplorerFolderContext, FilesExplorerFocusedContext
import { FileCopiedContext, NEW_FILE_COMMAND_ID, NEW_FOLDER_COMMAND_ID } from '../fileActions.js';
import * as DOM from '../../../../../base/browser/dom.js';
import { IWorkbenchLayoutService } from '../../../../services/layout/browser/layoutService.js';
import { IWorkspaceContextService, WorkbenchState } from '../../../../../platform/workspace/common/workspace.js';
import { isUntitledWorkspace, IWorkspaceContextService, WorkbenchState } from '../../../../../platform/workspace/common/workspace.js';
import { IConfigurationService, IConfigurationChangeEvent } from '../../../../../platform/configuration/common/configuration.js';
import { IKeybindingService } from '../../../../../platform/keybinding/common/keybinding.js';
import { IInstantiationService, ServicesAccessor } from '../../../../../platform/instantiation/common/instantiation.js';
Expand All @@ -38,7 +38,7 @@ import { IAsyncDataTreeViewState } from '../../../../../base/browser/ui/tree/asy
import { FuzzyScore } from '../../../../../base/common/filters.js';
import { IClipboardService } from '../../../../../platform/clipboard/common/clipboardService.js';
import { IFileService, FileSystemProviderCapabilities } from '../../../../../platform/files/common/files.js';
import { IDisposable } from '../../../../../base/common/lifecycle.js';
import { IDisposable, toDisposable } from '../../../../../base/common/lifecycle.js';
import { Event } from '../../../../../base/common/event.js';
import { IViewDescriptorService } from '../../../../common/views.js';
import { IViewsService } from '../../../../services/views/common/viewsService.js';
Expand All @@ -54,6 +54,7 @@ import { ResourceMap } from '../../../../../base/common/map.js';
import { AbstractTreePart } from '../../../../../base/browser/ui/tree/abstractTree.js';
import { IHoverService } from '../../../../../platform/hover/browser/hover.js';
import { IAccessibilityService } from '../../../../../platform/accessibility/common/accessibility.js';
import { IEnvironmentService } from '../../../../../platform/environment/common/environment.js';


function hasExpandedRootChild(tree: WorkbenchCompressibleAsyncDataTree<ExplorerItem | ExplorerItem[], ExplorerItem, FuzzyScore>, treeInput: ExplorerItem[]): boolean {
Expand Down Expand Up @@ -150,6 +151,9 @@ export interface IExplorerViewPaneOptions extends IViewPaneOptions {
}

export class ExplorerView extends ViewPane implements IExplorerView {

private static readonly preserveWorkspaceNameCaseClass = 'preserve-workspace-name-case';
private static readonly preserveMergedWorkspaceNameCaseClass = 'preserve-merged-workspace-name-case';
static readonly TREE_VIEW_STATE_STORAGE_KEY: string = 'workbench.explorer.treeViewState';

private tree!: WorkbenchCompressibleAsyncDataTree<ExplorerItem | ExplorerItem[], ExplorerItem, FuzzyScore>;
Expand Down Expand Up @@ -182,6 +186,7 @@ export class ExplorerView extends ViewPane implements IExplorerView {
private dragHandler!: DelayedDragHandler;
private _autoReveal: boolean | 'force' | 'focusNoScroll' = false;
private readonly delegate: IExplorerViewContainerDelegate | undefined;
private workspaceTitleContainer: HTMLElement | undefined;

override get singleViewPaneContainerTitle(): string {
return this.name;
Expand Down Expand Up @@ -211,7 +216,8 @@ export class ExplorerView extends ViewPane implements IExplorerView {
@IUriIdentityService private readonly uriIdentityService: IUriIdentityService,
@ICommandService private readonly commandService: ICommandService,
@IOpenerService openerService: IOpenerService,
@IAccessibilityService private readonly accessibilityService: IAccessibilityService
@IAccessibilityService private readonly accessibilityService: IAccessibilityService,
@IEnvironmentService private readonly environmentService: IEnvironmentService
) {
super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, hoverService);

Expand All @@ -231,8 +237,8 @@ export class ExplorerView extends ViewPane implements IExplorerView {
this.viewHasSomeCollapsibleRootItem = ViewHasSomeCollapsibleRootItemContext.bindTo(contextKeyService);
this.viewVisibleContextKey = FoldersViewVisibleContext.bindTo(contextKeyService);


this.explorerService.registerView(this);
this._register(toDisposable(() => this.clearWorkspaceTitleContainer()));
}

get autoReveal() {
Expand All @@ -255,9 +261,24 @@ export class ExplorerView extends ViewPane implements IExplorerView {
// noop
}

override get headerVisible(): boolean {
return super.headerVisible;
}

override set headerVisible(visible: boolean) {
super.headerVisible = visible;
this.updateWorkspaceTitleCase();
}

override setVisible(visible: boolean): void {
if (!visible) {
this.clearWorkspaceTitleContainer();
}
this.viewVisibleContextKey.set(visible);
super.setVisible(visible);
if (visible) {
this.updateWorkspaceTitleContainer();
}
}

@memoize private get fileCopiedContextKey(): IContextKey<boolean> {
Expand Down Expand Up @@ -285,11 +306,37 @@ export class ExplorerView extends ViewPane implements IExplorerView {
titleElement.setAttribute('aria-label', this.ariaHeaderLabel);
};

this._register(this.contextService.onDidChangeWorkspaceName(setHeader));
this._register(this.contextService.onDidChangeWorkspaceName(() => {
setHeader();
this.updateWorkspaceTitleCase();
}));
this._register(this.contextService.onDidChangeWorkbenchState(() => this.updateWorkspaceTitleCase()));
this._register(this.labelService.onDidChangeFormatters(setHeader));
setHeader();
}

private updateWorkspaceTitleContainer(): void {
const workspaceTitleContainer = DOM.findParentWithClass(this.element, 'part') ?? undefined;
if (this.workspaceTitleContainer !== workspaceTitleContainer) {
this.workspaceTitleContainer?.classList.remove(ExplorerView.preserveMergedWorkspaceNameCaseClass);
this.workspaceTitleContainer = workspaceTitleContainer;
}
this.updateWorkspaceTitleCase();
}

private clearWorkspaceTitleContainer(): void {
this.workspaceTitleContainer?.classList.remove(ExplorerView.preserveMergedWorkspaceNameCaseClass);
this.workspaceTitleContainer = undefined;
}

private updateWorkspaceTitleCase(): void {
const workspace = this.contextService.getWorkspace();
const isUntitled = workspace.configuration ? isUntitledWorkspace(workspace.configuration, this.environmentService) : false;
const preserveWorkspaceNameCase = this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY && !isUntitled;
this.element.classList.toggle(ExplorerView.preserveWorkspaceNameCaseClass, preserveWorkspaceNameCase);
this.workspaceTitleContainer?.classList.toggle(ExplorerView.preserveMergedWorkspaceNameCaseClass, preserveWorkspaceNameCase && this.isVisible() && !this.headerVisible);
}

protected override layoutBody(height: number, width: number): void {
super.layoutBody(height, width);
this.tree.layout(height, width);
Expand Down
12 changes: 4 additions & 8 deletions src/vs/workbench/contrib/modernUI/browser/media/fontRamp.css
Original file line number Diff line number Diff line change
Expand Up @@ -134,15 +134,11 @@
}

/*
* File Explorer titles — respect the real casing of the workspace / folder name
* (user input). `text-transform: none` overrides both the base `uppercase` rule
* and the `capitalize` override above. System labels such as "Untitled
* (Workspace)" are already title-cased in source, so they still render
* correctly; only user-provided folder names (e.g. "vscode-dev") are left
* untouched.
* Named File Explorer workspace titles preserve the casing supplied by the
* user. Untitled workspaces continue to use the standard capitalized header.
*/
.modern-ui.monaco-workbench .part:not(.editor)[data-active-composite="workbench.view.explorer"] > .title > .title-label h2,
.modern-ui .monaco-pane-view .pane > .pane-header > .icon.codicon-explorer-view-icon + .title {
.modern-ui.monaco-workbench .part.preserve-merged-workspace-name-case > .title > .title-label h2,
.modern-ui .pane.preserve-workspace-name-case > .pane-header > .icon.codicon-explorer-view-icon + .title {
text-transform: none;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -282,11 +282,22 @@ suite('ModernUIContribution', () => {
const paneHeader = appendElement(appendElement(paneView, 'pane'), 'pane-header');
const paneTitle = appendElement(paneHeader, 'title');

const explorerPart = appendElement(layoutService.mainContainer, 'part');
explorerPart.dataset.activeComposite = 'workbench.view.explorer';
const explorerPart = appendElement(layoutService.mainContainer, 'part preserve-merged-workspace-name-case');
explorerPart.dataset.activeComposite = 'workbench.views.service.sidebar.custom';
const explorerTitleLabel = appendElement(appendElement(explorerPart, 'title'), 'title-label');
const explorerTitle = document.createElement('h2');
explorerTitleLabel.appendChild(explorerTitle);
const explorerPaneHeader = appendElement(appendElement(appendElement(explorerPart, 'monaco-pane-view'), 'pane preserve-workspace-name-case'), 'pane-header');
appendElement(explorerPaneHeader, 'icon codicon-explorer-view-icon');
const explorerPaneTitle = appendElement(explorerPaneHeader, 'title');
const multiViewPart = appendElement(layoutService.mainContainer, 'part');
multiViewPart.dataset.activeComposite = 'workbench.views.service.sidebar.multiView';
const multiViewTitleLabel = appendElement(appendElement(multiViewPart, 'title'), 'title-label');
const multiViewTitle = document.createElement('h2');
multiViewTitleLabel.appendChild(multiViewTitle);
const multiViewExplorerPaneHeader = appendElement(appendElement(appendElement(multiViewPart, 'monaco-pane-view'), 'pane preserve-workspace-name-case'), 'pane-header');
appendElement(multiViewExplorerPaneHeader, 'icon codicon-explorer-view-icon');
const multiViewExplorerPaneTitle = appendElement(multiViewExplorerPaneHeader, 'title');
const extensionsPart = appendElement(layoutService.mainContainer, 'part');
const extensionsTitleLabel = appendElement(appendElement(extensionsPart, 'title'), 'title-label');
const extensionsTitle = document.createElement('h2');
Expand All @@ -300,6 +311,9 @@ suite('ModernUIContribution', () => {
classApplied: layoutService.mainContainer.classList.contains('modern-ui-uppercase-view-headers'),
paneTitleTransform: targetWindow.getComputedStyle(paneTitle).textTransform,
explorerTitleTransform: targetWindow.getComputedStyle(explorerTitle).textTransform,
explorerPaneTitleTransform: targetWindow.getComputedStyle(explorerPaneTitle).textTransform,
multiViewTitleTransform: targetWindow.getComputedStyle(multiViewTitle).textTransform,
multiViewExplorerPaneTitleTransform: targetWindow.getComputedStyle(multiViewExplorerPaneTitle).textTransform,
extensionsTitleTransform: targetWindow.getComputedStyle(extensionsTitle).textTransform,
panelTabTransform: targetWindow.getComputedStyle(panelTab).textTransform,
layoutCount: layoutService.layoutCount,
Expand All @@ -318,6 +332,9 @@ suite('ModernUIContribution', () => {
classApplied: layoutService.mainContainer.classList.contains('modern-ui-uppercase-view-headers'),
paneTitleTransform: targetWindow.getComputedStyle(paneTitle).textTransform,
explorerTitleTransform: targetWindow.getComputedStyle(explorerTitle).textTransform,
explorerPaneTitleTransform: targetWindow.getComputedStyle(explorerPaneTitle).textTransform,
multiViewTitleTransform: targetWindow.getComputedStyle(multiViewTitle).textTransform,
multiViewExplorerPaneTitleTransform: targetWindow.getComputedStyle(multiViewExplorerPaneTitle).textTransform,
extensionsTitleTransform: targetWindow.getComputedStyle(extensionsTitle).textTransform,
panelTabTransform: targetWindow.getComputedStyle(panelTab).textTransform,
layoutCount: layoutService.layoutCount,
Expand All @@ -326,19 +343,49 @@ suite('ModernUIContribution', () => {
classApplied: false,
paneTitleTransform: 'capitalize',
explorerTitleTransform: 'none',
explorerPaneTitleTransform: 'none',
multiViewTitleTransform: 'capitalize',
multiViewExplorerPaneTitleTransform: 'none',
extensionsTitleTransform: 'capitalize',
panelTabTransform: 'capitalize',
layoutCount: 0,
},
classApplied: true,
paneTitleTransform: 'uppercase',
explorerTitleTransform: 'uppercase',
explorerPaneTitleTransform: 'uppercase',
multiViewTitleTransform: 'uppercase',
multiViewExplorerPaneTitleTransform: 'uppercase',
extensionsTitleTransform: 'uppercase',
panelTabTransform: 'uppercase',
layoutCount: 0,
});
});

test('capitalizes Untitled Explorer titles by default', () => {
Comment thread
mrleemurray marked this conversation as resolved.
Outdated
const root = document.createElement('div');
root.className = 'monaco-workbench modern-ui';
const explorerPart = appendElement(root, 'part');
explorerPart.dataset.activeComposite = 'workbench.view.explorer';
const explorerTitleLabel = appendElement(appendElement(explorerPart, 'title'), 'title-label');
const explorerTitle = document.createElement('h2');
explorerTitleLabel.appendChild(explorerTitle);

document.body.appendChild(root);
store.add(toDisposable(() => root.remove()));
const targetWindow = getWindow(root);
const defaultTransform = targetWindow.getComputedStyle(explorerTitle).textTransform;
root.classList.add('modern-ui-uppercase-view-headers');

assert.deepStrictEqual({
defaultTransform,
uppercaseTransform: targetWindow.getComputedStyle(explorerTitle).textTransform,
}, {
defaultTransform: 'capitalize',
uppercaseTransform: 'uppercase',
});
});

test('pane composite actions fill regular and Agents headers', () => {
const regularRoot = document.createElement('div');
regularRoot.className = 'monaco-workbench modern-ui modern-ui-tabs';
Expand Down
Loading