Merge pull request #131264 from microsoft/joh/langStatus

Language status items
This commit is contained in:
Johannes Rieken
2021-08-30 13:26:10 +02:00
committed by GitHub
14 changed files with 431 additions and 95 deletions
+4
View File
@@ -102,6 +102,10 @@
"name": "vs/workbench/contrib/interactive",
"project": "vscode-workbench"
},
{
"name": "vs/workbench/contrib/languageStatus",
"project": "vscode-workbench"
},
{
"name": "vs/workbench/contrib/keybindings",
"project": "vscode-workbench"
+2 -2
View File
@@ -20,7 +20,7 @@
display: none;
}
.monaco-hover .hover-contents {
.monaco-hover .hover-contents:not(.html-hover-contents) {
padding: 4px 8px;
}
@@ -137,7 +137,7 @@
}
/** Spans in markdown hovers need a margin-bottom to avoid looking cramped: https://github.com/microsoft/vscode/issues/101496 **/
.monaco-hover .markdown-hover .hover-contents:not(.code-hover-contents) span {
.monaco-hover .markdown-hover .hover-contents:not(.code-hover-contents):not(.html-hover-contents) span {
margin-bottom: 4px;
display: inline-block;
}
@@ -23,7 +23,7 @@ export interface IIconLabelCreationOptions {
}
export interface IIconLabelMarkdownString {
markdown: IMarkdownString | string | undefined | ((token: CancellationToken) => Promise<IMarkdownString | string | undefined>);
markdown: IMarkdownString | string | HTMLElement | undefined | ((token: CancellationToken) => Promise<IMarkdownString | string | undefined>);
markdownNotSupportedFallback: string | undefined;
}
@@ -25,7 +25,7 @@ export function setupNativeHover(htmlElement: HTMLElement, tooltip: string | IIc
}
}
export function setupCustomHover(hoverDelegate: IHoverDelegate, htmlElement: HTMLElement, markdownTooltip: string | IIconLabelMarkdownString | undefined): IDisposable | undefined {
export function setupCustomHover(hoverDelegate: IHoverDelegate, htmlElement: HTMLElement, markdownTooltip: string | IIconLabelMarkdownString | HTMLElement | undefined): IDisposable | undefined {
if (!markdownTooltip) {
return undefined;
}
@@ -79,7 +79,7 @@ export function setupCustomHover(hoverDelegate: IHoverDelegate, htmlElement: HTM
hoverWidget?.dispose();
hoverWidget = hoverDelegate.showHover(hoverOptions);
const resolvedTooltip = (await tooltip(tokenSource.token)) ?? (!isString(markdownTooltip) ? markdownTooltip.markdownNotSupportedFallback : undefined);
const resolvedTooltip = (await tooltip(tokenSource.token)) ?? (!isString(markdownTooltip) && !(markdownTooltip instanceof HTMLElement) ? markdownTooltip.markdownNotSupportedFallback : undefined);
hoverWidget?.dispose();
hoverWidget = undefined;
@@ -119,8 +119,8 @@ export function setupCustomHover(hoverDelegate: IHoverDelegate, htmlElement: HTM
}
function getTooltipForCustom(markdownTooltip: string | IIconLabelMarkdownString): (token: CancellationToken) => Promise<string | IMarkdownString | undefined> {
if (isString(markdownTooltip)) {
function getTooltipForCustom(markdownTooltip: string | IIconLabelMarkdownString | HTMLElement): (token: CancellationToken) => Promise<string | IMarkdownString | HTMLElement | undefined> {
if (isString(markdownTooltip) || markdownTooltip instanceof HTMLElement) {
return async () => markdownTooltip;
} else if (isFunction(markdownTooltip.markdown)) {
return markdownTooltip.markdown;
+6 -3
View File
@@ -2873,15 +2873,18 @@ declare module 'vscode' {
}
interface LanguageStatusItem {
readonly id: string;
selector: DocumentSelector;
text: string;
detail: string | MarkdownString
severity: LanguageStatusSeverity;
name: string | undefined;
text: string;
detail: string;
command: Command | undefined;
dispose(): void;
}
namespace languages {
export function createLanguageStatusItem(selector: DocumentSelector): LanguageStatusItem;
export function createLanguageStatusItem(id: string, selector: DocumentSelector): LanguageStatusItem;
}
//#endregion
@@ -186,7 +186,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
const extHostMessageService = new ExtHostMessageService(rpcProtocol, extHostLogService);
const extHostDialogs = new ExtHostDialogs(rpcProtocol);
const extHostStatusBar = new ExtHostStatusBar(rpcProtocol, extHostCommands.converter);
const extHostLanguages = new ExtHostLanguages(rpcProtocol, extHostDocuments);
const extHostLanguages = new ExtHostLanguages(rpcProtocol, extHostDocuments, extHostCommands.converter);
// Register API-ish commands
ExtHostApiCommands.register(extHostCommands);
@@ -507,9 +507,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
checkProposedApiEnabled(extension);
return extHostLanguageFeatures.registerTypeHierarchyProvider(extension, selector, provider);
},
createLanguageStatusItem(selector: vscode.DocumentSelector): vscode.LanguageStatusItem {
createLanguageStatusItem(id: string, selector: vscode.DocumentSelector): vscode.LanguageStatusItem {
checkProposedApiEnabled(extension);
return extHostLanguages.createLanguageStatusItem(selector);
return extHostLanguages.createLanguageStatusItem(extension, id, selector);
}
};
+43 -13
View File
@@ -10,19 +10,20 @@ import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters';
import { StandardTokenType, Range, Position, LanguageStatusSeverity } from 'vs/workbench/api/common/extHostTypes';
import Severity from 'vs/base/common/severity';
import { disposableTimeout } from 'vs/base/common/async';
import { IDisposable } from 'vs/base/common/lifecycle';
import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle';
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { CommandsConverter } from 'vs/workbench/api/common/extHostCommands';
export class ExtHostLanguages {
private readonly _proxy: MainThreadLanguagesShape;
private readonly _documents: ExtHostDocuments;
constructor(
mainContext: IMainContext,
documents: ExtHostDocuments
private readonly _documents: ExtHostDocuments,
private readonly _commands: CommandsConverter
) {
this._proxy = mainContext.getProxy(MainContext.MainThreadLanguages);
this._documents = documents;
}
getLanguages(): Promise<string[]> {
@@ -67,32 +68,58 @@ export class ExtHostLanguages {
private _handlePool: number = 0;
createLanguageStatusItem(selector: vscode.DocumentSelector): vscode.LanguageStatusItem {
createLanguageStatusItem(extension: IExtensionDescription, id: string, selector: vscode.DocumentSelector): vscode.LanguageStatusItem {
const handle = this._handlePool++;
const proxy = this._proxy;
const data: { selector: any, text: string, detail: string | vscode.MarkdownString, severity: vscode.LanguageStatusSeverity } = {
const data: Omit<vscode.LanguageStatusItem, 'dispose'> = {
selector,
id,
name: extension.displayName ?? extension.name,
severity: LanguageStatusSeverity.Information,
command: undefined,
text: '',
detail: '',
severity: LanguageStatusSeverity.Information,
};
let soonHandle: IDisposable | undefined;
let commandDisposables = new DisposableStore();
const updateAsync = () => {
soonHandle?.dispose();
soonHandle = disposableTimeout(() => {
commandDisposables.clear();
this._proxy.$setLanguageStatus(handle, {
id: `${extension.identifier.value}/${id}`,
name: data.name ?? extension.displayName ?? extension.name,
source: extension.displayName ?? extension.name,
selector: data.selector,
text: data.text,
message: typeof data.detail === 'string' ? data.detail : typeConvert.MarkdownString.from(data.detail),
severity: data.severity === LanguageStatusSeverity.Error ? Severity.Error : data.severity === LanguageStatusSeverity.Warning ? Severity.Warning : Severity.Info
label: data.text,
detail: data.detail,
severity: data.severity === LanguageStatusSeverity.Error ? Severity.Error : data.severity === LanguageStatusSeverity.Warning ? Severity.Warning : Severity.Info,
command: data.command && this._commands.toInternal(data.command, commandDisposables)
});
}, 0);
};
const result: vscode.LanguageStatusItem = {
dispose() {
commandDisposables.dispose();
soonHandle?.dispose();
proxy.$removeLanguageStatus(handle);
},
get id() {
return data.id;
},
get name() {
return data.name;
},
set name(value) {
data.name = value;
updateAsync();
},
get selector() {
return data.selector;
},
@@ -121,9 +148,12 @@ export class ExtHostLanguages {
data.severity = value;
updateAsync();
},
dispose() {
soonHandle?.dispose();
proxy.$removeLanguageStatus(handle);
get command() {
return data.command;
},
set command(value) {
data.command = value;
updateAsync();
}
};
updateAsync();
@@ -37,7 +37,7 @@ import { ICursorPositionChangedEvent } from 'vs/editor/common/controller/cursorE
import { ConfigurationChangedEvent, IEditorOptions, EditorOption } from 'vs/editor/common/config/editorOptions';
import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService';
import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { deepClone, equals } from 'vs/base/common/objects';
import { deepClone } from 'vs/base/common/objects';
import { ICodeEditor, getCodeEditor } from 'vs/editor/browser/editorBrowser';
import { Schemas } from 'vs/base/common/network';
import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences';
@@ -50,11 +50,10 @@ import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessi
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { IStatusbarEntryAccessor, IStatusbarService, StatusbarAlignment, IStatusbarEntry } from 'vs/workbench/services/statusbar/browser/statusbar';
import { IMarker, IMarkerService, MarkerSeverity, IMarkerData } from 'vs/platform/markers/common/markers';
import { STATUS_BAR_ERROR_ITEM_BACKGROUND, STATUS_BAR_ERROR_ITEM_FOREGROUND, STATUS_BAR_PROMINENT_ITEM_BACKGROUND, STATUS_BAR_PROMINENT_ITEM_FOREGROUND, STATUS_BAR_WARNING_ITEM_BACKGROUND, STATUS_BAR_WARNING_ITEM_FOREGROUND } from 'vs/workbench/common/theme';
import { ThemeColor, themeColorFromId } from 'vs/platform/theme/common/themeService';
import { STATUS_BAR_PROMINENT_ITEM_BACKGROUND, STATUS_BAR_PROMINENT_ITEM_FOREGROUND } from 'vs/workbench/common/theme';
import { themeColorFromId } from 'vs/platform/theme/common/themeService';
import { ITelemetryData, ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { SideBySideEditorInput } from 'vs/workbench/common/editor/sideBySideEditorInput';
import { ILanguageStatus, ILanguageStatusService } from 'vs/workbench/services/languageStatus/common/languageStatusService';
import { AutomaticLanguageDetectionLikelyWrongClassification, AutomaticLanguageDetectionLikelyWrongId, IAutomaticLanguageDetectionLikelyWrongData, ILanguageDetectionService } from 'vs/workbench/services/languageDetection/common/languageDetectionWorkerService';
class SideBySideEditorEncodingSupport implements IEncodingSupport {
@@ -182,7 +181,6 @@ class StateChange {
type StateDelta = (
{ type: 'selectionStatus'; selectionStatus: string | undefined; }
| { type: 'mode'; mode: string | undefined; }
| { type: 'languageStatus'; status: ILanguageStatus[] | undefined; }
| { type: 'encoding'; encoding: string | undefined; }
| { type: 'EOL'; EOL: string | undefined; }
| { type: 'indentation'; indentation: string | undefined; }
@@ -200,9 +198,6 @@ class State {
private _mode: string | undefined;
get mode(): string | undefined { return this._mode; }
private _status: ILanguageStatus[] | undefined;
get status(): ILanguageStatus[] | undefined { return this._status; }
private _encoding: string | undefined;
get encoding(): string | undefined { return this._encoding; }
@@ -248,13 +243,6 @@ class State {
}
}
if (update.type === 'languageStatus') {
if (!equals(this._status, update.status)) {
this._status = update.status;
change.languageStatus = true;
}
}
if (update.type === 'encoding') {
if (this._encoding !== update.encoding) {
this._encoding = update.encoding;
@@ -318,7 +306,6 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution {
private readonly encodingElement = this._register(new MutableDisposable<IStatusbarEntryAccessor>());
private readonly eolElement = this._register(new MutableDisposable<IStatusbarEntryAccessor>());
private readonly modeElement = this._register(new MutableDisposable<IStatusbarEntryAccessor>());
private readonly statusElement = this._register(new MutableDisposable<IStatusbarEntryAccessor>());
private readonly metadataElement = this._register(new MutableDisposable<IStatusbarEntryAccessor>());
private readonly currentProblemStatus: ShowCurrentMarkerInStatusbarContribution = this._register(this.instantiationService.createInstance(ShowCurrentMarkerInStatusbarContribution));
@@ -330,7 +317,6 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution {
private promptedScreenReader: boolean = false;
constructor(
@ILanguageStatusService private readonly languageStatusService: ILanguageStatusService,
@IEditorService private readonly editorService: IEditorService,
@IQuickInputService private readonly quickInputService: IQuickInputService,
@IModeService private readonly modeService: IModeService,
@@ -559,36 +545,6 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution {
this.updateElement(this.modeElement, props, 'status.editor.mode', StatusbarAlignment.RIGHT, 100.1);
}
private updateStatusElement(status: ILanguageStatus[] | undefined): void {
if (!status || status.length === 0) {
this.statusElement.clear();
return;
}
const [first] = status;
let backgroundColor: ThemeColor | undefined;
let color: ThemeColor | undefined;
if (first.severity === Severity.Error) {
backgroundColor = themeColorFromId(STATUS_BAR_ERROR_ITEM_BACKGROUND);
color = themeColorFromId(STATUS_BAR_ERROR_ITEM_FOREGROUND);
} else if (first.severity === Severity.Warning) {
backgroundColor = themeColorFromId(STATUS_BAR_WARNING_ITEM_BACKGROUND);
color = themeColorFromId(STATUS_BAR_WARNING_ITEM_FOREGROUND);
}
const props: IStatusbarEntry = {
name: localize('status.editor.status', "Language Status"),
text: first.text,
ariaLabel: first.text,
tooltip: first.message,
backgroundColor,
color
};
this.updateElement(this.statusElement, props, 'status.editor.status', StatusbarAlignment.RIGHT, 100.05);
}
private updateMetadataElement(text: string | undefined): void {
if (!text) {
this.metadataElement.clear();
@@ -645,7 +601,6 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution {
this.updateEncodingElement(this.state.encoding);
this.updateEOLElement(this.state.EOL ? this.state.EOL === '\r\n' ? nlsEOLCRLF : nlsEOLLF : undefined);
this.updateModeElement(this.state.mode);
this.updateStatusElement(this.state.status);
this.updateMetadataElement(this.state.metadata);
}
@@ -683,7 +638,6 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution {
this.onScreenReaderModeChange(activeCodeEditor);
this.onSelectionChange(activeCodeEditor);
this.onModeChange(activeCodeEditor, activeInput);
this.onLanguageStatusChange(activeCodeEditor);
this.onEOLChange(activeCodeEditor);
this.onEncodingChange(activeEditorPane, activeCodeEditor);
this.onIndentationChange(activeCodeEditor);
@@ -717,10 +671,6 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution {
this.onModeChange(activeCodeEditor, activeInput);
}));
this.activeEditorListeners.add(this.languageStatusService.onDidChange(() => {
this.onLanguageStatusChange(activeCodeEditor);
}));
// Hook Listener for content changes
this.activeEditorListeners.add(activeCodeEditor.onDidChangeModelContent((e) => {
this.onEOLChange(activeCodeEditor);
@@ -787,14 +737,6 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution {
this.updateState(info);
}
private async onLanguageStatusChange(editorWidget: ICodeEditor | undefined): Promise<void> {
const update: StateDelta = { type: 'languageStatus', status: undefined };
if (editorWidget?.hasModel()) {
update.status = await this.languageStatusService.getLanguageStatus(editorWidget.getModel());
}
this.updateState(update);
}
private onIndentationChange(editorWidget: ICodeEditor | undefined): void {
const update: StateDelta = { type: 'indentation', indentation: undefined };
@@ -0,0 +1,287 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/languageStatus';
import * as dom from 'vs/base/browser/dom';
import { renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels';
import { DisposableStore, dispose } from 'vs/base/common/lifecycle';
import Severity from 'vs/base/common/severity';
import { getCodeEditor } from 'vs/editor/browser/editorBrowser';
import { localize } from 'vs/nls';
import { Registry } from 'vs/platform/registry/common/platform';
import { registerThemingParticipant } from 'vs/platform/theme/common/themeService';
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { NOTIFICATIONS_BORDER, STATUS_BAR_ITEM_ACTIVE_BACKGROUND } from 'vs/workbench/common/theme';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { ILanguageStatus, ILanguageStatusService } from 'vs/workbench/services/languageStatus/common/languageStatusService';
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
import { IStatusbarEntry, IStatusbarEntryAccessor, IStatusbarService, StatusbarAlignment } from 'vs/workbench/services/statusbar/browser/statusbar';
import { parseLinkedText } from 'vs/base/common/linkedText';
import { Link } from 'vs/platform/opener/browser/link';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { Button } from 'vs/base/browser/ui/button/button';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { MarkdownString } from 'vs/base/common/htmlContent';
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
import { Action } from 'vs/base/common/actions';
import { Codicon } from 'vs/base/common/codicons';
import { IStorageService, IStorageValueChangeEvent, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
import { equals } from 'vs/base/common/arrays';
class LanguageStatusViewModel {
constructor(
readonly combined: readonly ILanguageStatus[],
readonly dedicated: readonly ILanguageStatus[]
) { }
isEqual(other: LanguageStatusViewModel) {
return equals(this.combined, other.combined) && equals(this.dedicated, other.dedicated);
}
}
class EditorStatusContribution implements IWorkbenchContribution {
private static readonly _id = 'status.languageStatus';
private static readonly _keyDedicatedItems = 'languageStatus.dedicated';
private readonly _disposables = new DisposableStore();
private _dedicated = new Set<string>();
private _model?: LanguageStatusViewModel;
private _combinedEntry?: IStatusbarEntryAccessor;
private _dedicatedEntries = new Map<string, IStatusbarEntryAccessor>();
private _renderDisposables = new DisposableStore();
constructor(
@ILanguageStatusService private readonly _languageStatusService: ILanguageStatusService,
@IStatusbarService private readonly _statusBarService: IStatusbarService,
@IEditorService private readonly _editorService: IEditorService,
@IOpenerService private readonly _openerService: IOpenerService,
@ICommandService private readonly _commandService: ICommandService,
@IStorageService private readonly _storageService: IStorageService,
) {
_storageService.onDidChangeValue(this._handleStorageChange, this, this._disposables);
this._restoreState();
_languageStatusService.onDidChange(this._update, this, this._disposables);
_editorService.onDidActiveEditorChange(this._update, this, this._disposables);
this._update();
_statusBarService.onDidChangeEntryVisibility(e => {
if (!e.visible && this._dedicated.has(e.id)) {
this._dedicated.delete(e.id);
this._update();
this._storeState();
}
}, this._disposables);
}
dispose(): void {
this._disposables.dispose();
this._combinedEntry?.dispose();
dispose(this._dedicatedEntries.values());
this._renderDisposables.dispose();
}
// --- persisting dedicated items
private _handleStorageChange(e: IStorageValueChangeEvent) {
if (e.key !== EditorStatusContribution._keyDedicatedItems) {
return;
}
this._restoreState();
this._update();
}
private _restoreState(): void {
const raw = this._storageService.get(EditorStatusContribution._keyDedicatedItems, StorageScope.GLOBAL, '[]');
try {
const ids = <string[]>JSON.parse(raw);
this._dedicated = new Set(ids);
} catch {
this._dedicated.clear();
}
}
private _storeState(): void {
if (this._dedicated.size === 0) {
this._storageService.remove(EditorStatusContribution._keyDedicatedItems, StorageScope.GLOBAL);
} else {
const raw = JSON.stringify(Array.from(this._dedicated.keys()));
this._storageService.store(EditorStatusContribution._keyDedicatedItems, raw, StorageScope.GLOBAL, StorageTarget.USER);
}
}
// --- language status model and UI
private _createViewModel(): LanguageStatusViewModel {
const editor = getCodeEditor(this._editorService.activeTextEditorControl);
if (!editor?.hasModel()) {
return new LanguageStatusViewModel([], []);
}
const all = this._languageStatusService.getLanguageStatus(editor.getModel());
const combined: ILanguageStatus[] = [];
const dedicated: ILanguageStatus[] = [];
for (let item of all) {
if (this._dedicated.has(item.id)) {
dedicated.push(item);
} else {
combined.push(item);
}
}
return new LanguageStatusViewModel(combined, dedicated);
}
private _update(): void {
const model = this._createViewModel();
if (this._model?.isEqual(model)) {
return;
}
this._model = model;
this._renderDisposables.clear();
// combined status bar item is a single item which hover shows
// each status item
if (model.combined.length === 0) {
// nothing
this._combinedEntry?.dispose();
this._combinedEntry = undefined;
} else {
const [first] = model.combined;
let text: string = '$(info)';
if (first.severity === Severity.Error) {
text = '$(error)';
} else if (first.severity === Severity.Warning) {
text = '$(warning)';
}
const element = document.createElement('div');
for (const status of model.combined) {
element.appendChild(this._renderStatus(status, this._renderDisposables));
}
const props: IStatusbarEntry = {
name: localize('status.editor.status', "Editor Language Status"),
ariaLabel: localize('status.editor.status', "Editor Language Status"),
tooltip: element,
text,
};
if (!this._combinedEntry) {
this._combinedEntry = this._statusBarService.addEntry(props, EditorStatusContribution._id, StatusbarAlignment.RIGHT, 100.11);
} else {
this._combinedEntry.update(props);
}
}
// dedicated status bar items are shows as-is in the status bar
const newDedicatedEntries = new Map<string, IStatusbarEntryAccessor>();
for (const status of model.dedicated) {
const props = EditorStatusContribution._asStatusbarEntry(status);
let entry = this._dedicatedEntries.get(status.id);
if (!entry) {
entry = this._statusBarService.addEntry(props, status.id, StatusbarAlignment.RIGHT, 100.09999);
} else {
entry.update(props);
this._dedicatedEntries.delete(status.id);
}
newDedicatedEntries.set(status.id, entry);
}
dispose(this._dedicatedEntries.values());
this._dedicatedEntries = newDedicatedEntries;
}
private _renderStatus(status: ILanguageStatus, store: DisposableStore): HTMLElement {
const node = document.createElement('div');
node.classList.add('hover-language-status-element');
const left = document.createElement('div');
left.classList.add('left');
node.appendChild(left);
const label = document.createElement('span');
label.classList.add('label');
dom.append(label, ...renderLabelWithIcons(status.label));
left.appendChild(label);
const detail = document.createElement('span');
detail.classList.add('detail');
this._renderTextPlus(detail, status.detail, store);
left.appendChild(detail);
const right = document.createElement('div');
right.classList.add('right');
node.appendChild(right);
const { command } = status;
if (command) {
const btn = new Button(right, { title: command.tooltip });
btn.label = command.title;
btn.onDidClick(_e => {
if (command.arguments) {
this._commandService.executeCommand(command.id, ...command.arguments);
} else {
this._commandService.executeCommand(command.id);
}
});
store.add(btn);
}
// -- pin
const action = new Action('pin', localize('pin', "Pin to Status Bar"), Codicon.pin.classNames, true, () => {
this._dedicated.add(status.id);
this._statusBarService.updateEntryVisibility(status.id, true);
this._update();
this._storeState();
});
const actionBar = new ActionBar(right, {});
actionBar.push(action, { icon: true, label: false });
store.add(action);
store.add(actionBar);
return node;
}
private _renderTextPlus(target: HTMLElement, text: string, store: DisposableStore): void {
for (let node of parseLinkedText(text).nodes) {
if (typeof node === 'string') {
const parts = renderLabelWithIcons(node);
dom.append(target, ...parts);
} else {
const link = new Link(node, undefined, this._openerService);
store.add(link);
dom.append(target, link.el);
}
}
}
// ---
private static _asStatusbarEntry(item: ILanguageStatus): IStatusbarEntry {
return {
name: item.name,
text: item.label,
ariaLabel: item.label,
tooltip: new MarkdownString(item.detail, true),
command: item.command
};
}
}
registerThemingParticipant((theme, collector) => {
collector.addRule(`:root {
--code-notifications-border: ${theme.getColor(NOTIFICATIONS_BORDER)};
--code-language-status-item-active-background: ${theme.getColor(STATUS_BAR_ITEM_ACTIVE_BACKGROUND)?.darken(.8)};
}`);
});
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(EditorStatusContribution, LifecyclePhase.Restored);
@@ -0,0 +1,61 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.monaco-workbench .hover-language-status-element {
display: flex;
justify-content: space-between;
padding: 4px 8px;
vertical-align: middle;
}
.monaco-workbench .hover-language-status-element:not(:last-child) {
border-bottom: 1px solid var(--code-notifications-border);
}
.monaco-workbench .hover-language-status-element > .left > .label::after {
content: '';
padding: 0 4px;
opacity: 0.6;
}
.monaco-workbench .hover-language-status-element > .left > .label:empty {
display: none;
}
.monaco-workbench .hover-language-status-element .right {
margin: auto 0;
display: flex;
}
.monaco-workbench .hover-language-status-element .right:not(:empty) {
padding-left: 16px;
}
.monaco-workbench .hover-language-status-element .right .monaco-button.monaco-text-button {
font-size: smaller;
padding: 2px 6px
}
.monaco-workbench .hover-language-status-element .right .monaco-action-bar:not(:first-child) {
padding-left: 8px;
}
/* todo@jrieken - unsets the default padding, something like friend/parent status bar items is needed */
.monaco-workbench .part.statusbar > .items-container > DIV#status\.languageStatus.statusbar-item a {
padding: 0 4px;
margin: 0;
}
.monaco-workbench .part.statusbar > .items-container > DIV#status\.languageStatus.statusbar-item a:hover {
background-color: var(--code-language-status-item-active-background);
}
.monaco-workbench .part.statusbar > .items-container > DIV#status\.editor\.mode.statusbar-item:hover + DIV#status\.languageStatus.statusbar-item {
background-color: var(--code-language-status-item-active-background);
}
.monaco-workbench .part.statusbar > .items-container > DIV#status\.editor\.mode.statusbar-item A {
margin-left: 0;
}
@@ -114,6 +114,7 @@ export class HoverWidget extends Widget {
} else if (options.content instanceof HTMLElement) {
contentsElement.appendChild(options.content);
contentsElement.classList.add('html-hover-contents');
} else {
const markdown = options.content;
@@ -5,10 +5,10 @@
import { CancellationToken } from 'vs/base/common/cancellation';
import { Event } from 'vs/base/common/event';
import { IMarkdownString } from 'vs/base/common/htmlContent';
import { IDisposable } from 'vs/base/common/lifecycle';
import Severity from 'vs/base/common/severity';
import { ITextModel } from 'vs/editor/common/model';
import { Command } from 'vs/editor/common/modes';
import { LanguageFeatureRegistry } from 'vs/editor/common/modes/languageFeatureRegistry';
import { LanguageSelector } from 'vs/editor/common/modes/languageSelector';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
@@ -16,10 +16,15 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'
export interface ILanguageStatus {
selector: LanguageSelector,
severity: Severity;
text: string;
message: string | IMarkdownString;
readonly id: string;
readonly name: string;
readonly selector: LanguageSelector;
readonly severity: Severity;
readonly label: string;
readonly detail: string;
readonly source: string;
readonly command: Command | undefined;
}
export interface ILanguageStatusProvider {
@@ -36,7 +41,7 @@ export interface ILanguageStatusService {
addStatus(status: ILanguageStatus): IDisposable;
getLanguageStatus(model: ITextModel): Promise<ILanguageStatus[]>;
getLanguageStatus(model: ITextModel): ILanguageStatus[];
}
@@ -52,7 +57,7 @@ class LanguageStatusServiceImpl implements ILanguageStatusService {
return this._provider.register(status.selector, status);
}
async getLanguageStatus(model: ITextModel): Promise<ILanguageStatus[]> {
getLanguageStatus(model: ITextModel): ILanguageStatus[] {
return this._provider.ordered(model).sort((a, b) => b.severity - a.severity);
}
}
@@ -49,7 +49,7 @@ export interface IStatusbarEntry {
/**
* An optional tooltip text to show when you hover over the entry
*/
readonly tooltip?: string | IMarkdownString;
readonly tooltip?: string | IMarkdownString | HTMLElement;
/**
* An optional color to use for the entry
@@ -300,6 +300,9 @@ import 'vs/workbench/contrib/typeHierarchy/browser/typeHierarchy.contribution';
import 'vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsOutline';
import 'vs/workbench/contrib/outline/browser/outline.contribution';
// Language Status
import 'vs/workbench/contrib/languageStatus/browser/languageStatus.contribution';
// Experiments
import 'vs/workbench/contrib/experiments/browser/experiments.contribution';