mirror of
https://github.com/microsoft/vscode.git
synced 2026-07-07 15:26:49 +01:00
189 lines
8.1 KiB
TypeScript
189 lines
8.1 KiB
TypeScript
/*---------------------------------------------------------------------------------------------
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
|
*--------------------------------------------------------------------------------------------*/
|
|
|
|
import { Constants } from 'vs/base/common/uint';
|
|
import { Range } from 'vs/editor/common/core/range';
|
|
import { TrackedRangeStickiness, IModelDeltaDecoration, IModelDecorationOptions, OverviewRulerLane } from 'vs/editor/common/model';
|
|
import { IDebugService, IStackFrame } from 'vs/workbench/contrib/debug/common/debug';
|
|
import { registerThemingParticipant, themeColorFromId, ThemeIcon } from 'vs/platform/theme/common/themeService';
|
|
import { registerColor } from 'vs/platform/theme/common/colorRegistry';
|
|
import { localize } from 'vs/nls';
|
|
import { Event } from 'vs/base/common/event';
|
|
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
|
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/platform/uriIdentity/common/uriIdentity';
|
|
import { debugStackframe, debugStackframeFocused } from 'vs/workbench/contrib/debug/browser/debugIcons';
|
|
import { ILogService } from 'vs/platform/log/common/log';
|
|
|
|
export 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.'));
|
|
export 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.'));
|
|
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 = {
|
|
description: 'top-stack-frame-margin',
|
|
glyphMarginClassName: ThemeIcon.asClassName(debugStackframe),
|
|
stickiness,
|
|
overviewRuler: {
|
|
position: OverviewRulerLane.Full,
|
|
color: themeColorFromId(topStackFrameColor)
|
|
}
|
|
};
|
|
const FOCUSED_STACK_FRAME_MARGIN: IModelDecorationOptions = {
|
|
description: 'focused-stack-frame-margin',
|
|
glyphMarginClassName: ThemeIcon.asClassName(debugStackframeFocused),
|
|
stickiness,
|
|
overviewRuler: {
|
|
position: OverviewRulerLane.Full,
|
|
color: themeColorFromId(focusedStackFrameColor)
|
|
}
|
|
};
|
|
const TOP_STACK_FRAME_DECORATION: IModelDecorationOptions = {
|
|
description: 'top-stack-frame-decoration',
|
|
isWholeLine: true,
|
|
className: 'debug-top-stack-frame-line',
|
|
stickiness
|
|
};
|
|
const FOCUSED_STACK_FRAME_DECORATION: IModelDecorationOptions = {
|
|
description: 'focused-stack-frame-decoration',
|
|
isWholeLine: true,
|
|
className: 'debug-focused-stack-frame-line',
|
|
stickiness
|
|
};
|
|
|
|
export function createDecorationsForStackFrame(stackFrame: IStackFrame, isFocusedSession: boolean, noCharactersBefore: boolean): IModelDeltaDecoration[] {
|
|
// only show decorations for the currently focused thread.
|
|
const result: IModelDeltaDecoration[] = [];
|
|
const columnUntilEOLRange = new Range(stackFrame.range.startLineNumber, stackFrame.range.startColumn, stackFrame.range.startLineNumber, Constants.MAX_SAFE_SMALL_INTEGER);
|
|
const range = new Range(stackFrame.range.startLineNumber, stackFrame.range.startColumn, stackFrame.range.startLineNumber, stackFrame.range.startColumn + 1);
|
|
|
|
// compute how to decorate the editor. Different decorations are used if this is a top stack frame, focused stack frame,
|
|
// an exception or a stack frame that did not change the line number (we only decorate the columns, not the whole line).
|
|
const topStackFrame = stackFrame.thread.getTopStackFrame();
|
|
if (stackFrame.getId() === topStackFrame?.getId()) {
|
|
if (isFocusedSession) {
|
|
result.push({
|
|
options: TOP_STACK_FRAME_MARGIN,
|
|
range
|
|
});
|
|
}
|
|
|
|
result.push({
|
|
options: TOP_STACK_FRAME_DECORATION,
|
|
range: columnUntilEOLRange
|
|
});
|
|
|
|
if (stackFrame.range.startColumn > 1) {
|
|
result.push({
|
|
options: {
|
|
description: 'top-stack-frame-inline-decoration',
|
|
before: {
|
|
content: '\uEB8B',
|
|
inlineClassName: noCharactersBefore ? 'debug-top-stack-frame-column start-of-line' : 'debug-top-stack-frame-column',
|
|
inlineClassNameAffectsLetterSpacing: true
|
|
},
|
|
},
|
|
range: columnUntilEOLRange
|
|
});
|
|
}
|
|
} else {
|
|
if (isFocusedSession) {
|
|
result.push({
|
|
options: FOCUSED_STACK_FRAME_MARGIN,
|
|
range
|
|
});
|
|
}
|
|
|
|
result.push({
|
|
options: FOCUSED_STACK_FRAME_DECORATION,
|
|
range: columnUntilEOLRange
|
|
});
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
export class CallStackEditorContribution implements IEditorContribution {
|
|
private toDispose: IDisposable[] = [];
|
|
private decorationIds: string[] = [];
|
|
|
|
constructor(
|
|
private readonly editor: ICodeEditor,
|
|
@IDebugService private readonly debugService: IDebugService,
|
|
@IUriIdentityService private readonly uriIdentityService: IUriIdentityService,
|
|
@ILogService private readonly logService: ILogService,
|
|
) {
|
|
const setDecorations = () => this.decorationIds = this.editor.deltaDecorations(this.decorationIds, this.createCallStackDecorations());
|
|
this.toDispose.push(Event.any(this.debugService.getViewModel().onDidFocusStackFrame, this.debugService.getModel().onDidChangeCallStack)(() => {
|
|
setDecorations();
|
|
}));
|
|
this.toDispose.push(this.editor.onDidChangeModel(e => {
|
|
if (e.newModelUrl) {
|
|
setDecorations();
|
|
}
|
|
}));
|
|
}
|
|
|
|
private createCallStackDecorations(): IModelDeltaDecoration[] {
|
|
const editor = this.editor;
|
|
if (!editor.hasModel()) {
|
|
return [];
|
|
}
|
|
|
|
const focusedStackFrame = this.debugService.getViewModel().focusedStackFrame;
|
|
const decorations: IModelDeltaDecoration[] = [];
|
|
this.debugService.getModel().getSessions().forEach(s => {
|
|
const isSessionFocused = s === focusedStackFrame?.thread.session;
|
|
s.getAllThreads().forEach(t => {
|
|
if (t.stopped) {
|
|
const callStack = t.getCallStack();
|
|
const stackFrames: IStackFrame[] = [];
|
|
if (callStack.length > 0) {
|
|
// Always decorate top stack frame, and decorate focused stack frame if it is not the top stack frame
|
|
if (focusedStackFrame && !focusedStackFrame.equals(callStack[0])) {
|
|
stackFrames.push(focusedStackFrame);
|
|
}
|
|
stackFrames.push(callStack[0]);
|
|
}
|
|
|
|
stackFrames.forEach(candidateStackFrame => {
|
|
if (candidateStackFrame && this.uriIdentityService.extUri.isEqual(candidateStackFrame.source.uri, editor.getModel()?.uri)) {
|
|
if (candidateStackFrame.range.startLineNumber > editor.getModel()?.getLineCount() || candidateStackFrame.range.startLineNumber < 1) {
|
|
this.logService.warn(`CallStackEditorContribution: invalid stack frame line number: ${candidateStackFrame.range.startLineNumber}`);
|
|
return;
|
|
}
|
|
|
|
const noCharactersBefore = editor.getModel().getLineFirstNonWhitespaceColumn(candidateStackFrame.range.startLineNumber) >= candidateStackFrame.range.startColumn;
|
|
decorations.push(...createDecorationsForStackFrame(candidateStackFrame, isSessionFocused, noCharactersBefore));
|
|
}
|
|
});
|
|
}
|
|
});
|
|
});
|
|
|
|
// Deduplicate same decorations so colors do not stack #109045
|
|
return distinct(decorations, d => `${d.options.className} ${d.options.glyphMarginClassName} ${d.range.startLineNumber} ${d.range.startColumn}`);
|
|
}
|
|
|
|
dispose(): void {
|
|
this.editor.deltaDecorations(this.decorationIds, []);
|
|
this.toDispose = dispose(this.toDispose);
|
|
}
|
|
}
|
|
|
|
registerThemingParticipant((theme, collector) => {
|
|
const topStackFrame = theme.getColor(topStackFrameColor);
|
|
if (topStackFrame) {
|
|
collector.addRule(`.monaco-editor .view-overlays .debug-top-stack-frame-line { background: ${topStackFrame}; }`);
|
|
}
|
|
|
|
const focusedStackFrame = theme.getColor(focusedStackFrameColor);
|
|
if (focusedStackFrame) {
|
|
collector.addRule(`.monaco-editor .view-overlays .debug-focused-stack-frame-line { background: ${focusedStackFrame}; }`);
|
|
}
|
|
});
|