Skip to content

themable debug icons #111183

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Nov 24, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
9 changes: 7 additions & 2 deletions src/vs/base/common/codicons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,12 @@ class Registry implements IIconRegistry {
private readonly _onDidRegister = new Emitter<Codicon>();

public add(icon: Codicon) {
if (!this._icons.has(icon.id)) {
const existing = this._icons.get(icon.id);
if (!existing) {
this._icons.set(icon.id, icon);
this._onDidRegister.fire(icon);
} else if (icon.description) {
existing.description = icon.description;
} else {
console.error(`Duplicate registration of codicon ${icon.id}`);
}
Expand All @@ -44,7 +47,7 @@ const _registry = new Registry();
export const iconRegistry: IIconRegistry = _registry;

export function registerIcon(id: string, def: Codicon, description?: string) {
return new Codicon(id, def);
return new Codicon(id, def, description);
}

export class Codicon {
Expand All @@ -55,6 +58,8 @@ export class Codicon {
// classNamesArray is useful for migrating to ES6 classlist
public get classNamesArray() { return ['codicon', 'codicon-' + this.id]; }
public get cssSelector() { return '.codicon.codicon-' + this.id; }

public get classNameIdentifier() { return 'codicon-' + this.id; }
}

interface IconDefinition {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { BreakpointWidget } from 'vs/workbench/contrib/debug/browser/breakpointWidget';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { MarkdownString } from 'vs/base/common/htmlContent';
import { getBreakpointMessageAndClassName } from 'vs/workbench/contrib/debug/browser/breakpointsView';
import { getBreakpointMessageAndIcon } from 'vs/workbench/contrib/debug/browser/breakpointsView';
import { generateUuid } from 'vs/base/common/uuid';
import { memoize } from 'vs/base/common/decorators';
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
Expand All @@ -35,6 +35,7 @@ import { isSafari } from 'vs/base/browser/browser';
import { registerThemingParticipant, themeColorFromId } from 'vs/platform/theme/common/themeService';
import { registerColor } from 'vs/platform/theme/common/colorRegistry';
import { ILabelService } from 'vs/platform/label/common/label';
import * as icons from 'vs/workbench/contrib/debug/browser/debugIcons';

const $ = dom.$;

Expand All @@ -46,7 +47,7 @@ interface IBreakpointDecoration {
}

const breakpointHelperDecoration: IModelDecorationOptions = {
glyphMarginClassName: 'codicon-debug-hint',
glyphMarginClassName: icons.debugBreakpointHint.classNames,
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges
};

Expand All @@ -72,7 +73,7 @@ export function createBreakpointDecorations(model: ITextModel, breakpoints: Read
}

function getBreakpointDecorationOptions(model: ITextModel, breakpoint: IBreakpoint, state: State, breakpointsActivated: boolean, showBreakpointsInOverviewRuler: boolean): IModelDecorationOptions {
const { className, message } = getBreakpointMessageAndClassName(state, breakpointsActivated, breakpoint, undefined);
const { icon, message } = getBreakpointMessageAndIcon(state, breakpointsActivated, breakpoint, undefined);
let glyphMarginHoverMessage: MarkdownString | undefined;

if (message) {
Expand All @@ -94,7 +95,7 @@ function getBreakpointDecorationOptions(model: ITextModel, breakpoint: IBreakpoi

const renderInline = breakpoint.column && (breakpoint.column > model.getLineFirstNonWhitespaceColumn(breakpoint.lineNumber));
return {
glyphMarginClassName: `${className}`,
glyphMarginClassName: icon.classNames,
glyphMarginHoverMessage,
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
beforeContentClassName: renderInline ? `debug-breakpoint-placeholder` : undefined,
Expand Down Expand Up @@ -453,9 +454,9 @@ export class BreakpointEditorContribution implements IBreakpointEditorContributi
// Candidate decoration has a breakpoint attached when a breakpoint is already at that location and we did not yet set a decoration there
// In practice this happens for the first breakpoint that was set on a line
// We could have also rendered this first decoration as part of desiredBreakpointDecorations however at that moment we have no location information
const cssClass = candidate.breakpoint ? getBreakpointMessageAndClassName(this.debugService.state, this.debugService.getModel().areBreakpointsActivated(), candidate.breakpoint, this.labelService).className : 'codicon-debug-breakpoint-disabled';
const icon = candidate.breakpoint ? getBreakpointMessageAndIcon(this.debugService.state, this.debugService.getModel().areBreakpointsActivated(), candidate.breakpoint, this.labelService).icon : icons.debugBreakpointDisabled;
const contextMenuActions = () => this.getContextMenuActions(candidate.breakpoint ? [candidate.breakpoint] : [], activeCodeEditor.getModel().uri, candidate.range.startLineNumber, candidate.range.startColumn);
const inlineWidget = new InlineBreakpointWidget(activeCodeEditor, decorationId, cssClass, candidate.breakpoint, this.debugService, this.contextMenuService, contextMenuActions);
const inlineWidget = new InlineBreakpointWidget(activeCodeEditor, decorationId, icon.classNames, candidate.breakpoint, this.debugService, this.contextMenuService, contextMenuActions);

return {
decorationId,
Expand Down Expand Up @@ -575,9 +576,8 @@ class InlineBreakpointWidget implements IContentWidget, IDisposable {

private create(cssClass: string | null | undefined): void {
this.domNode = $('.inline-breakpoint-widget');
this.domNode.classList.add('codicon');
if (cssClass) {
this.domNode.classList.add(cssClass);
this.domNode.classList.add(...cssClass.split(' '));
}
this.toDispose.push(dom.addDisposableListener(this.domNode, dom.EventType.CLICK, async e => {
if (this.breakpoint) {
Expand Down Expand Up @@ -645,15 +645,15 @@ registerThemingParticipant((theme, collector) => {
const debugIconBreakpointColor = theme.getColor(debugIconBreakpointForeground);
if (debugIconBreakpointColor) {
collector.addRule(`
.monaco-workbench .codicon-debug-breakpoint,
.monaco-workbench .codicon-debug-breakpoint-conditional,
.monaco-workbench .codicon-debug-breakpoint-log,
.monaco-workbench .codicon-debug-breakpoint-function,
.monaco-workbench .codicon-debug-breakpoint-data,
.monaco-workbench .codicon-debug-breakpoint-unsupported,
.monaco-workbench .codicon-debug-hint:not([class*='codicon-debug-breakpoint']):not([class*='codicon-debug-stackframe']),
.monaco-workbench .codicon-debug-breakpoint.codicon-debug-stackframe-focused::after,
.monaco-workbench .codicon-debug-breakpoint.codicon-debug-stackframe::after {
.monaco-workbench ${icons.debugBreakpoint.cssSelector},
.monaco-workbench ${icons.debugBreakpointConditional.cssSelector},
.monaco-workbench ${icons.debugBreakpointLog.cssSelector},
.monaco-workbench ${icons.debugBreakpointFunction.cssSelector},
.monaco-workbench ${icons.debugBreakpointData.cssSelector},
.monaco-workbench ${icons.debugBreakpointUnsupported.cssSelector},
.monaco-workbench ${icons.debugBreakpointHint.cssSelector}:not([class*='${icons.debugBreakpoint.classNameIdentifier}']):not([class*='${icons.debugStackframe.classNameIdentifier}']),
.monaco-workbench ${icons.debugBreakpoint.cssSelector}${icons.debugStackframeFocused.cssSelector}::after,
.monaco-workbench ${icons.debugBreakpoint.cssSelector}${icons.debugStackframe.cssSelector}::after {
color: ${debugIconBreakpointColor} !important;
}
`);
Expand All @@ -680,7 +680,7 @@ registerThemingParticipant((theme, collector) => {
const debugIconBreakpointCurrentStackframeForegroundColor = theme.getColor(debugIconBreakpointCurrentStackframeForeground);
if (debugIconBreakpointCurrentStackframeForegroundColor) {
collector.addRule(`
.monaco-workbench .codicon-debug-stackframe,
.monaco-workbench ${icons.debugStackframe.cssSelector},
.monaco-editor .debug-top-stack-frame-column::before {
color: ${debugIconBreakpointCurrentStackframeForegroundColor} !important;
}
Expand All @@ -690,7 +690,7 @@ registerThemingParticipant((theme, collector) => {
const debugIconBreakpointStackframeFocusedColor = theme.getColor(debugIconBreakpointStackframeForeground);
if (debugIconBreakpointStackframeFocusedColor) {
collector.addRule(`
.monaco-workbench .codicon-debug-stackframe-focused {
.monaco-workbench ${icons.debugStackframeFocused.cssSelector} {
color: ${debugIconBreakpointStackframeFocusedColor} !important;
}
`);
Expand Down
40 changes: 21 additions & 19 deletions src/vs/workbench/contrib/debug/browser/breakpointsView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ import { IOpenerService } from 'vs/platform/opener/common/opener';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { Orientation } from 'vs/base/browser/ui/splitview/splitview';
import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget';
import * as icons from 'vs/workbench/contrib/debug/browser/debugIcons';
import { Codicon } from 'vs/base/common/codicons';

const $ = dom.$;

Expand Down Expand Up @@ -379,8 +381,8 @@ class BreakpointsRenderer implements IListRenderer<IBreakpoint, IBreakpointTempl
data.filePath.textContent = this.labelService.getUriLabel(resources.dirname(breakpoint.uri), { relative: true });
data.checkbox.checked = breakpoint.enabled;

const { message, className } = getBreakpointMessageAndClassName(this.debugService.state, this.debugService.getModel().areBreakpointsActivated(), breakpoint, this.labelService);
data.icon.className = `codicon ${className}`;
const { message, icon } = getBreakpointMessageAndIcon(this.debugService.state, this.debugService.getModel().areBreakpointsActivated(), breakpoint, this.labelService);
data.icon.className = icon.classNames;
data.breakpoint.title = breakpoint.message || message || '';

const debugActive = this.debugService.state === State.Running || this.debugService.state === State.Stopped;
Expand Down Expand Up @@ -475,8 +477,8 @@ class FunctionBreakpointsRenderer implements IListRenderer<FunctionBreakpoint, I
renderElement(functionBreakpoint: FunctionBreakpoint, _index: number, data: IBaseBreakpointWithIconTemplateData): void {
data.context = functionBreakpoint;
data.name.textContent = functionBreakpoint.name;
const { className, message } = getBreakpointMessageAndClassName(this.debugService.state, this.debugService.getModel().areBreakpointsActivated(), functionBreakpoint, this.labelService);
data.icon.className = `codicon ${className}`;
const { icon, message } = getBreakpointMessageAndIcon(this.debugService.state, this.debugService.getModel().areBreakpointsActivated(), functionBreakpoint, this.labelService);
data.icon.className = icon.classNames;
data.icon.title = message ? message : '';
data.checkbox.checked = functionBreakpoint.enabled;
data.breakpoint.title = message ? message : '';
Expand Down Expand Up @@ -531,8 +533,8 @@ class DataBreakpointsRenderer implements IListRenderer<DataBreakpoint, IBaseBrea
renderElement(dataBreakpoint: DataBreakpoint, _index: number, data: IBaseBreakpointWithIconTemplateData): void {
data.context = dataBreakpoint;
data.name.textContent = dataBreakpoint.description;
const { className, message } = getBreakpointMessageAndClassName(this.debugService.state, this.debugService.getModel().areBreakpointsActivated(), dataBreakpoint, this.labelService);
data.icon.className = `codicon ${className}`;
const { icon, message } = getBreakpointMessageAndIcon(this.debugService.state, this.debugService.getModel().areBreakpointsActivated(), dataBreakpoint, this.labelService);
data.icon.className = icon.classNames;
data.icon.title = message ? message : '';
data.checkbox.checked = dataBreakpoint.enabled;
data.breakpoint.title = message ? message : '';
Expand Down Expand Up @@ -622,9 +624,9 @@ class FunctionBreakpointInputRenderer implements IListRenderer<IFunctionBreakpoi
renderElement(functionBreakpoint: FunctionBreakpoint, _index: number, data: IInputTemplateData): void {
data.breakpoint = functionBreakpoint;
data.reactedOnEvent = false;
const { className, message } = getBreakpointMessageAndClassName(this.debugService.state, this.debugService.getModel().areBreakpointsActivated(), functionBreakpoint, this.labelService);
const { icon, message } = getBreakpointMessageAndIcon(this.debugService.state, this.debugService.getModel().areBreakpointsActivated(), functionBreakpoint, this.labelService);

data.icon.className = `codicon ${className}`;
data.icon.className = icon.classNames;
data.icon.title = message ? message : '';
data.checkbox.checked = functionBreakpoint.enabled;
data.checkbox.disabled = true;
Expand Down Expand Up @@ -664,7 +666,7 @@ class BreakpointsAccessibilityProvider implements IListAccessibilityProvider<Bre
return element.toString();
}

const { message } = getBreakpointMessageAndClassName(this.debugService.state, this.debugService.getModel().areBreakpointsActivated(), element as IBreakpoint | IDataBreakpoint | IFunctionBreakpoint, this.labelService);
const { message } = getBreakpointMessageAndIcon(this.debugService.state, this.debugService.getModel().areBreakpointsActivated(), element as IBreakpoint | IDataBreakpoint | IFunctionBreakpoint, this.labelService);
const toString = element.toString();

return message ? `${toString}, ${message}` : toString;
Expand Down Expand Up @@ -700,12 +702,12 @@ export function openBreakpointSource(breakpoint: IBreakpoint, sideBySide: boolea
}, sideBySide ? SIDE_GROUP : ACTIVE_GROUP);
}

export function getBreakpointMessageAndClassName(state: State, breakpointsActivated: boolean, breakpoint: IBreakpoint | IFunctionBreakpoint | IDataBreakpoint, labelService?: ILabelService): { message?: string, className: string } {
export function getBreakpointMessageAndIcon(state: State, breakpointsActivated: boolean, breakpoint: IBreakpoint | IFunctionBreakpoint | IDataBreakpoint, labelService?: ILabelService): { message?: string, icon: Codicon } {
const debugActive = state === State.Running || state === State.Stopped;

if (!breakpoint.enabled || !breakpointsActivated) {
return {
className: breakpoint instanceof DataBreakpoint ? 'codicon-debug-breakpoint-data-disabled' : breakpoint instanceof FunctionBreakpoint ? 'codicon-debug-breakpoint-function-disabled' : breakpoint.logMessage ? 'codicon-debug-breakpoint-log-disabled' : 'codicon-debug-breakpoint-disabled',
icon: breakpoint instanceof DataBreakpoint ? icons.debugBreakpointDataDisabled : breakpoint instanceof FunctionBreakpoint ? icons.debugBreakpointFunctionDisabled : breakpoint.logMessage ? icons.debugBreakpointLogDisabled : icons.debugBreakpointDisabled,
message: breakpoint.logMessage ? nls.localize('disabledLogpoint', "Disabled Logpoint") : nls.localize('disabledBreakpoint', "Disabled Breakpoint"),
};
}
Expand All @@ -715,35 +717,35 @@ export function getBreakpointMessageAndClassName(state: State, breakpointsActiva
};
if (debugActive && !breakpoint.verified) {
return {
className: breakpoint instanceof DataBreakpoint ? 'codicon-debug-breakpoint-data-unverified' : breakpoint instanceof FunctionBreakpoint ? 'codicon-debug-breakpoint-function-unverified' : breakpoint.logMessage ? 'codicon-debug-breakpoint-log-unverified' : 'codicon-debug-breakpoint-unverified',
icon: breakpoint instanceof DataBreakpoint ? icons.debugBreakpointDataUnverified : breakpoint instanceof FunctionBreakpoint ? icons.debugBreakpointFunctionUnverified : breakpoint.logMessage ? icons.debugBreakpointLogUnverified : icons.debugBreakpointUnverified,
message: ('message' in breakpoint && breakpoint.message) ? breakpoint.message : (breakpoint.logMessage ? nls.localize('unverifiedLogpoint', "Unverified Logpoint") : nls.localize('unverifiedBreakopint', "Unverified Breakpoint")),
};
}

if (breakpoint instanceof FunctionBreakpoint) {
if (!breakpoint.supported) {
return {
className: 'codicon-debug-breakpoint-function-unverified',
icon: icons.debugBreakpointFunctionUnverified,
message: nls.localize('functionBreakpointUnsupported', "Function breakpoints not supported by this debug type"),
};
}

return {
className: 'codicon-debug-breakpoint-function',
icon: icons.debugBreakpointFunction,
message: breakpoint.message || nls.localize('functionBreakpoint', "Function Breakpoint")
};
}

if (breakpoint instanceof DataBreakpoint) {
if (!breakpoint.supported) {
return {
className: 'codicon-debug-breakpoint-data-unverified',
icon: icons.debugBreakpointDataUnverified,
message: nls.localize('dataBreakpointUnsupported', "Data breakpoints not supported by this debug type"),
};
}

return {
className: 'codicon-debug-breakpoint-data',
icon: icons.debugBreakpointData,
message: breakpoint.message || nls.localize('dataBreakpoint', "Data Breakpoint")
};
}
Expand All @@ -753,7 +755,7 @@ export function getBreakpointMessageAndClassName(state: State, breakpointsActiva

if (!breakpoint.supported) {
return {
className: 'codicon-debug-breakpoint-unsupported',
icon: icons.debugBreakpointUnsupported,
message: nls.localize('breakpointUnsupported', "Breakpoints of this type are not supported by the debugger"),
};
}
Expand All @@ -769,14 +771,14 @@ export function getBreakpointMessageAndClassName(state: State, breakpointsActiva
}

return {
className: breakpoint.logMessage ? 'codicon-debug-breakpoint-log' : 'codicon-debug-breakpoint-conditional',
icon: breakpoint.logMessage ? icons.debugBreakpointLog : icons.debugBreakpointConditional,
message: appendMessage(messages.join('\n'))
};
}

const message = ('message' in breakpoint && breakpoint.message) ? breakpoint.message : breakpoint instanceof Breakpoint && labelService ? labelService.getUriLabel(breakpoint.uri) : nls.localize('breakpoint', "Breakpoint");
return {
className: 'codicon-debug-breakpoint',
icon: icons.debugBreakpoint,
message
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,23 @@ import { IEditorContribution } from 'vs/editor/common/editorCommon';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { distinct } from 'vs/base/common/arrays';
import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity';
import { debugStackframe, debugStackframeFocused } from 'vs/workbench/contrib/debug/browser/debugIcons';

const topStackFrameColor = registerColor('editor.stackFrameHighlightBackground', { dark: '#ffff0033', light: '#ffff6673', hc: '#ffff0033' }, localize('topStackFrameLineHighlight', 'Background color for the highlight of line at the top stack frame position.'));
const focusedStackFrameColor = registerColor('editor.focusedStackFrameHighlightBackground', { dark: '#7abd7a4d', light: '#cee7ce73', hc: '#7abd7a4d' }, localize('focusedStackFrameLineHighlight', 'Background color for the highlight of line at focused stack frame position.'));
const stickiness = TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges;

// we need a separate decoration for glyph margin, since we do not want it on each line of a multi line statement.
const TOP_STACK_FRAME_MARGIN: IModelDecorationOptions = {
glyphMarginClassName: 'codicon-debug-stackframe',
glyphMarginClassName: debugStackframe.classNames,
stickiness,
overviewRuler: {
position: OverviewRulerLane.Full,
color: themeColorFromId(topStackFrameColor)
}
};
const FOCUSED_STACK_FRAME_MARGIN: IModelDecorationOptions = {
glyphMarginClassName: 'codicon-debug-stackframe-focused',
glyphMarginClassName: debugStackframeFocused.classNames,
stickiness,
overviewRuler: {
position: OverviewRulerLane.Full,
Expand Down
Loading