Path: blob/main/src/vs/workbench/contrib/debug/browser/exceptionWidget.ts
5252 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import './media/exceptionWidget.css';6import * as nls from '../../../../nls.js';7import * as dom from '../../../../base/browser/dom.js';8import { ZoneWidget } from '../../../../editor/contrib/zoneWidget/browser/zoneWidget.js';9import { ICodeEditor } from '../../../../editor/browser/editorBrowser.js';10import { IExceptionInfo, IDebugSession, IDebugEditorContribution, EDITOR_CONTRIBUTION_ID } from '../common/debug.js';11import { RunOnceScheduler } from '../../../../base/common/async.js';12import { IThemeService, IColorTheme } from '../../../../platform/theme/common/themeService.js';13import { ThemeIcon } from '../../../../base/common/themables.js';14import { Color } from '../../../../base/common/color.js';15import { registerColor } from '../../../../platform/theme/common/colorRegistry.js';16import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';17import { DebugLinkHoverBehavior, DebugLinkHoverBehaviorTypeData, LinkDetector } from './linkDetector.js';18import { EditorOption } from '../../../../editor/common/config/editorOptions.js';19import { ActionBar } from '../../../../base/browser/ui/actionbar/actionbar.js';20import { Action } from '../../../../base/common/actions.js';21import { widgetClose } from '../../../../platform/theme/common/iconRegistry.js';22import { Range } from '../../../../editor/common/core/range.js';2324const $ = dom.$;2526// theming2728const debugExceptionWidgetBorder = registerColor('debugExceptionWidget.border', '#a31515', nls.localize('debugExceptionWidgetBorder', 'Exception widget border color.'));29const debugExceptionWidgetBackground = registerColor('debugExceptionWidget.background', { dark: '#420b0d', light: '#f1dfde', hcDark: '#420b0d', hcLight: '#f1dfde' }, nls.localize('debugExceptionWidgetBackground', 'Exception widget background color.'));3031export class ExceptionWidget extends ZoneWidget {3233private backgroundColor: Color | undefined;3435constructor(36editor: ICodeEditor,37private exceptionInfo: IExceptionInfo,38private debugSession: IDebugSession | undefined,39private readonly shouldScroll: () => boolean,40@IThemeService themeService: IThemeService,41@IInstantiationService private readonly instantiationService: IInstantiationService42) {43super(editor, { showFrame: true, showArrow: true, isAccessible: true, frameWidth: 1, className: 'exception-widget-container' });4445this.applyTheme(themeService.getColorTheme());46this._disposables.add(themeService.onDidColorThemeChange(this.applyTheme.bind(this)));4748this.create();49const onDidLayoutChangeScheduler = new RunOnceScheduler(() => this._doLayout(undefined, undefined), 50);50this._disposables.add(this.editor.onDidLayoutChange(() => onDidLayoutChangeScheduler.schedule()));51this._disposables.add(onDidLayoutChangeScheduler);52}5354private applyTheme(theme: IColorTheme): void {55this.backgroundColor = theme.getColor(debugExceptionWidgetBackground);56const frameColor = theme.getColor(debugExceptionWidgetBorder);57this.style({58arrowColor: frameColor,59frameColor: frameColor60}); // style() will trigger _applyStyles61}6263protected override _applyStyles(): void {64if (this.container) {65this.container.style.backgroundColor = this.backgroundColor ? this.backgroundColor.toString() : '';66}67super._applyStyles();68}6970protected _fillContainer(container: HTMLElement): void {71this.setCssClass('exception-widget');72// Set the font size and line height to the one from the editor configuration.73const fontInfo = this.editor.getOption(EditorOption.fontInfo);74container.style.fontSize = `${fontInfo.fontSize}px`;75container.style.lineHeight = `${fontInfo.lineHeight}px`;76container.tabIndex = 0;77const title = $('.title');78const label = $('.label');79dom.append(title, label);80const actions = $('.actions');81dom.append(title, actions);82label.textContent = this.exceptionInfo.id ? nls.localize('exceptionThrownWithId', 'Exception has occurred: {0}', this.exceptionInfo.id) : nls.localize('exceptionThrown', 'Exception has occurred.');83let ariaLabel = label.textContent;8485const actionBar = this._disposables.add(new ActionBar(actions));86actionBar.push(new Action('editor.closeExceptionWidget', nls.localize('close', "Close"), ThemeIcon.asClassName(widgetClose), true, async () => {87const contribution = this.editor.getContribution<IDebugEditorContribution>(EDITOR_CONTRIBUTION_ID);88contribution?.closeExceptionWidget();89}), { label: false, icon: true });9091dom.append(container, title);9293if (this.exceptionInfo.description) {94const description = $('.description');95description.textContent = this.exceptionInfo.description;96ariaLabel += ', ' + this.exceptionInfo.description;97dom.append(container, description);98}99100if (this.exceptionInfo.details && this.exceptionInfo.details.stackTrace) {101const stackTrace = $('.stack-trace');102const linkDetector = this.instantiationService.createInstance(LinkDetector);103const hoverBehaviour: DebugLinkHoverBehaviorTypeData = {104store: this._disposables,105type: DebugLinkHoverBehavior.Rich,106};107const linkedStackTrace = linkDetector.linkify(this.exceptionInfo.details.stackTrace, hoverBehaviour, true, this.debugSession ? this.debugSession.root : undefined, undefined);108stackTrace.appendChild(linkedStackTrace);109dom.append(container, stackTrace);110ariaLabel += ', ' + this.exceptionInfo.details.stackTrace;111}112container.setAttribute('aria-label', ariaLabel);113}114115protected override _doLayout(_heightInPixel: number | undefined, _widthInPixel: number | undefined): void {116// Reload the height with respect to the exception text content and relayout it to match the line count.117this.container!.style.height = 'initial';118119const lineHeight = this.editor.getOption(EditorOption.lineHeight);120const arrowHeight = Math.round(lineHeight / 3);121const computedLinesNumber = Math.ceil((this.container!.offsetHeight + arrowHeight) / lineHeight);122123this._relayout(computedLinesNumber);124}125126protected override revealRange(range: Range, isLastLine: boolean): void {127// Only reveal/scroll if this widget should scroll128// For inactive editors, skip the reveal to prevent scrolling129if (this.shouldScroll()) {130super.revealRange(range, isLastLine);131}132}133134focus(): void {135// Focus into the container for accessibility purposes so the exception and stack trace gets read136this.container?.focus();137}138139override hasFocus(): boolean {140if (!this.container) {141return false;142}143144return dom.isAncestorOfActiveElement(this.container);145}146147getWhitespaceHeight(): number {148// Returns the height of the whitespace zone from the editor's whitespaces149// This is more accurate than the container height as it includes the actual zone dimensions150if (!this._viewZone || !this._viewZone.id) {151return 0;152}153154const whitespaces = this.editor.getWhitespaces();155const whitespace = whitespaces.find(ws => ws.id === this._viewZone!.id);156return whitespace ? whitespace.height : 0;157}158}159160161