diff --git a/package.json b/package.json index df9c15452..6e1d736f1 100644 --- a/package.json +++ b/package.json @@ -1219,6 +1219,170 @@ ], "scope": "window" }, + "cmake.statusbar.visibility": { + "type": "string", + "default": "default", + "description": "%cmake-tools.configuration.cmake.statusbar.visibility.description%", + "enum": [ + "default", + "compact", + "icon", + "hidden" + ], + "scope": "window" + }, + "cmake.statusbar.advanced": { + "type": "object", + "default": {}, + "description": "%cmake-tools.configuration.cmake.statusbar.advanced.description%", + "properties": { + "kit": { + "type": "object", + "properties": { + "visibility": { + "type": "string", + "description": "%cmake-tools.configuration.cmake.statusbar.advanced.visibility.description%", + "enum": [ + "default", + "compact", + "icon", + "hidden" + ] + }, + "length": { + "description": "%cmake-tools.configuration.cmake.statusbar.advanced.length.description%", + "type": "integer", + "default": 20 + } + } + }, + "status": { + "type": "object", + "properties": { + "visibility": { + "type": "string", + "description": "%cmake-tools.configuration.cmake.statusbar.advanced.visibility.description%", + "enum": [ + "default", + "compact", + "icon", + "hidden" + ] + } + } + }, + "workspace": { + "type": "object", + "properties": { + "visibility": { + "type": "string", + "description": "%cmake-tools.configuration.cmake.statusbar.advanced.visibility.description%", + "enum": [ + "default", + "compact", + "icon", + "hidden" + ] + } + } + }, + "buildTarget": { + "type": "object", + "properties": { + "visibility": { + "type": "string", + "description": "%cmake-tools.configuration.cmake.statusbar.advanced.visibility.description%", + "enum": [ + "default", + "compact", + "icon", + "hidden" + ] + } + } + }, + "build": { + "type": "object", + "properties": { + "visibility": { + "type": "string", + "description": "%cmake-tools.configuration.cmake.statusbar.advanced.visibility.description%", + "enum": [ + "default", + "compact", + "icon", + "hidden" + ] + } + } + }, + "launchTarget": { + "type": "object", + "properties": { + "visibility": { + "type": "string", + "description": "%cmake-tools.configuration.cmake.statusbar.advanced.visibility.description%", + "enum": [ + "default", + "compact", + "icon", + "hidden" + ] + } + } + }, + "debug": { + "type": "object", + "properties": { + "visibility": { + "type": "string", + "description": "%cmake-tools.configuration.cmake.statusbar.advanced.visibility.description%", + "enum": [ + "default", + "compact", + "icon", + "hidden" + ] + } + } + }, + "launch": { + "type": "object", + "properties": { + "visibility": { + "type": "string", + "description": "%cmake-tools.configuration.cmake.statusbar.advanced.visibility.description%", + "enum": [ + "default", + "compact", + "icon", + "hidden" + ] + } + } + }, + "ctest": { + "type": "object", + "properties": { + "visibility": { + "type": "string", + "description": "%cmake-tools.configuration.cmake.statusbar.advanced.visibility.description%", + "enum": [ + "default", + "compact", + "icon", + "hidden" + ] + }, + "color": { + "type":"boolean", + "default": false, + "description": "%cmake-tools.configuration.cmake.statusbar.advanced.ctest.color.description%" + } + } + } + } + }, "cmake.revealLog": { "type": "string", "default": "always", diff --git a/package.nls.json b/package.nls.json index 25b78fc96..49c9aca33 100644 --- a/package.nls.json +++ b/package.nls.json @@ -106,6 +106,11 @@ "cmake-tools.configuration.cmake.outputLogEncoding.description": "Encoding of the output from external commands (eg.cmake -- build).", "cmake-tools.configuration.cmake.enableTraceLogging.description": "Enable trace logging to file and console (very noisy).", "cmake-tools.configuration.cmake.autoSelectActiveFolder.description": "Select active folder automatically", + "cmake-tools.configuration.cmake.statusbar.visibility.description": "Configures how the extension displays the buttons in the status bar", + "cmake-tools.configuration.cmake.statusbar.advanced.description": "Configures the settings for individual status bar buttons. These settings overwrite the more general 'cmake.statusbar.visibility' setting.", + "cmake-tools.configuration.cmake.statusbar.advanced.visibility.description": "Configures how the extension displays this button in the status bar", + "cmake-tools.configuration.cmake.statusbar.advanced.ctest.color.description": "Enables a change in color for this button depending on test results", + "cmake-tools.configuration.cmake.statusbar.advanced.length.description": "Configures the maximum length of visible text in 'compact' mode.", "cmake-tools.configuration.views.cmake.folders.description": "Folders", "cmake-tools.configuration.views.cmake.outline.description": "Project Outline" } diff --git a/src/config.ts b/src/config.ts index 7d42c7d15..5f5f47bc9 100644 --- a/src/config.ts +++ b/src/config.ts @@ -19,10 +19,48 @@ export type LogLevelKey = 'trace'|'debug'|'info'|'note'|'warning'|'error'|'fatal export type CMakeCommunicationMode = 'legacy'|'serverApi'|'fileApi'|'automatic'; +export type StatusBarButtonVisibility = "default" | "compact" | "icon" | "hidden"; + interface HardEnv { [key: string]: string; } +export interface AdvancedStatusBarConfig { + kit?: { + visibility?: StatusBarButtonVisibility; + length?: number; + }; + status?: { + visibility?: StatusBarButtonVisibility; + }; + workspace?: { + visibility?: StatusBarButtonVisibility; + }; + buildTarget?: { + visibility?: StatusBarButtonVisibility; + }; + build?: { + visibility?: StatusBarButtonVisibility; + }; + launchTarget?: { + visibility?: StatusBarButtonVisibility; + }; + debug?: { + visibility?: StatusBarButtonVisibility; + }; + launch?: { + visibility?: StatusBarButtonVisibility; + }; + ctest?: { + color?: boolean; + visibility?: StatusBarButtonVisibility; + }; +} +export interface StatusBarConfig { + advanced?: AdvancedStatusBarConfig; + visibility: StatusBarButtonVisibility; +} + export interface ExtensionConfigurationSettings { autoSelectActiveFolder: boolean; cmakePath: string; @@ -65,6 +103,7 @@ export interface ExtensionConfigurationSettings { outputLogEncoding: string; enableTraceLogging: boolean; loggingLevel: LogLevelKey; + statusbar: StatusBarConfig; } type EmittersOf = { @@ -85,6 +124,8 @@ export class ConfigurationReader implements vscode.Disposable { get configData() { return this._configData; } + get statusbar() { return this._configData.statusbar; } + dispose() { if (this._updateSubscription) { this._updateSubscription.dispose(); @@ -290,6 +331,8 @@ export class ConfigurationReader implements vscode.Disposable { outputLogEncoding: new vscode.EventEmitter(), enableTraceLogging: new vscode.EventEmitter(), loggingLevel: new vscode.EventEmitter(), + statusbar: new vscode.EventEmitter() + }; /** diff --git a/src/extension.ts b/src/extension.ts index 2080fd459..550fedfe6 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -58,7 +58,7 @@ type CMakeToolsQueryMapFn = (cmt: CMakeTools) => Thenable { console.assert(this._folders.size === vscode.workspace.workspaceFolders?.length); if (this._folders.size === 1) { @@ -183,7 +183,7 @@ class ExtensionManager implements vscode.Disposable { /** * The status bar controller */ - private readonly _statusBar = new StatusBar(); + private readonly _statusBar = new StatusBar(this._workspaceConfig); // Subscriptions for status bar items: private _statusMessageSub: vscode.Disposable = new DummyDisposable(); private _targetNameSub: vscode.Disposable = new DummyDisposable(); @@ -389,7 +389,7 @@ class ExtensionManager implements vscode.Disposable { } else if (!ws) { // When adding a folder but the focus is on somewhere else // Do nothing but make sure we are showing the active folder correctly - this._statusBar.reloadVisibility(); + this._statusBar.update(); } } } @@ -520,14 +520,14 @@ class ExtensionManager implements vscode.Disposable { this._statusBar.setVisible(true); this._statusMessageSub = cmt.onStatusMessageChanged(FireNow, s => this._statusBar.setStatusMessage(s)); this._targetNameSub = cmt.onTargetNameChanged(FireNow, t => { - this._statusBar.targetName = t; + this._statusBar.setBuildTargetName(t); }); this._buildTypeSub = cmt.onBuildTypeChanged(FireNow, bt => this._statusBar.setBuildTypeLabel(bt)); this._launchTargetSub = cmt.onLaunchTargetNameChanged(FireNow, t => { this._statusBar.setLaunchTargetName(t || ''); }); - this._ctestEnabledSub = cmt.onCTestEnabledChanged(FireNow, e => this._statusBar.ctestEnabled = e); - this._testResultsSub = cmt.onTestResultsChanged(FireNow, r => this._statusBar.testResults = r); + this._ctestEnabledSub = cmt.onCTestEnabledChanged(FireNow, e => this._statusBar.setCTestEnabled(e)); + this._testResultsSub = cmt.onTestResultsChanged(FireNow, r => this._statusBar.setTestResults(r)); this._isBusySub = cmt.onIsBusyChanged(FireNow, b => this._statusBar.setIsBusy(b)); this._statusBar.setActiveKitName(cmt.activeKit ? cmt.activeKit.name : ''); } @@ -877,6 +877,7 @@ class ExtensionManager implements vscode.Disposable { async hideLaunchCommand(shouldHide: boolean = true) { // Don't hide command selectLaunchTarget here since the target can still be useful, one example is ${command:cmake.launchTargetPath} in launch.json + this._statusBar.hideLaunchButton(shouldHide); await util.setContextValue(HIDE_LAUNCH_COMMAND_KEY, shouldHide); } diff --git a/src/status.ts b/src/status.ts index ad454842d..855cdc754 100644 --- a/src/status.ts +++ b/src/status.ts @@ -1,259 +1,483 @@ -import * as vscode from 'vscode'; +import {ConfigurationReader, StatusBarButtonVisibility as ButtonVisibility} from '@cmt/config'; import {BasicTestResults} from '@cmt/ctest'; -import * as nls from 'vscode-nls'; import {SpecialKits} from '@cmt/kit'; +import * as vscode from 'vscode'; +import * as nls from 'vscode-nls'; -nls.config({ messageFormat: nls.MessageFormat.bundle, bundleFormat: nls.BundleFormat.standalone })(); +nls.config({messageFormat: nls.MessageFormat.bundle, bundleFormat: nls.BundleFormat.standalone})(); const localize: nls.LocalizeFunc = nls.loadMessageBundle(); -interface Hideable { - show(): void; - hide(): void; -} +//--------------------------------------------- +//-------------- Helper Functions ------------- +//--------------------------------------------- -function setVisible(i: Hideable, v: boolean) { - if (v) { - i.show(); - } else { - i.hide(); +function hasCPPTools(): boolean { return vscode.extensions.getExtension('ms-vscode.cpptools') !== undefined; } +//--------------------------------------------- +//---------------- Button Class --------------- +//--------------------------------------------- + +abstract class Button { + readonly settingsName: string|null = null; + protected readonly button: vscode.StatusBarItem; + private _forceHidden: boolean = false; + private _text: string = ''; + private _tooltip: string|null = null; + private _icon: string|null = null; + + constructor(protected readonly config: ConfigurationReader, private readonly _priority: number) { + this.button = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, this._priority); } -} -export class StatusBar implements vscode.Disposable { - private readonly _activeFolderButton = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 3.6); - private readonly _cmakeToolsStatusItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 3.5); - private readonly _kitSelectionButton = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 3.45); - private readonly _buildButton = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 3.4); - private readonly _targetButton = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 3.3); - private readonly _debugButton = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 3.25); - private readonly _launchTargetNameButton = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 3.2); - private readonly _testButton = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 3.1); - private readonly _warningMessage = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 3); - - private readonly _activeFolderButtonAutoSelectTooltip = localize('active.folder.auto.select.tooltip', 'Active folder'); - private readonly _activeFolderButtonTooltip = localize('active.folder.tooltip', 'Select Active folder'); - - dispose() { - const items = [ - this._activeFolderButton, - this._cmakeToolsStatusItem, - this._kitSelectionButton, - this._buildButton, - this._targetButton, - this._launchTargetNameButton, - this._testButton, - this._warningMessage, - ]; - for (const item of items) { - item.dispose(); + set forceHidden(v: boolean) { + this._forceHidden = v; + this.update(); + } + + get text(): string { return this._text; } + set text(v: string) { + this._text = v; + this.update(); + } + + get bracketText(): string { return `[${this._text}]`; } + + get tooltip(): string|null { return this._tooltip; } + set tooltip(v: string|null) { + this._tooltip = v; + this.update(); + } + + protected set icon(v: string|null) { this._icon = v ? `$(${v})` : null; } + + protected set command(v: string|null) { this.button.command = v || undefined; } + + dispose(): void { this.button.dispose(); } + update(): void { + const visible = this._isVisible(); + if (!visible || this._forceHidden) { + this.button.hide(); + return; } + const text = this._getText(true); + if (text === '') { + this.button.hide(); + return; + } + this.button.text = text; + this.button.tooltip = this._getTooltip() || undefined; + this.button.show(); } - constructor() { - this._activeFolderButton.command = 'cmake.selectActiveFolder'; - this._activeFolderButton.tooltip = this._activeFolderButtonTooltip; - this._activeFolderButton.text = this._activeFolder; - this._cmakeToolsStatusItem.command = 'cmake.setVariant'; - this._cmakeToolsStatusItem.tooltip = localize('click.to.select.variant.tooltip', 'Click to select the current build variant'); - this._buildButton.command = 'cmake.build'; - this._kitSelectionButton.command = 'cmake.selectKit'; - this._kitSelectionButton.tooltip = localize('click.to.change.kit.tooltip', 'Click to change the active kit'); - this._targetButton.command = 'cmake.setDefaultTarget'; - this._targetButton.tooltip = localize('set.active.target.tooltip', 'Set the active target to build'); - this._testButton.command = 'cmake.ctest'; - this._testButton.tooltip = localize('run.ctest.tests.tooltip', 'Run CTest tests'); - this._debugButton.tooltip = localize('launch.debugger.tooltip', 'Launch the debugger for the selected target'); - this._debugButton.command = 'cmake.debugTarget'; - this._launchTargetNameButton.command = 'cmake.selectLaunchTarget'; - this._launchTargetNameButton.tooltip = localize('select.target.tooltip', 'Select the target to launch'); - this._reloadBuildButton(); - this.reloadVisibility(); - } - - reloadVisibility() { - setVisible(this._activeFolderButton, Boolean(this._visible && vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders.length > 1 && !!this._activeFolderButton.text)); - const autovis_items = [ - this._cmakeToolsStatusItem, - this._buildButton, - this._kitSelectionButton, - this._targetButton, - this._debugButton, - this._launchTargetNameButton, - ]; - for (const item of autovis_items) { - setVisible(item, this._visible && !!item.text); + protected getTextNormal(): string { + if (this._text.length > 0) { + return this.bracketText; } - setVisible(this._debugButton, - this._visible && !this._hideDebugButton && vscode.extensions.getExtension('ms-vscode.cpptools') !== undefined - && !!this._debugButton.text); + return ''; } + protected getTextShort(): string { return this.getTextNormal(); } + protected getTextIcon(): string { return ''; } - /** - * Whether the status bar items are visible - */ - setVisible(v: boolean) { - this._visible = v; - this.reloadVisibility(); + protected getTooltipNormal(): string|null { return this._tooltip; } + protected getTooltipShort(): string|null { + const tooltip = this.getTooltipNormal(); + const text = this.getTextNormal(); + if (!tooltip && !text) { + return null; + } + if (!tooltip || !text) { + return this.prependCMake(`${tooltip || text}`); + } + return this.prependCMake(`${text}\n${tooltip}`); } - private _visible: boolean = true; + protected getTooltipIcon(): string|null { return this.getTooltipShort(); } - private _reloadStatusButton() { - this._cmakeToolsStatusItem.text = `CMake: ${this._buildTypeLabel}: ${this._statusMessage}`; - this.reloadVisibility(); + protected isVisible(): boolean { return true; } + protected prependCMake(text: string|null): any { + if (!!text) { + return `CMake: ${text}`; + } + return text; } - private _reloadDebugButton() { - if (!this._launchTargetNameButton.text) { - this._debugButton.text = '$(bug)'; - this._launchTargetNameButton.hide(); - } else { - this._debugButton.text = `$(bug) ${localize('debug', 'Debug')}`; - if (this._visible) { - this._launchTargetNameButton.show(); - } + private _isVisible(): boolean { return this.isVisible() && this._getVisibility() !== 'hidden'; } + private _getVisibility(): ButtonVisibility|null { + if (this.settingsName) { + const setting = Object(this.config.statusbar.advanced)[this.settingsName]?.visibility; + return setting || this.config.statusbar.visibility || null; } - this.reloadVisibility(); + return this.config.statusbar.visibility || null; } - private _reloadActiveFolderButton() { - this._activeFolderButton.text = this._activeFolder; - this._activeFolderButton.tooltip = this._autoSelectActiveFolder ? this._activeFolderButtonAutoSelectTooltip : this._activeFolderButtonTooltip; - this.reloadVisibility(); + private _getTooltip(): string|null { + const visibility = this._getVisibility(); + switch (visibility) { + case 'hidden': + return null; + case 'icon': + return this.getTooltipIcon(); + case 'compact': + return this.getTooltipShort(); + default: + return this.getTooltipNormal(); + } } + private _getText(icon: boolean = false): string { + const type = this._getVisibility(); + let text: string; + switch (type) { + case 'icon': + text = this.getTextIcon(); + break; + case 'compact': + text = this.getTextShort(); + break; + default: + text = this.getTextNormal(); + break; + } + if (!icon) { + return text; + } + if (!this._icon) { + return text; + } + if (text == '') { + return this._icon || ''; + } + return `${this._icon} ${text}`; + } +} + +class WorkspaceButton extends Button { + // private static readonly _autoSelectToolTip = localize('active.folder.auto.select.tooltip', 'Active folder'); + // private static readonly _toolTip = localize('active.folder.tooltip', 'Select Active folder'); + // private static readonly _autoSelectToolTip = localize('active.folder.auto.tooltip', 'auto'); + + settingsName = 'workspace'; + command = 'cmake.selectActiveFolder'; + icon = 'folder-active'; + tooltip = localize('click.to.select.workspace.tooltip', 'Click to select the active folder'); - private _activeFolder: string = ''; - setActiveFolderName(v: string) { - this._activeFolder = v; - this._reloadActiveFolderButton(); + // private _autoSelect: boolean = false; + set autoSelect(v: boolean) { + if (v) {} + // this._autoSelect = v; + // this.update(); } - private _autoSelectActiveFolder: boolean = false; - setAutoSelectActiveFolder(autoSelectActiveFolder: boolean) { - this._autoSelectActiveFolder = autoSelectActiveFolder; - this._reloadActiveFolderButton(); + protected getTooltipNormal(): string|null { + // if (this._autoSelect) { + // return `${this.tooltip} (${WorkspaceButton._autoSelectToolTip})`; + //} + return this.tooltip; } + protected getTooltipShort(): string|null { return this.prependCMake(this.getTooltipNormal()); } + protected getTooltipIcon(): string|null { return super.getTooltipShort(); } - /** - * The build type label. Determined by the active build variant - */ - private _buildTypeLabel: string = localize('unconfigured', 'Unconfigured'); - setBuildTypeLabel(v: string) { - this._buildTypeLabel = v; - this._reloadStatusButton(); + protected isVisible(): boolean { + return Boolean(vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders.length > 1); } +} + +class CMakeStatus extends Button { + settingsName = 'status'; + command = 'cmake.setVariant'; + icon = 'info'; + text: string = localize('unconfigured', 'Unconfigured'); + tooltip = localize('click.to.select.variant.tooltip', 'Click to select the current build variant'); - /** - * The message shown in the primary status button. Tells the user what the - * extension is currently up to. - */ private _statusMessage: string = localize('loading.status', 'Loading...'); - setStatusMessage(v: string) { + + set statusMessage(v: string) { this._statusMessage = v; - this._reloadStatusButton(); + this.update(); } - /** - * The name of the currently active target to build - */ - private _targetName: string = ''; - public get targetName(): string { return this._targetName; } - public set targetName(v: string) { - this._targetName = v; - this._targetButton.text = `[${v}]`; - this.reloadVisibility(); + protected getTextNormal(): string { return this.prependCMake(`${this.bracketText}: ${this._statusMessage}`); } + protected getTextShort(): string { return this.bracketText; } + + protected getTooltipShort(): string|null { + return this.prependCMake(`${this.bracketText} - ${this._statusMessage}\n${this.tooltip}`); } +} - setLaunchTargetName(v: string) { - this._launchTargetNameButton.text = v; - this._reloadDebugButton(); +class KitSelection extends Button { + private static readonly _noActiveKit = localize('no.active.kit', 'No active kit'); + private static readonly _noKitSelected = localize('no.kit.selected', 'No Kit Selected'); + + settingsName = 'kit'; + command = 'cmake.selectKit'; + icon = 'tools'; + tooltip = localize('click.to.change.kit.tooltip', 'Click to change the active kit'); + + protected getTextNormal(): string { + const text = this.text; + if (text === SpecialKits.Unspecified) { + return KitSelection._noActiveKit; + } + if (text.length === 0) { + return KitSelection._noKitSelected; + } + return this.bracketText; + } + protected getTextShort(): string { + let len = this.config.statusbar.advanced?.kit?.length || 0; + if (!Number.isInteger(len) || len <= 0) { + len = 20; + } + let text = this.getTextNormal(); + if (len + 3 < text.length) { + text = `${text.substr(0, len)}...`; + if (text.startsWith('[')) { + text = `${text}]`; + } + } + return text; } - private _ctestEnabled: boolean = false; - public get ctestEnabled(): boolean { return this._ctestEnabled; } - public set ctestEnabled(v: boolean) { - this._ctestEnabled = v; - setVisible(this._testButton, v); + protected getTooltipShort(): string|null { + if (this.getTextNormal() == this.getTextShort()) { + return this.prependCMake(this.getTooltipNormal()); + } + return super.getTooltipShort(); } +} + +class BuildTargetSelectionButton extends Button { + settingsName = 'buildTarget'; + command = 'cmake.setDefaultTarget'; + tooltip = localize('set.active.target.tooltip', 'Set the active target to build'); + + protected getTooltipShort(): string|null { return this.prependCMake(this.tooltip); } +} +class LaunchTargetSelectionButton extends Button { + settingsName = 'launchTarget'; + command = 'cmake.selectLaunchTarget'; + tooltip = localize('select.target.tooltip', 'Select the target to launch'); + + protected getTooltipShort(): string|null { return this.prependCMake(this.tooltip); } +} +class DebugButton extends Button { + settingsName = 'debug'; + command = 'cmake.debugTarget'; + icon = 'bug'; + tooltip = localize('launch.debugger.tooltip', 'Launch the debugger for the selected target'); - private _testResults: BasicTestResults|null = null; - public get testResults(): BasicTestResults|null { return this._testResults; } - public set testResults(v: BasicTestResults|null) { - this._testResults = v; + private _hidden: boolean = false; + private _target: string|null = null; + set hidden(v: boolean) { + this._hidden = v; + this.update(); + } + set target(v: string|null) { + this._target = v; + this.update(); + } + + protected getTooltipNormal(): string|null { + if (!!this._target) { + return `${this.tooltip}: [${this._target}]`; + } + return this.tooltip; + } + + protected isVisible(): boolean { return !this._hidden && hasCPPTools(); } +} +class LaunchButton extends Button { + settingsName = 'launch'; + command = 'cmake.launchTarget'; + icon = 'play'; + tooltip = localize('launch.tooltip', 'Launch the selected target in the terminal window'); + + private _hidden: boolean = false; + private _target: string|null = null; + + set hidden(v: boolean) { + this._hidden = v; + this.update(); + } + set target(v: string|null) { + this._target = v; + this.update(); + } + + protected getTooltipNormal(): string|null { + if (!!this._target) { + return `${this.tooltip}: [${this._target}]`; + } + return this.tooltip; + } + + protected isVisible(): boolean { return super.isVisible() && !this._hidden; } +} + +class CTestButton extends Button { + settingsName = 'ctest'; + command = 'cmake.ctest'; + tooltip = localize('run.ctest.tests.tooltip', 'Run CTest tests'); + + private _enabled: boolean = false; + private _results: BasicTestResults|null = null; + private _color: string = ''; + + set enabled(v: boolean) { + this._enabled = v; + this.update(); + } + set results(v: BasicTestResults|null) { + this._results = v; if (!v) { - this._testButton.text = localize('run.ctest', 'Run CTest'); - this._testButton.color = ''; - return; + this._color = ''; + } else { + this._color = v.passing === v.total ? 'lightgreen' : 'yellow'; } + this.update(); + } - const passing = v.passing; - const total = v.total; - const good = passing == total; - const icon = good ? 'check' : 'x'; - let testPassingText: string; - if (total == 1) { - testPassingText = localize('test.passing', '{0}/{1} test passing', passing, total); + update(): void { + if (this._results) { + const {passing, total} = this._results; + this.icon = passing == total ? 'check' : 'x'; + } else { + this.icon = 'beaker'; + } + if (this.config.statusbar.advanced?.ctest?.color === true) { + this.button.color = this._color; } else { - testPassingText = localize('tests.passing', '{0}/{1} tests passing', passing, total); + this.button.color = ''; } - this._testButton.text = `$(${icon}) ${testPassingText}`; - this._testButton.color = good ? 'lightgreen' : 'yellow'; + super.update(); } - /** Reloads the content of the build button */ - private _reloadBuildButton() { - this._buildButton.text = ``; - this._buildButton.text = this._isBusy ? `$(x) ${localize('stop', 'Stop')}` : `$(gear) ${localize('build', 'Build')}:`; - this._buildButton.command = this._isBusy ? 'cmake.stop' : 'cmake.build'; - if (this._isBusy) { - this._buildButton.show(); + protected isVisible(): boolean { return this._enabled; } + + protected getTextNormal(): string { + if (!this._results) { + this.button.color = ''; + return localize('run.ctest', 'Run CTest'); + } + const {passing, total} = this._results; + if (total == 1) { + return localize('test.passing', '{0}/{1} test passing', passing, total); } + return localize('tests.passing', '{0}/{1} tests passing', passing, total); } - /** - * Whether or not to show a 'Build' or 'Stop' button. Changes the content - * of the button and the command that is executed when the button is pressed - */ - private _isBusy: boolean = false; - setIsBusy(v: boolean) { - this._isBusy = v; - this._reloadBuildButton(); + protected getTextShort(): string { + if (!this._results) { + return ''; + } + const {passing, total} = this._results; + return `${passing}/${total}`; } - private _reloadKitsButton() { - if (this._visible) { - if (this._activeKitName.length) { - this._kitSelectionButton.text = this._activeKitName; - } else { - this._kitSelectionButton.text = localize('no.kit.selected', 'No Kit Selected'); - } - this.reloadVisibility(); - } else { - this._kitSelectionButton.hide(); + protected getTooltipShort(): string|null { return this.prependCMake(this.getTooltipNormal()); } + protected getTooltipIcon() { + if (!!this._results) { + return this.prependCMake(`${this.getTextNormal()}\n${this.getTooltipNormal()}`); } + return this.getTooltipShort(); } +} - setActiveKitName(v: string) { - if (v === SpecialKits.Unspecified) { - this._activeKitName = `[${localize('no.active.kit', 'No active kit')}]`; - } else { - this._activeKitName = v; +class BuildButton extends Button { + private static readonly _build = localize('build', 'Build'); + private static readonly _stop = localize('stop', 'Stop'); + + settingsName = 'build'; + command = 'cmake.build'; + tooltip = localize('build.tooltip', 'Build the selected target'); + + private _isBusy: boolean = false; + private _target: string|null = null; + + set isBusy(v: boolean) { + this._isBusy = v; + this.button.command = v ? 'cmake.stop' : 'cmake.build'; + this.icon = this._isBusy ? 'x' : 'gear'; + this.text = this._isBusy ? BuildButton._stop : BuildButton._build; + // update implicitly called in set text. + // this.update(); + } + set target(v: string|null) { + this._target = v; + this.update(); + } + + protected getTextNormal(): string { return this.text; } + protected getTextShort(): string { return ''; } + + protected getTooltipNormal(): string|null { + if (!!this._target) { + return `${this.tooltip}: [${this._target}]`; } - this._reloadKitsButton(); + return this.tooltip; } - private _activeKitName: string = ''; + protected getTooltipShort(): string|null { return this.prependCMake(this.getTooltipNormal()); } + + protected isVisible(): boolean { return this._isBusy || true; } +} + +export class StatusBar implements vscode.Disposable { + private readonly _workspaceButton = new WorkspaceButton(this._config, 3.6); + + private readonly _cmakeToolsStatusItem = new CMakeStatus(this._config, 3.5); + private readonly _kitSelectionButton = new KitSelection(this._config, 3.4); + + private readonly _buildButton: BuildButton = new BuildButton(this._config, 3.35); + private readonly _buildTargetNameButton = new BuildTargetSelectionButton(this._config, 3.3); - showWarningMessage(msg: string) { - this._warningMessage.color = 'yellow'; - this._warningMessage.text = `$(alert) ${msg}`; - this._warningMessage.show(); - setTimeout(() => this._warningMessage.hide(), 5000); + private readonly _debugButton: DebugButton = new DebugButton(this._config, 3.22); + private readonly _launchButton = new LaunchButton(this._config, 3.21); + private readonly _launchTargetNameButton = new LaunchTargetSelectionButton(this._config, 3.2); + + private readonly _testButton = new CTestButton(this._config, 3.1); + + private readonly _buttons: Button[]; + + constructor(private readonly _config: ConfigurationReader) { + this._buttons = [ + this._workspaceButton, + this._cmakeToolsStatusItem, + this._kitSelectionButton, + this._buildTargetNameButton, + this._launchTargetNameButton, + this._debugButton, + this._buildButton, + this._testButton, + this._launchButton + ]; + this._config.onChange('statusbar', () => this.update()); + this.update(); } - private _hideDebugButton = false; - hideDebugButton(shouldHide: boolean = true) { - this._hideDebugButton = shouldHide; - this.reloadVisibility(); + dispose(): void { this._buttons.forEach(btn => btn.dispose()); } + update(): void { this._buttons.forEach(btn => btn.update()); } + + setVisible(v: boolean): void { this._buttons.forEach(btn => btn.forceHidden = !v); } + + setActiveFolderName(v: string): void { this._workspaceButton.text = v; } + setAutoSelectActiveFolder(autoSelectActiveFolder: boolean): void { + this._workspaceButton.autoSelect = autoSelectActiveFolder; } + setBuildTypeLabel(v: string): void { this._cmakeToolsStatusItem.text = v; } + setStatusMessage(v: string): void { this._cmakeToolsStatusItem.statusMessage = v; } + setBuildTargetName(v: string): void { + this._buildTargetNameButton.text = v; + this._buildButton.target = v; + } + setLaunchTargetName(v: string): void { + this._launchTargetNameButton.text = v; + this._launchButton.target = v; + this._debugButton.target = v; + } + setCTestEnabled(v: boolean): void { this._testButton.enabled = v; } + setTestResults(v: BasicTestResults|null): void { this._testButton.results = v; } + setIsBusy(v: boolean): void { this._buildButton.isBusy = v; } + setActiveKitName(v: string): void { this._kitSelectionButton.text = v; } + + hideLaunchButton(shouldHide: boolean = true): void { this._launchButton.hidden = shouldHide; } + hideDebugButton(shouldHide: boolean = true): void { this._debugButton.hidden = shouldHide; } } \ No newline at end of file diff --git a/test/unit-tests/config.test.ts b/test/unit-tests/config.test.ts index d6ae571db..403fa5774 100644 --- a/test/unit-tests/config.test.ts +++ b/test/unit-tests/config.test.ts @@ -47,6 +47,10 @@ function createConfig(conf: Partial): Configurat outputLogEncoding: 'auto', enableTraceLogging: false, loggingLevel: 'info', + statusbar: { + advanced: {}, + visibility: "default" + } }); ret.updatePartial(conf); return ret;