Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/debug/browser/exceptionWidget.ts
5252 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import './media/exceptionWidget.css';
7
import * as nls from '../../../../nls.js';
8
import * as dom from '../../../../base/browser/dom.js';
9
import { ZoneWidget } from '../../../../editor/contrib/zoneWidget/browser/zoneWidget.js';
10
import { ICodeEditor } from '../../../../editor/browser/editorBrowser.js';
11
import { IExceptionInfo, IDebugSession, IDebugEditorContribution, EDITOR_CONTRIBUTION_ID } from '../common/debug.js';
12
import { RunOnceScheduler } from '../../../../base/common/async.js';
13
import { IThemeService, IColorTheme } from '../../../../platform/theme/common/themeService.js';
14
import { ThemeIcon } from '../../../../base/common/themables.js';
15
import { Color } from '../../../../base/common/color.js';
16
import { registerColor } from '../../../../platform/theme/common/colorRegistry.js';
17
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
18
import { DebugLinkHoverBehavior, DebugLinkHoverBehaviorTypeData, LinkDetector } from './linkDetector.js';
19
import { EditorOption } from '../../../../editor/common/config/editorOptions.js';
20
import { ActionBar } from '../../../../base/browser/ui/actionbar/actionbar.js';
21
import { Action } from '../../../../base/common/actions.js';
22
import { widgetClose } from '../../../../platform/theme/common/iconRegistry.js';
23
import { Range } from '../../../../editor/common/core/range.js';
24
25
const $ = dom.$;
26
27
// theming
28
29
const debugExceptionWidgetBorder = registerColor('debugExceptionWidget.border', '#a31515', nls.localize('debugExceptionWidgetBorder', 'Exception widget border color.'));
30
const debugExceptionWidgetBackground = registerColor('debugExceptionWidget.background', { dark: '#420b0d', light: '#f1dfde', hcDark: '#420b0d', hcLight: '#f1dfde' }, nls.localize('debugExceptionWidgetBackground', 'Exception widget background color.'));
31
32
export class ExceptionWidget extends ZoneWidget {
33
34
private backgroundColor: Color | undefined;
35
36
constructor(
37
editor: ICodeEditor,
38
private exceptionInfo: IExceptionInfo,
39
private debugSession: IDebugSession | undefined,
40
private readonly shouldScroll: () => boolean,
41
@IThemeService themeService: IThemeService,
42
@IInstantiationService private readonly instantiationService: IInstantiationService
43
) {
44
super(editor, { showFrame: true, showArrow: true, isAccessible: true, frameWidth: 1, className: 'exception-widget-container' });
45
46
this.applyTheme(themeService.getColorTheme());
47
this._disposables.add(themeService.onDidColorThemeChange(this.applyTheme.bind(this)));
48
49
this.create();
50
const onDidLayoutChangeScheduler = new RunOnceScheduler(() => this._doLayout(undefined, undefined), 50);
51
this._disposables.add(this.editor.onDidLayoutChange(() => onDidLayoutChangeScheduler.schedule()));
52
this._disposables.add(onDidLayoutChangeScheduler);
53
}
54
55
private applyTheme(theme: IColorTheme): void {
56
this.backgroundColor = theme.getColor(debugExceptionWidgetBackground);
57
const frameColor = theme.getColor(debugExceptionWidgetBorder);
58
this.style({
59
arrowColor: frameColor,
60
frameColor: frameColor
61
}); // style() will trigger _applyStyles
62
}
63
64
protected override _applyStyles(): void {
65
if (this.container) {
66
this.container.style.backgroundColor = this.backgroundColor ? this.backgroundColor.toString() : '';
67
}
68
super._applyStyles();
69
}
70
71
protected _fillContainer(container: HTMLElement): void {
72
this.setCssClass('exception-widget');
73
// Set the font size and line height to the one from the editor configuration.
74
const fontInfo = this.editor.getOption(EditorOption.fontInfo);
75
container.style.fontSize = `${fontInfo.fontSize}px`;
76
container.style.lineHeight = `${fontInfo.lineHeight}px`;
77
container.tabIndex = 0;
78
const title = $('.title');
79
const label = $('.label');
80
dom.append(title, label);
81
const actions = $('.actions');
82
dom.append(title, actions);
83
label.textContent = this.exceptionInfo.id ? nls.localize('exceptionThrownWithId', 'Exception has occurred: {0}', this.exceptionInfo.id) : nls.localize('exceptionThrown', 'Exception has occurred.');
84
let ariaLabel = label.textContent;
85
86
const actionBar = this._disposables.add(new ActionBar(actions));
87
actionBar.push(new Action('editor.closeExceptionWidget', nls.localize('close', "Close"), ThemeIcon.asClassName(widgetClose), true, async () => {
88
const contribution = this.editor.getContribution<IDebugEditorContribution>(EDITOR_CONTRIBUTION_ID);
89
contribution?.closeExceptionWidget();
90
}), { label: false, icon: true });
91
92
dom.append(container, title);
93
94
if (this.exceptionInfo.description) {
95
const description = $('.description');
96
description.textContent = this.exceptionInfo.description;
97
ariaLabel += ', ' + this.exceptionInfo.description;
98
dom.append(container, description);
99
}
100
101
if (this.exceptionInfo.details && this.exceptionInfo.details.stackTrace) {
102
const stackTrace = $('.stack-trace');
103
const linkDetector = this.instantiationService.createInstance(LinkDetector);
104
const hoverBehaviour: DebugLinkHoverBehaviorTypeData = {
105
store: this._disposables,
106
type: DebugLinkHoverBehavior.Rich,
107
};
108
const linkedStackTrace = linkDetector.linkify(this.exceptionInfo.details.stackTrace, hoverBehaviour, true, this.debugSession ? this.debugSession.root : undefined, undefined);
109
stackTrace.appendChild(linkedStackTrace);
110
dom.append(container, stackTrace);
111
ariaLabel += ', ' + this.exceptionInfo.details.stackTrace;
112
}
113
container.setAttribute('aria-label', ariaLabel);
114
}
115
116
protected override _doLayout(_heightInPixel: number | undefined, _widthInPixel: number | undefined): void {
117
// Reload the height with respect to the exception text content and relayout it to match the line count.
118
this.container!.style.height = 'initial';
119
120
const lineHeight = this.editor.getOption(EditorOption.lineHeight);
121
const arrowHeight = Math.round(lineHeight / 3);
122
const computedLinesNumber = Math.ceil((this.container!.offsetHeight + arrowHeight) / lineHeight);
123
124
this._relayout(computedLinesNumber);
125
}
126
127
protected override revealRange(range: Range, isLastLine: boolean): void {
128
// Only reveal/scroll if this widget should scroll
129
// For inactive editors, skip the reveal to prevent scrolling
130
if (this.shouldScroll()) {
131
super.revealRange(range, isLastLine);
132
}
133
}
134
135
focus(): void {
136
// Focus into the container for accessibility purposes so the exception and stack trace gets read
137
this.container?.focus();
138
}
139
140
override hasFocus(): boolean {
141
if (!this.container) {
142
return false;
143
}
144
145
return dom.isAncestorOfActiveElement(this.container);
146
}
147
148
getWhitespaceHeight(): number {
149
// Returns the height of the whitespace zone from the editor's whitespaces
150
// This is more accurate than the container height as it includes the actual zone dimensions
151
if (!this._viewZone || !this._viewZone.id) {
152
return 0;
153
}
154
155
const whitespaces = this.editor.getWhitespaces();
156
const whitespace = whitespaces.find(ws => ws.id === this._viewZone!.id);
157
return whitespace ? whitespace.height : 0;
158
}
159
}
160
161