Path: blob/main/src/vs/workbench/contrib/debug/browser/callStackEditorContribution.ts
3296 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 { distinct } from '../../../../base/common/arrays.js';6import { Event } from '../../../../base/common/event.js';7import { Disposable } from '../../../../base/common/lifecycle.js';8import { Constants } from '../../../../base/common/uint.js';9import { ICodeEditor } from '../../../../editor/browser/editorBrowser.js';10import { Range } from '../../../../editor/common/core/range.js';11import { IEditorContribution, IEditorDecorationsCollection } from '../../../../editor/common/editorCommon.js';12import { GlyphMarginLane, IModelDecorationOptions, IModelDeltaDecoration, OverviewRulerLane, TrackedRangeStickiness } from '../../../../editor/common/model.js';13import { localize } from '../../../../nls.js';14import { ILogService } from '../../../../platform/log/common/log.js';15import { registerColor } from '../../../../platform/theme/common/colorRegistry.js';16import { themeColorFromId } from '../../../../platform/theme/common/themeService.js';17import { ThemeIcon } from '../../../../base/common/themables.js';18import { IUriIdentityService } from '../../../../platform/uriIdentity/common/uriIdentity.js';19import { debugStackframe, debugStackframeFocused } from './debugIcons.js';20import { IDebugService, IStackFrame } from '../common/debug.js';21import './media/callStackEditorContribution.css';2223export const topStackFrameColor = registerColor('editor.stackFrameHighlightBackground', { dark: '#ffff0033', light: '#ffff6673', hcDark: '#ffff0033', hcLight: '#ffff6673' }, localize('topStackFrameLineHighlight', 'Background color for the highlight of line at the top stack frame position.'));24export const focusedStackFrameColor = registerColor('editor.focusedStackFrameHighlightBackground', { dark: '#7abd7a4d', light: '#cee7ce73', hcDark: '#7abd7a4d', hcLight: '#cee7ce73' }, localize('focusedStackFrameLineHighlight', 'Background color for the highlight of line at focused stack frame position.'));25const stickiness = TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges;2627// we need a separate decoration for glyph margin, since we do not want it on each line of a multi line statement.28const TOP_STACK_FRAME_MARGIN: IModelDecorationOptions = {29description: 'top-stack-frame-margin',30glyphMarginClassName: ThemeIcon.asClassName(debugStackframe),31glyphMargin: { position: GlyphMarginLane.Right },32zIndex: 9999,33stickiness,34overviewRuler: {35position: OverviewRulerLane.Full,36color: themeColorFromId(topStackFrameColor)37}38};39const FOCUSED_STACK_FRAME_MARGIN: IModelDecorationOptions = {40description: 'focused-stack-frame-margin',41glyphMarginClassName: ThemeIcon.asClassName(debugStackframeFocused),42glyphMargin: { position: GlyphMarginLane.Right },43zIndex: 9999,44stickiness,45overviewRuler: {46position: OverviewRulerLane.Full,47color: themeColorFromId(focusedStackFrameColor)48}49};50export const TOP_STACK_FRAME_DECORATION: IModelDecorationOptions = {51description: 'top-stack-frame-decoration',52isWholeLine: true,53className: 'debug-top-stack-frame-line',54stickiness55};56export const FOCUSED_STACK_FRAME_DECORATION: IModelDecorationOptions = {57description: 'focused-stack-frame-decoration',58isWholeLine: true,59className: 'debug-focused-stack-frame-line',60stickiness61};6263export const makeStackFrameColumnDecoration = (noCharactersBefore: boolean): IModelDecorationOptions => ({64description: 'top-stack-frame-inline-decoration',65before: {66content: '\uEB8B',67inlineClassName: noCharactersBefore ? 'debug-top-stack-frame-column start-of-line' : 'debug-top-stack-frame-column',68inlineClassNameAffectsLetterSpacing: true69},70});7172export function createDecorationsForStackFrame(stackFrame: IStackFrame, isFocusedSession: boolean, noCharactersBefore: boolean): IModelDeltaDecoration[] {73// only show decorations for the currently focused thread.74const result: IModelDeltaDecoration[] = [];75const columnUntilEOLRange = new Range(stackFrame.range.startLineNumber, stackFrame.range.startColumn, stackFrame.range.startLineNumber, Constants.MAX_SAFE_SMALL_INTEGER);76const range = new Range(stackFrame.range.startLineNumber, stackFrame.range.startColumn, stackFrame.range.startLineNumber, stackFrame.range.startColumn + 1);7778// compute how to decorate the editor. Different decorations are used if this is a top stack frame, focused stack frame,79// an exception or a stack frame that did not change the line number (we only decorate the columns, not the whole line).80const topStackFrame = stackFrame.thread.getTopStackFrame();81if (stackFrame.getId() === topStackFrame?.getId()) {82if (isFocusedSession) {83result.push({84options: TOP_STACK_FRAME_MARGIN,85range86});87}8889result.push({90options: TOP_STACK_FRAME_DECORATION,91range: columnUntilEOLRange92});9394if (stackFrame.range.startColumn > 1) {95result.push({96options: makeStackFrameColumnDecoration(noCharactersBefore),97range: columnUntilEOLRange98});99}100} else {101if (isFocusedSession) {102result.push({103options: FOCUSED_STACK_FRAME_MARGIN,104range105});106}107108result.push({109options: FOCUSED_STACK_FRAME_DECORATION,110range: columnUntilEOLRange111});112}113114return result;115}116117export class CallStackEditorContribution extends Disposable implements IEditorContribution {118private decorations: IEditorDecorationsCollection;119120constructor(121private readonly editor: ICodeEditor,122@IDebugService private readonly debugService: IDebugService,123@IUriIdentityService private readonly uriIdentityService: IUriIdentityService,124@ILogService private readonly logService: ILogService,125) {126super();127this.decorations = this.editor.createDecorationsCollection();128129const setDecorations = () => this.decorations.set(this.createCallStackDecorations());130this._register(Event.any(this.debugService.getViewModel().onDidFocusStackFrame, this.debugService.getModel().onDidChangeCallStack)(() => {131setDecorations();132}));133this._register(this.editor.onDidChangeModel(e => {134if (e.newModelUrl) {135setDecorations();136}137}));138setDecorations();139}140141private createCallStackDecorations(): IModelDeltaDecoration[] {142const editor = this.editor;143if (!editor.hasModel()) {144return [];145}146147const focusedStackFrame = this.debugService.getViewModel().focusedStackFrame;148const decorations: IModelDeltaDecoration[] = [];149this.debugService.getModel().getSessions().forEach(s => {150const isSessionFocused = s === focusedStackFrame?.thread.session;151s.getAllThreads().forEach(t => {152if (t.stopped) {153const callStack = t.getCallStack();154const stackFrames: IStackFrame[] = [];155if (callStack.length > 0) {156// Always decorate top stack frame, and decorate focused stack frame if it is not the top stack frame157if (focusedStackFrame && !focusedStackFrame.equals(callStack[0])) {158stackFrames.push(focusedStackFrame);159}160stackFrames.push(callStack[0]);161}162163stackFrames.forEach(candidateStackFrame => {164if (candidateStackFrame && this.uriIdentityService.extUri.isEqual(candidateStackFrame.source.uri, editor.getModel()?.uri)) {165if (candidateStackFrame.range.startLineNumber > editor.getModel()?.getLineCount() || candidateStackFrame.range.startLineNumber < 1) {166this.logService.warn(`CallStackEditorContribution: invalid stack frame line number: ${candidateStackFrame.range.startLineNumber}`);167return;168}169170const noCharactersBefore = editor.getModel().getLineFirstNonWhitespaceColumn(candidateStackFrame.range.startLineNumber) >= candidateStackFrame.range.startColumn;171decorations.push(...createDecorationsForStackFrame(candidateStackFrame, isSessionFocused, noCharactersBefore));172}173});174}175});176});177178// Deduplicate same decorations so colors do not stack #109045179return distinct(decorations, d => `${d.options.className} ${d.options.glyphMarginClassName} ${d.range.startLineNumber} ${d.range.startColumn}`);180}181182override dispose(): void {183super.dispose();184this.decorations.clear();185}186}187188189190