mirror of
https://github.com/Microsoft/vscode
synced 2024-10-02 17:32:41 +00:00
Workbench - ability to contribute window title variables (#204538)
--------- Co-authored-by: Benjamin Pasero <benjamin.pasero@microsoft.com>
This commit is contained in:
parent
c4a1ed80ec
commit
b05778eb90
|
@ -120,6 +120,8 @@ export class SettingsDocument {
|
|||
completions.push(this.newSimpleCompletionItem(getText('remoteName'), range, vscode.l10n.t("e.g. SSH")));
|
||||
completions.push(this.newSimpleCompletionItem(getText('dirty'), range, vscode.l10n.t("an indicator for when the active editor has unsaved changes")));
|
||||
completions.push(this.newSimpleCompletionItem(getText('separator'), range, vscode.l10n.t("a conditional separator (' - ') that only shows when surrounded by variables with values")));
|
||||
completions.push(this.newSimpleCompletionItem(getText('activeRepositoryName'), range, vscode.l10n.t("the name of the active repository (e.g. vscode)")));
|
||||
completions.push(this.newSimpleCompletionItem(getText('activeRepositoryBranchName'), range, vscode.l10n.t("the name of the active branch in the active repository (e.g. main)")));
|
||||
|
||||
return completions;
|
||||
}
|
||||
|
|
|
@ -55,6 +55,11 @@ import { mainWindow } from 'vs/base/browser/window';
|
|||
import { ACCOUNTS_ACTIVITY_TILE_ACTION, GLOBAL_ACTIVITY_TITLE_ACTION } from 'vs/workbench/browser/parts/titlebar/titlebarActions';
|
||||
import { IView } from 'vs/base/browser/ui/grid/grid';
|
||||
|
||||
export interface ITitleVariable {
|
||||
readonly name: string;
|
||||
readonly contextKey: string;
|
||||
}
|
||||
|
||||
export interface ITitleProperties {
|
||||
isPure?: boolean;
|
||||
isAdmin?: boolean;
|
||||
|
@ -72,6 +77,11 @@ export interface ITitlebarPart extends IDisposable {
|
|||
* Update some environmental title properties.
|
||||
*/
|
||||
updateProperties(properties: ITitleProperties): void;
|
||||
|
||||
/**
|
||||
* Adds variables to be supported in the window title.
|
||||
*/
|
||||
registerVariables(variables: ITitleVariable[]): void;
|
||||
}
|
||||
|
||||
export class BrowserTitleService extends MultiWindowParts<BrowserTitlebarPart> implements ITitleService {
|
||||
|
@ -134,6 +144,14 @@ export class BrowserTitleService extends MultiWindowParts<BrowserTitlebarPart> i
|
|||
disposables.add(Event.runAndSubscribe(titlebarPart.onDidChange, () => titlebarPartContainer.style.height = `${titlebarPart.height}px`));
|
||||
titlebarPart.create(titlebarPartContainer);
|
||||
|
||||
if (this.properties) {
|
||||
titlebarPart.updateProperties(this.properties);
|
||||
}
|
||||
|
||||
if (this.variables.length) {
|
||||
titlebarPart.registerVariables(this.variables);
|
||||
}
|
||||
|
||||
Event.once(titlebarPart.onWillDispose)(() => disposables.dispose());
|
||||
|
||||
return titlebarPart;
|
||||
|
@ -150,12 +168,26 @@ export class BrowserTitleService extends MultiWindowParts<BrowserTitlebarPart> i
|
|||
|
||||
readonly onMenubarVisibilityChange = this.mainPart.onMenubarVisibilityChange;
|
||||
|
||||
private properties: ITitleProperties | undefined = undefined;
|
||||
|
||||
updateProperties(properties: ITitleProperties): void {
|
||||
this.properties = properties;
|
||||
|
||||
for (const part of this.parts) {
|
||||
part.updateProperties(properties);
|
||||
}
|
||||
}
|
||||
|
||||
private variables: ITitleVariable[] = [];
|
||||
|
||||
registerVariables(variables: ITitleVariable[]): void {
|
||||
this.variables.push(...variables);
|
||||
|
||||
for (const part of this.parts) {
|
||||
part.registerVariables(variables);
|
||||
}
|
||||
}
|
||||
|
||||
//#endregion
|
||||
}
|
||||
|
||||
|
@ -379,6 +411,10 @@ export class BrowserTitlebarPart extends Part implements ITitlebarPart {
|
|||
this.windowTitle.updateProperties(properties);
|
||||
}
|
||||
|
||||
registerVariables(variables: ITitleVariable[]): void {
|
||||
this.windowTitle.registerVariables(variables);
|
||||
}
|
||||
|
||||
protected override createContentArea(parent: HTMLElement): HTMLElement {
|
||||
this.element = parent;
|
||||
this.rootContainer = append(parent, $('.titlebar-container'));
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
import { localize } from 'vs/nls';
|
||||
import { dirname, basename } from 'vs/base/common/resources';
|
||||
import { ITitleProperties } from 'vs/workbench/browser/parts/titlebar/titlebarPart';
|
||||
import { ITitleProperties, ITitleVariable } from 'vs/workbench/browser/parts/titlebar/titlebarPart';
|
||||
import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
|
@ -26,6 +26,7 @@ import { getVirtualWorkspaceLocation } from 'vs/platform/workspace/common/virtua
|
|||
import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile';
|
||||
import { IViewsService } from 'vs/workbench/services/views/common/viewsService';
|
||||
import { ICodeEditor, isCodeEditor, isDiffEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
|
||||
const enum WindowSettingNames {
|
||||
titleSeparator = 'window.titleSeparator',
|
||||
|
@ -39,6 +40,8 @@ export class WindowTitle extends Disposable {
|
|||
private static readonly TITLE_DIRTY = '\u25cf ';
|
||||
|
||||
private readonly properties: ITitleProperties = { isPure: true, isAdmin: false, prefix: undefined };
|
||||
private readonly variables = new Map<string /* context key */, string /* name */>();
|
||||
|
||||
private readonly activeEditorListeners = this._register(new DisposableStore());
|
||||
private readonly titleUpdater = this._register(new RunOnceScheduler(() => this.doUpdateTitle(), 0));
|
||||
|
||||
|
@ -66,6 +69,7 @@ export class WindowTitle extends Disposable {
|
|||
private readonly targetWindow: Window,
|
||||
editorGroupsContainer: IEditorGroupsContainer | 'main',
|
||||
@IConfigurationService protected readonly configurationService: IConfigurationService,
|
||||
@IContextKeyService private readonly contextKeyService: IContextKeyService,
|
||||
@IEditorService editorService: IEditorService,
|
||||
@IBrowserWorkbenchEnvironmentService protected readonly environmentService: IBrowserWorkbenchEnvironmentService,
|
||||
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
|
||||
|
@ -95,6 +99,11 @@ export class WindowTitle extends Disposable {
|
|||
this.titleUpdater.schedule();
|
||||
}
|
||||
}));
|
||||
this._register(this.contextKeyService.onDidChangeContext(e => {
|
||||
if (e.affectsSome(this.variables)) {
|
||||
this.titleUpdater.schedule();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private onConfigurationChanged(event: IConfigurationChangeEvent): void {
|
||||
|
@ -223,6 +232,22 @@ export class WindowTitle extends Disposable {
|
|||
}
|
||||
}
|
||||
|
||||
registerVariables(variables: ITitleVariable[]): void {
|
||||
let changed = false;
|
||||
|
||||
for (const { name, contextKey } of variables) {
|
||||
if (!this.variables.has(contextKey)) {
|
||||
this.variables.set(contextKey, name);
|
||||
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
this.titleUpdater.schedule();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Possible template values:
|
||||
*
|
||||
|
@ -303,6 +328,12 @@ export class WindowTitle extends Disposable {
|
|||
const titleTemplate = this.configurationService.getValue<string>(WindowSettingNames.title);
|
||||
const focusedView: string = this.viewsService.getFocusedViewName();
|
||||
|
||||
// Variables (contributed)
|
||||
const contributedVariables: { [key: string]: string } = {};
|
||||
for (const [contextKey, name] of this.variables) {
|
||||
contributedVariables[name] = this.contextKeyService.getContextKeyValue(contextKey) ?? '';
|
||||
}
|
||||
|
||||
return template(titleTemplate, {
|
||||
activeEditorShort,
|
||||
activeEditorLong,
|
||||
|
@ -320,6 +351,7 @@ export class WindowTitle extends Disposable {
|
|||
remoteName,
|
||||
profileName,
|
||||
focusedView,
|
||||
...contributedVariables,
|
||||
separator: { label: separator }
|
||||
});
|
||||
}
|
||||
|
|
|
@ -611,6 +611,8 @@ const registry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Con
|
|||
localize('remoteName', "`${remoteName}`: e.g. SSH"),
|
||||
localize('dirty', "`${dirty}`: an indicator for when the active editor has unsaved changes."),
|
||||
localize('focusedView', "`${focusedView}`: the name of the view that is currently focused."),
|
||||
localize('activeRepositoryName', "`${activeRepositoryName}`: the name of the active repository (e.g. vscode)."),
|
||||
localize('activeRepositoryBranchName', "`${activeRepositoryBranchName}`: the name of the active branch in the active repository (e.g. main)."),
|
||||
localize('separator', "`${separator}`: a conditional separator (\" - \") that only shows when surrounded by variables with values or static text.")
|
||||
].join('\n- '); // intentionally concatenated to not produce a string that is too long for translations
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ import { Event } from 'vs/base/common/event';
|
|||
import { VIEW_PANE_ID, ISCMService, ISCMRepository, ISCMViewService } from 'vs/workbench/contrib/scm/common/scm';
|
||||
import { IActivityService, NumberBadge } from 'vs/workbench/services/activity/common/activity';
|
||||
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
|
||||
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IStatusbarEntry, IStatusbarService, StatusbarAlignment as MainThreadStatusBarAlignment } from 'vs/workbench/services/statusbar/browser/statusbar';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
|
@ -18,6 +18,7 @@ import { EditorResourceAccessor } from 'vs/workbench/common/editor';
|
|||
import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { Iterable } from 'vs/base/common/iterator';
|
||||
import { ITitleService } from 'vs/workbench/services/title/browser/titleService';
|
||||
|
||||
function getCount(repository: ISCMRepository): number {
|
||||
if (typeof repository.provider.count === 'number') {
|
||||
|
@ -27,10 +28,18 @@ function getCount(repository: ISCMRepository): number {
|
|||
}
|
||||
}
|
||||
|
||||
const ContextKeys = {
|
||||
ActiveRepositoryName: new RawContextKey<string>('scmActiveRepositoryName', ''),
|
||||
ActiveRepositoryBranchName: new RawContextKey<string>('scmActiveRepositoryBranchName', ''),
|
||||
};
|
||||
|
||||
export class SCMStatusController implements IWorkbenchContribution {
|
||||
|
||||
private activeRepositoryNameContextKey: IContextKey<string>;
|
||||
private activeRepositoryBranchNameContextKey: IContextKey<string>;
|
||||
|
||||
private statusBarDisposable: IDisposable = Disposable.None;
|
||||
private focusDisposable: IDisposable = Disposable.None;
|
||||
private focusDisposables = new DisposableStore();
|
||||
private focusedRepository: ISCMRepository | undefined = undefined;
|
||||
private readonly badgeDisposable = new MutableDisposable<IDisposable>();
|
||||
private readonly disposables = new DisposableStore();
|
||||
|
@ -43,7 +52,9 @@ export class SCMStatusController implements IWorkbenchContribution {
|
|||
@IActivityService private readonly activityService: IActivityService,
|
||||
@IEditorService private readonly editorService: IEditorService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
@IUriIdentityService private readonly uriIdentityService: IUriIdentityService
|
||||
@IUriIdentityService private readonly uriIdentityService: IUriIdentityService,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@ITitleService titleService: ITitleService
|
||||
) {
|
||||
this.scmService.onDidAddRepository(this.onDidAddRepository, this, this.disposables);
|
||||
this.scmService.onDidRemoveRepository(this.onDidRemoveRepository, this, this.disposables);
|
||||
|
@ -55,6 +66,14 @@ export class SCMStatusController implements IWorkbenchContribution {
|
|||
this.onDidAddRepository(repository);
|
||||
}
|
||||
|
||||
this.activeRepositoryNameContextKey = ContextKeys.ActiveRepositoryName.bindTo(contextKeyService);
|
||||
this.activeRepositoryBranchNameContextKey = ContextKeys.ActiveRepositoryBranchName.bindTo(contextKeyService);
|
||||
|
||||
titleService.registerVariables([
|
||||
{ name: 'activeRepositoryName', contextKey: ContextKeys.ActiveRepositoryName.key },
|
||||
{ name: 'activeRepositoryBranchName', contextKey: ContextKeys.ActiveRepositoryBranchName.key, }
|
||||
]);
|
||||
|
||||
this.scmViewService.onDidFocusRepository(this.focusRepository, this, this.disposables);
|
||||
this.focusRepository(this.scmViewService.focusedRepository);
|
||||
|
||||
|
@ -125,17 +144,33 @@ export class SCMStatusController implements IWorkbenchContribution {
|
|||
return;
|
||||
}
|
||||
|
||||
this.focusDisposable.dispose();
|
||||
this.focusDisposables.clear();
|
||||
this.focusedRepository = repository;
|
||||
|
||||
if (repository && repository.provider.onDidChangeStatusBarCommands) {
|
||||
this.focusDisposable = repository.provider.onDidChangeStatusBarCommands(() => this.renderStatusBar(repository));
|
||||
if (repository) {
|
||||
if (repository.provider.onDidChangeStatusBarCommands) {
|
||||
this.focusDisposables.add(repository.provider.onDidChangeStatusBarCommands(() => this.renderStatusBar(repository)));
|
||||
}
|
||||
|
||||
this.focusDisposables.add(repository.provider.onDidChangeHistoryProvider(() => {
|
||||
if (repository.provider.historyProvider) {
|
||||
this.focusDisposables.add(repository.provider.historyProvider.onDidChangeCurrentHistoryItemGroup(() => this.updateContextKeys(repository)));
|
||||
}
|
||||
|
||||
this.updateContextKeys(repository);
|
||||
}));
|
||||
}
|
||||
|
||||
this.updateContextKeys(repository);
|
||||
this.renderStatusBar(repository);
|
||||
this.renderActivityCount();
|
||||
}
|
||||
|
||||
private updateContextKeys(repository: ISCMRepository | undefined): void {
|
||||
this.activeRepositoryNameContextKey.set(repository?.provider.name ?? '');
|
||||
this.activeRepositoryBranchNameContextKey.set(repository?.provider.historyProvider?.currentHistoryItemGroup?.label ?? '');
|
||||
}
|
||||
|
||||
private renderStatusBar(repository: ISCMRepository | undefined): void {
|
||||
this.statusBarDisposable.dispose();
|
||||
|
||||
|
@ -204,7 +239,7 @@ export class SCMStatusController implements IWorkbenchContribution {
|
|||
}
|
||||
|
||||
dispose(): void {
|
||||
this.focusDisposable.dispose();
|
||||
this.focusDisposables.dispose();
|
||||
this.statusBarDisposable.dispose();
|
||||
this.badgeDisposable.dispose();
|
||||
this.disposables.dispose();
|
||||
|
|
Loading…
Reference in a new issue