Merge from vscode a234f13c45b40a0929777cb440ee011b7549eed2 (#8911)

* Merge from vscode a234f13c45b40a0929777cb440ee011b7549eed2

* update distro

* fix layering

* update distro

* fix tests
This commit is contained in:
Anthony Dresser
2020-01-22 13:42:37 -08:00
committed by GitHub
parent 977111eb21
commit bd7aac8ee0
895 changed files with 24651 additions and 14520 deletions

View File

@@ -9,7 +9,7 @@ import { Expression, Variable, ExpressionContainer } from 'vs/workbench/contrib/
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { IInputValidationOptions, InputBox } from 'vs/base/browser/ui/inputbox/inputBox';
import { ITreeRenderer, ITreeNode } from 'vs/base/browser/ui/tree/tree';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { IDisposable, dispose, Disposable, toDisposable } from 'vs/base/common/lifecycle';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { attachInputBoxStyler } from 'vs/platform/theme/common/styler';
import { KeyCode } from 'vs/base/common/keyCodes';
@@ -18,6 +18,7 @@ import { HighlightedLabel, IHighlight } from 'vs/base/browser/ui/highlightedlabe
import { FuzzyScore, createMatches } from 'vs/base/common/filters';
import { LinkDetector } from 'vs/workbench/contrib/debug/browser/linkDetector';
import { ReplEvaluationResult } from 'vs/workbench/contrib/debug/common/replModel';
import { once } from 'vs/base/common/functional';
export const MAX_VALUE_RENDER_LENGTH_IN_VIEWLET = 1024;
export const twistiePixels = 20;
@@ -42,7 +43,7 @@ export interface IVariableTemplateData {
}
export function renderViewTree(container: HTMLElement): HTMLElement {
const treeContainer = document.createElement('div');
const treeContainer = $('.');
dom.addClass(treeContainer, 'debug-view-content');
container.appendChild(treeContainer);
return treeContainer;
@@ -137,8 +138,7 @@ export interface IExpressionTemplateData {
name: HTMLSpanElement;
value: HTMLSpanElement;
inputBoxContainer: HTMLElement;
enableInputBox(options: IInputBoxOptions): void;
toDispose: IDisposable[];
toDispose: IDisposable;
label: HighlightedLabel;
}
@@ -159,77 +159,84 @@ export abstract class AbstractExpressionsRenderer implements ITreeRenderer<IExpr
const label = new HighlightedLabel(name, false);
const inputBoxContainer = dom.append(expression, $('.inputBoxContainer'));
const toDispose: IDisposable[] = [];
const enableInputBox = (options: IInputBoxOptions) => {
name.style.display = 'none';
value.style.display = 'none';
inputBoxContainer.style.display = 'initial';
const inputBox = new InputBox(inputBoxContainer, this.contextViewService, options);
const styler = attachInputBoxStyler(inputBox, this.themeService);
inputBox.value = replaceWhitespace(options.initialValue);
inputBox.focus();
inputBox.select();
let disposed = false;
toDispose.push(inputBox);
toDispose.push(styler);
const wrapUp = (renamed: boolean) => {
if (!disposed) {
disposed = true;
this.debugService.getViewModel().setSelectedExpression(undefined);
options.onFinish(inputBox.value, renamed);
// need to remove the input box since this template will be reused.
inputBoxContainer.removeChild(inputBox.element);
name.style.display = 'initial';
value.style.display = 'initial';
inputBoxContainer.style.display = 'none';
dispose(toDispose);
}
};
toDispose.push(dom.addStandardDisposableListener(inputBox.inputElement, 'keydown', (e: IKeyboardEvent) => {
const isEscape = e.equals(KeyCode.Escape);
const isEnter = e.equals(KeyCode.Enter);
if (isEscape || isEnter) {
e.preventDefault();
e.stopPropagation();
wrapUp(isEnter);
}
}));
toDispose.push(dom.addDisposableListener(inputBox.inputElement, 'blur', () => {
wrapUp(true);
}));
toDispose.push(dom.addDisposableListener(inputBox.inputElement, 'click', e => {
// Do not expand / collapse selected elements
e.preventDefault();
e.stopPropagation();
}));
};
return { expression, name, value, label, enableInputBox, inputBoxContainer, toDispose };
return { expression, name, value, label, inputBoxContainer, toDispose: Disposable.None };
}
renderElement(node: ITreeNode<IExpression, FuzzyScore>, index: number, data: IExpressionTemplateData): void {
data.toDispose.dispose();
data.toDispose = Disposable.None;
const { element } = node;
if (element === this.debugService.getViewModel().getSelectedExpression() || (element instanceof Variable && element.errorMessage)) {
const options = this.getInputBoxOptions(element);
if (options) {
data.enableInputBox(options);
data.toDispose = this.renderInputBox(data.name, data.value, data.inputBoxContainer, options);
return;
}
}
this.renderExpression(element, data, createMatches(node.filterData));
}
renderInputBox(nameElement: HTMLElement, valueElement: HTMLElement, inputBoxContainer: HTMLElement, options: IInputBoxOptions): IDisposable {
nameElement.style.display = 'none';
valueElement.style.display = 'none';
inputBoxContainer.style.display = 'initial';
const inputBox = new InputBox(inputBoxContainer, this.contextViewService, options);
const styler = attachInputBoxStyler(inputBox, this.themeService);
inputBox.value = replaceWhitespace(options.initialValue);
inputBox.focus();
inputBox.select();
const done = once((success: boolean, finishEditing: boolean) => {
nameElement.style.display = 'initial';
valueElement.style.display = 'initial';
inputBoxContainer.style.display = 'none';
const value = inputBox.value;
dispose(toDispose);
if (finishEditing) {
this.debugService.getViewModel().setSelectedExpression(undefined);
options.onFinish(value, success);
}
});
const toDispose = [
inputBox,
dom.addStandardDisposableListener(inputBox.inputElement, dom.EventType.KEY_DOWN, (e: IKeyboardEvent) => {
const isEscape = e.equals(KeyCode.Escape);
const isEnter = e.equals(KeyCode.Enter);
if (isEscape || isEnter) {
e.preventDefault();
e.stopPropagation();
done(isEnter, true);
}
}),
dom.addDisposableListener(inputBox.inputElement, dom.EventType.BLUR, () => {
done(true, true);
}),
dom.addDisposableListener(inputBox.inputElement, dom.EventType.CLICK, e => {
// Do not expand / collapse selected elements
e.preventDefault();
e.stopPropagation();
}),
styler
];
return toDisposable(() => {
done(false, false);
});
}
protected abstract renderExpression(expression: IExpression, data: IExpressionTemplateData, highlights: IHighlight[]): void;
protected abstract getInputBoxOptions(expression: IExpression): IInputBoxOptions | undefined;
disposeElement(node: ITreeNode<IExpression, FuzzyScore>, index: number, templateData: IExpressionTemplateData): void {
templateData.toDispose.dispose();
}
disposeTemplate(templateData: IExpressionTemplateData): void {
dispose(templateData.toDispose);
templateData.toDispose.dispose();
}
}

View File

@@ -11,13 +11,12 @@ import severity from 'vs/base/common/severity';
import { IAction, Action } from 'vs/base/common/actions';
import { Range } from 'vs/editor/common/core/range';
import { ICodeEditor, IEditorMouseEvent, MouseTargetType, IContentWidget, IActiveCodeEditor, IContentWidgetPosition, ContentWidgetPositionPreference } from 'vs/editor/browser/editorBrowser';
import { registerEditorContribution } from 'vs/editor/browser/editorExtensions';
import { IModelDecorationOptions, IModelDeltaDecoration, TrackedRangeStickiness, ITextModel, OverviewRulerLane, IModelDecorationOverviewRulerOptions } from 'vs/editor/common/model';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { RemoveBreakpointAction } from 'vs/workbench/contrib/debug/browser/debugActions';
import { IDebugService, IBreakpoint, CONTEXT_BREAKPOINT_WIDGET_VISIBLE, BreakpointWidgetContext, BREAKPOINT_EDITOR_CONTRIBUTION_ID, IBreakpointEditorContribution, IBreakpointUpdateData, IDebugConfiguration } from 'vs/workbench/contrib/debug/common/debug';
import { IDebugService, IBreakpoint, CONTEXT_BREAKPOINT_WIDGET_VISIBLE, BreakpointWidgetContext, IBreakpointEditorContribution, IBreakpointUpdateData, IDebugConfiguration, State, IDebugSession } from 'vs/workbench/contrib/debug/common/debug';
import { IMarginData } from 'vs/editor/browser/controller/mouseTarget';
import { ContextSubMenu } from 'vs/base/browser/contextmenu';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
@@ -51,7 +50,7 @@ const breakpointHelperDecoration: IModelDecorationOptions = {
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges
};
function createBreakpointDecorations(model: ITextModel, breakpoints: ReadonlyArray<IBreakpoint>, debugService: IDebugService, debugSettings: IDebugConfiguration): { range: Range; options: IModelDecorationOptions; }[] {
export function createBreakpointDecorations(model: ITextModel, breakpoints: ReadonlyArray<IBreakpoint>, state: State, breakpointsActivated: boolean, showBreakpointsInOverviewRuler: boolean): { range: Range; options: IModelDecorationOptions; }[] {
const result: { range: Range; options: IModelDecorationOptions; }[] = [];
breakpoints.forEach((breakpoint) => {
if (breakpoint.lineNumber > model.getLineCount()) {
@@ -64,7 +63,7 @@ function createBreakpointDecorations(model: ITextModel, breakpoints: ReadonlyArr
);
result.push({
options: getBreakpointDecorationOptions(model, breakpoint, debugService, debugSettings),
options: getBreakpointDecorationOptions(model, breakpoint, state, breakpointsActivated, showBreakpointsInOverviewRuler),
range
});
});
@@ -72,8 +71,8 @@ function createBreakpointDecorations(model: ITextModel, breakpoints: ReadonlyArr
return result;
}
function getBreakpointDecorationOptions(model: ITextModel, breakpoint: IBreakpoint, debugService: IDebugService, debugSettings: IDebugConfiguration): IModelDecorationOptions {
const { className, message } = getBreakpointMessageAndClassName(debugService, breakpoint);
function getBreakpointDecorationOptions(model: ITextModel, breakpoint: IBreakpoint, state: State, breakpointsActivated: boolean, showBreakpointsInOverviewRuler: boolean): IModelDecorationOptions {
const { className, message } = getBreakpointMessageAndClassName(state, breakpointsActivated, breakpoint);
let glyphMarginHoverMessage: MarkdownString | undefined;
if (message) {
@@ -85,14 +84,12 @@ function getBreakpointDecorationOptions(model: ITextModel, breakpoint: IBreakpoi
}
}
let overviewRulerDecoration: IModelDecorationOverviewRulerOptions | null;
if (debugSettings.showBreakpointsInOverviewRuler) {
let overviewRulerDecoration: IModelDecorationOverviewRulerOptions | null = null;
if (showBreakpointsInOverviewRuler) {
overviewRulerDecoration = {
color: 'rgb(124, 40, 49)',
position: OverviewRulerLane.Left
};
} else {
overviewRulerDecoration = null;
}
const renderInline = breakpoint.column && (breakpoint.column > model.getLineFirstNonWhitespaceColumn(breakpoint.lineNumber));
@@ -105,11 +102,10 @@ function getBreakpointDecorationOptions(model: ITextModel, breakpoint: IBreakpoi
};
}
async function createCandidateDecorations(model: ITextModel, breakpointDecorations: IBreakpointDecoration[], debugService: IDebugService): Promise<{ range: Range; options: IModelDecorationOptions; breakpoint: IBreakpoint | undefined }[]> {
async function createCandidateDecorations(model: ITextModel, breakpointDecorations: IBreakpointDecoration[], session: IDebugSession): Promise<{ range: Range; options: IModelDecorationOptions; breakpoint: IBreakpoint | undefined }[]> {
const lineNumbers = distinct(breakpointDecorations.map(bpd => bpd.range.startLineNumber));
const result: { range: Range; options: IModelDecorationOptions; breakpoint: IBreakpoint | undefined }[] = [];
const session = debugService.getViewModel().focusedSession;
if (session && session.capabilities.supportsBreakpointLocationsRequest) {
if (session.capabilities.supportsBreakpointLocationsRequest) {
await Promise.all(lineNumbers.map(async lineNumber => {
try {
const positions = await session.breakpointsLocations(model.uri, lineNumber);
@@ -148,7 +144,7 @@ async function createCandidateDecorations(model: ITextModel, breakpointDecoratio
return result;
}
class BreakpointEditorContribution implements IBreakpointEditorContribution {
export class BreakpointEditorContribution implements IBreakpointEditorContribution {
private breakpointHintDecoration: string[] = [];
private breakpointWidget: BreakpointWidget | undefined;
@@ -403,7 +399,7 @@ class BreakpointEditorContribution implements IBreakpointEditorContribution {
const model = activeCodeEditor.getModel();
const breakpoints = this.debugService.getModel().getBreakpoints({ uri: model.uri });
const debugSettings = this.configurationService.getValue<IDebugConfiguration>('debug');
const desiredBreakpointDecorations = createBreakpointDecorations(model, breakpoints, this.debugService, debugSettings);
const desiredBreakpointDecorations = createBreakpointDecorations(model, breakpoints, this.debugService.state, this.debugService.getModel().areBreakpointsActivated(), debugSettings.showBreakpointsInOverviewRuler);
try {
this.ignoreDecorationsChangedEvent = true;
@@ -436,7 +432,8 @@ class BreakpointEditorContribution implements IBreakpointEditorContribution {
}
// Set breakpoint candidate decorations
const desiredCandidateDecorations = debugSettings.showInlineBreakpointCandidates ? await createCandidateDecorations(this.editor.getModel(), this.breakpointDecorations, this.debugService) : [];
const session = this.debugService.getViewModel().focusedSession;
const desiredCandidateDecorations = debugSettings.showInlineBreakpointCandidates && session ? await createCandidateDecorations(this.editor.getModel(), this.breakpointDecorations, session) : [];
const candidateDecorationIds = this.editor.deltaDecorations(this.candidateDecorations.map(c => c.decorationId), desiredCandidateDecorations);
this.candidateDecorations.forEach(candidate => {
candidate.inlineWidget.dispose();
@@ -446,7 +443,7 @@ class BreakpointEditorContribution implements IBreakpointEditorContribution {
// 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, candidate.breakpoint).className : 'codicon-debug-breakpoint-disabled';
const cssClass = candidate.breakpoint ? getBreakpointMessageAndClassName(this.debugService.state, this.debugService.getModel().areBreakpointsActivated(), candidate.breakpoint).className : 'codicon-debug-breakpoint-disabled';
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);
@@ -471,6 +468,7 @@ class BreakpointEditorContribution implements IBreakpointEditorContribution {
const newBreakpointRange = model.getDecorationRange(breakpointDecoration.decorationId);
if (newBreakpointRange && (!breakpointDecoration.range.equalsRange(newBreakpointRange))) {
somethingChanged = true;
breakpointDecoration.range = newBreakpointRange;
}
});
if (!somethingChanged) {
@@ -694,5 +692,3 @@ const debugIconBreakpointDisabledForeground = registerColor('debugIcon.breakpoin
const debugIconBreakpointUnverifiedForeground = registerColor('debugIcon.breakpointUnverifiedForeground', { dark: '#848484', light: '#848484', hc: '#848484' }, nls.localize('debugIcon.breakpointUnverifiedForeground', 'Icon color for unverified breakpoints.'));
const debugIconBreakpointCurrentStackframeForeground = registerColor('debugIcon.breakpointCurrentStackframeForeground', { dark: '#FFCC00', light: '#FFCC00', hc: '#FFCC00' }, nls.localize('debugIcon.breakpointCurrentStackframeForeground', 'Icon color for the current breakpoint stack frame.'));
const debugIconBreakpointStackframeForeground = registerColor('debugIcon.breakpointStackframeForeground', { dark: '#89D185', light: '#89D185', hc: '#89D185' }, nls.localize('debugIcon.breakpointStackframeForeground', 'Icon color for all breakpoint stack frames.'));
registerEditorContribution(BREAKPOINT_EDITOR_CONTRIBUTION_ID, BreakpointEditorContribution);

View File

@@ -15,7 +15,7 @@ import { ZoneWidget } from 'vs/editor/contrib/zoneWidget/zoneWidget';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { IDebugService, IBreakpoint, BreakpointWidgetContext as Context, CONTEXT_BREAKPOINT_WIDGET_VISIBLE, DEBUG_SCHEME, CONTEXT_IN_BREAKPOINT_WIDGET, IBreakpointUpdateData, IBreakpointEditorContribution, BREAKPOINT_EDITOR_CONTRIBUTION_ID } from 'vs/workbench/contrib/debug/common/debug';
import { attachSelectBoxStyler } from 'vs/platform/theme/common/styler';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IThemeService, ITheme } from 'vs/platform/theme/common/themeService';
import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { ServicesAccessor, EditorCommand, registerEditorCommand } from 'vs/editor/browser/editorExtensions';
@@ -46,6 +46,34 @@ export interface IPrivateBreakpointWidgetService {
}
const DECORATION_KEY = 'breakpointwidgetdecoration';
function isCurlyBracketOpen(input: IActiveCodeEditor): boolean {
const model = input.getModel();
const prevBracket = model.findPrevBracket(input.getPosition());
if (prevBracket && prevBracket.isOpen) {
return true;
}
return false;
}
function createDecorations(theme: ITheme, placeHolder: string): IDecorationOptions[] {
const transparentForeground = transparent(editorForeground, 0.4)(theme);
return [{
range: {
startLineNumber: 0,
endLineNumber: 0,
startColumn: 0,
endColumn: 1
},
renderOptions: {
after: {
contentText: placeHolder,
color: transparentForeground ? transparentForeground.toString() : undefined
}
}
}];
}
export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWidgetService {
_serviceBrand: undefined;
@@ -197,7 +225,7 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi
this.toDispose.push(model);
const setDecorations = () => {
const value = this.input.getModel().getValue();
const decorations = !!value ? [] : this.createDecorations();
const decorations = !!value ? [] : createDecorations(this.themeService.getTheme(), this.placeholder);
this.input.setDecorations(DECORATION_KEY, decorations);
};
this.input.getModel().onDidChangeContent(() => setDecorations());
@@ -207,7 +235,7 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi
provideCompletionItems: (model: ITextModel, position: Position, _context: CompletionContext, token: CancellationToken): Promise<CompletionList> => {
let suggestionsPromise: Promise<CompletionList>;
const underlyingModel = this.editor.getModel();
if (underlyingModel && (this.context === Context.CONDITION || this.context === Context.LOG_MESSAGE && this.isCurlyBracketOpen())) {
if (underlyingModel && (this.context === Context.CONDITION || (this.context === Context.LOG_MESSAGE && isCurlyBracketOpen(this.input)))) {
suggestionsPromise = provideSuggestionItems(underlyingModel, new Position(this.lineNumber, 1), new CompletionOptions(undefined, new Set<CompletionItemKind>().add(CompletionItemKind.Snippet)), _context, token).then(suggestions => {
let overwriteBefore = 0;
@@ -260,42 +288,6 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi
}
}
private createDecorations(): IDecorationOptions[] {
const transparentForeground = transparent(editorForeground, 0.4)(this.themeService.getTheme());
return [{
range: {
startLineNumber: 0,
endLineNumber: 0,
startColumn: 0,
endColumn: 1
},
renderOptions: {
after: {
contentText: this.placeholder,
color: transparentForeground ? transparentForeground.toString() : undefined
}
}
}];
}
private isCurlyBracketOpen(): boolean {
const value = this.input.getModel().getValue();
const position = this.input.getPosition();
if (position) {
for (let i = position.column - 2; i >= 0; i--) {
if (value[i] === '{') {
return true;
}
if (value[i] === '}') {
return false;
}
}
}
return false;
}
close(success: boolean): void {
if (success) {
// if there is already a breakpoint on this location - remove it.

View File

@@ -7,7 +7,7 @@ import * as nls from 'vs/nls';
import * as resources from 'vs/base/common/resources';
import * as dom from 'vs/base/browser/dom';
import { IAction, Action } from 'vs/base/common/actions';
import { IDebugService, IBreakpoint, CONTEXT_BREAKPOINTS_FOCUSED, State, DEBUG_SCHEME, IFunctionBreakpoint, IExceptionBreakpoint, IEnablement, BREAKPOINT_EDITOR_CONTRIBUTION_ID, IBreakpointEditorContribution } from 'vs/workbench/contrib/debug/common/debug';
import { IDebugService, IBreakpoint, CONTEXT_BREAKPOINTS_FOCUSED, State, DEBUG_SCHEME, IFunctionBreakpoint, IExceptionBreakpoint, IEnablement, BREAKPOINT_EDITOR_CONTRIBUTION_ID, IBreakpointEditorContribution, IDebugModel, IDataBreakpoint } from 'vs/workbench/contrib/debug/common/debug';
import { ExceptionBreakpoint, FunctionBreakpoint, Breakpoint, DataBreakpoint } from 'vs/workbench/contrib/debug/common/debugModel';
import { AddFunctionBreakpointAction, ToggleBreakpointsActivatedAction, RemoveAllBreakpointsAction, RemoveBreakpointAction, EnableAllBreakpointsAction, DisableAllBreakpointsAction, ReapplyBreakpointsAction } from 'vs/workbench/contrib/debug/browser/debugActions';
import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView';
@@ -45,9 +45,14 @@ function createCheckbox(): HTMLInputElement {
return checkbox;
}
const MAX_VISIBLE_BREAKPOINTS = 9;
export function getExpandedBodySize(model: IDebugModel): number {
const length = model.getBreakpoints().length + model.getExceptionBreakpoints().length + model.getFunctionBreakpoints().length + model.getDataBreakpoints().length;
return Math.min(MAX_VISIBLE_BREAKPOINTS, length) * 22;
}
export class BreakpointsView extends ViewPane {
private static readonly MAX_VISIBLE_FILES = 9;
private list!: WorkbenchList<IEnablement>;
private needsRefresh = false;
@@ -56,16 +61,16 @@ export class BreakpointsView extends ViewPane {
@IContextMenuService contextMenuService: IContextMenuService,
@IDebugService private readonly debugService: IDebugService,
@IKeybindingService keybindingService: IKeybindingService,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IInstantiationService instantiationService: IInstantiationService,
@IThemeService private readonly themeService: IThemeService,
@IEditorService private readonly editorService: IEditorService,
@IContextViewService private readonly contextViewService: IContextViewService,
@IConfigurationService configurationService: IConfigurationService,
@IContextKeyService contextKeyService: IContextKeyService,
) {
super({ ...(options as IViewPaneOptions), ariaHeaderLabel: nls.localize('breakpointsSection', "Breakpoints Section") }, keybindingService, contextMenuService, configurationService, contextKeyService);
super({ ...(options as IViewPaneOptions), ariaHeaderLabel: nls.localize('breakpointsSection', "Breakpoints Section") }, keybindingService, contextMenuService, configurationService, contextKeyService, instantiationService);
this.minimumBodySize = this.maximumBodySize = this.getExpandedBodySize();
this.minimumBodySize = this.maximumBodySize = getExpandedBodySize(this.debugService.getModel());
this._register(this.debugService.getModel().onDidChangeBreakpoints(() => this.onBreakpointsChange()));
}
@@ -215,7 +220,7 @@ export class BreakpointsView extends ViewPane {
private onBreakpointsChange(): void {
if (this.isBodyVisible()) {
this.minimumBodySize = this.getExpandedBodySize();
this.minimumBodySize = getExpandedBodySize(this.debugService.getModel());
if (this.maximumBodySize < Number.POSITIVE_INFINITY) {
this.maximumBodySize = this.minimumBodySize;
}
@@ -234,12 +239,6 @@ export class BreakpointsView extends ViewPane {
return elements;
}
private getExpandedBodySize(): number {
const model = this.debugService.getModel();
const length = model.getBreakpoints().length + model.getExceptionBreakpoints().length + model.getFunctionBreakpoints().length + model.getDataBreakpoints().length;
return Math.min(BreakpointsView.MAX_VISIBLE_FILES, length) * 22;
}
}
class BreakpointsDelegate implements IListVirtualDelegate<IEnablement> {
@@ -351,7 +350,7 @@ 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, breakpoint);
const { message, className } = getBreakpointMessageAndClassName(this.debugService.state, this.debugService.getModel().areBreakpointsActivated(), breakpoint);
data.icon.className = `codicon ${className}`;
data.breakpoint.title = breakpoint.message || message || '';
@@ -443,10 +442,10 @@ class FunctionBreakpointsRenderer implements IListRenderer<FunctionBreakpoint, I
return data;
}
renderElement(functionBreakpoint: FunctionBreakpoint, index: number, data: IBaseBreakpointWithIconTemplateData): void {
renderElement(functionBreakpoint: FunctionBreakpoint, _index: number, data: IBaseBreakpointWithIconTemplateData): void {
data.context = functionBreakpoint;
data.name.textContent = functionBreakpoint.name;
const { className, message } = getBreakpointMessageAndClassName(this.debugService, functionBreakpoint);
const { className, message } = getBreakpointMessageAndClassName(this.debugService.state, this.debugService.getModel().areBreakpointsActivated(), functionBreakpoint);
data.icon.className = `codicon ${className}`;
data.icon.title = message ? message : '';
data.checkbox.checked = functionBreakpoint.enabled;
@@ -498,10 +497,10 @@ class DataBreakpointsRenderer implements IListRenderer<DataBreakpoint, IBaseBrea
return data;
}
renderElement(dataBreakpoint: DataBreakpoint, index: number, data: IBaseBreakpointWithIconTemplateData): void {
renderElement(dataBreakpoint: DataBreakpoint, _index: number, data: IBaseBreakpointWithIconTemplateData): void {
data.context = dataBreakpoint;
data.name.textContent = dataBreakpoint.description;
const { className, message } = getBreakpointMessageAndClassName(this.debugService, dataBreakpoint);
const { className, message } = getBreakpointMessageAndClassName(this.debugService.state, this.debugService.getModel().areBreakpointsActivated(), dataBreakpoint);
data.icon.className = `codicon ${className}`;
data.icon.title = message ? message : '';
data.checkbox.checked = dataBreakpoint.enabled;
@@ -588,10 +587,10 @@ class FunctionBreakpointInputRenderer implements IListRenderer<IFunctionBreakpoi
return template;
}
renderElement(functionBreakpoint: FunctionBreakpoint, index: number, data: IInputTemplateData): void {
renderElement(functionBreakpoint: FunctionBreakpoint, _index: number, data: IInputTemplateData): void {
data.breakpoint = functionBreakpoint;
data.reactedOnEvent = false;
const { className, message } = getBreakpointMessageAndClassName(this.debugService, functionBreakpoint);
const { className, message } = getBreakpointMessageAndClassName(this.debugService.state, this.debugService.getModel().areBreakpointsActivated(), functionBreakpoint);
data.icon.className = `codicon ${className}`;
data.icon.title = message ? message : '';
@@ -638,11 +637,10 @@ export function openBreakpointSource(breakpoint: IBreakpoint, sideBySide: boolea
}, sideBySide ? SIDE_GROUP : ACTIVE_GROUP);
}
export function getBreakpointMessageAndClassName(debugService: IDebugService, breakpoint: IBreakpoint | FunctionBreakpoint | DataBreakpoint): { message?: string, className: string } {
const state = debugService.state;
export function getBreakpointMessageAndClassName(state: State, breakpointsActivated: boolean, breakpoint: IBreakpoint | IFunctionBreakpoint | IDataBreakpoint): { message?: string, className: string } {
const debugActive = state === State.Running || state === State.Stopped;
if (!breakpoint.enabled || !debugService.getModel().areBreakpointsActivated()) {
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',
message: breakpoint.logMessage ? nls.localize('disabledLogpoint', "Disabled Logpoint") : nls.localize('disabledBreakpoint', "Disabled Breakpoint"),
@@ -650,12 +648,12 @@ export function getBreakpointMessageAndClassName(debugService: IDebugService, br
}
const appendMessage = (text: string): string => {
return !(breakpoint instanceof FunctionBreakpoint) && !(breakpoint instanceof DataBreakpoint) && breakpoint.message ? text.concat(', ' + breakpoint.message) : text;
return ('message' in breakpoint && breakpoint.message) ? text.concat(', ' + breakpoint.message) : text;
};
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',
message: breakpoint.message || (breakpoint.logMessage ? nls.localize('unverifiedLogpoint', "Unverified Logpoint") : nls.localize('unverifiedBreakopint', "Unverified Breakpoint")),
message: ('message' in breakpoint && breakpoint.message) ? breakpoint.message : (breakpoint.logMessage ? nls.localize('unverifiedLogpoint', "Unverified Logpoint") : nls.localize('unverifiedBreakopint', "Unverified Breakpoint")),
};
}
@@ -715,6 +713,6 @@ export function getBreakpointMessageAndClassName(debugService: IDebugService, br
return {
className: 'codicon-debug-breakpoint',
message: breakpoint.message || nls.localize('breakpoint', "Breakpoint")
message: ('message' in breakpoint && breakpoint.message) ? breakpoint.message : nls.localize('breakpoint', "Breakpoint")
};
}

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { Constants } from 'vs/base/common/uint';
import { Range } from 'vs/editor/common/core/range';
import { Range, IRange } from 'vs/editor/common/core/range';
import { TrackedRangeStickiness, IModelDeltaDecoration, IModelDecorationOptions } from 'vs/editor/common/model';
import { IDebugService, IStackFrame } from 'vs/workbench/contrib/debug/common/debug';
import { registerThemingParticipant } from 'vs/platform/theme/common/themeService';
@@ -13,11 +13,75 @@ import { localize } from 'vs/nls';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { IEditorContribution } from 'vs/editor/common/editorCommon';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { registerEditorContribution } from 'vs/editor/browser/editorExtensions';
const stickiness = TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges;
class CallStackEditorContribution implements IEditorContribution {
// 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',
stickiness
};
const FOCUSED_STACK_FRAME_MARGIN: IModelDecorationOptions = {
glyphMarginClassName: 'codicon-debug-stackframe-focused',
stickiness
};
const TOP_STACK_FRAME_DECORATION: IModelDecorationOptions = {
isWholeLine: true,
className: 'debug-top-stack-frame-line',
stickiness
};
const TOP_STACK_FRAME_INLINE_DECORATION: IModelDecorationOptions = {
beforeContentClassName: 'debug-top-stack-frame-column'
};
const FOCUSED_STACK_FRAME_DECORATION: IModelDecorationOptions = {
isWholeLine: true,
className: 'debug-focused-stack-frame-line',
stickiness
};
export function createDecorationsForStackFrame(stackFrame: IStackFrame, topStackFrameRange: IRange | undefined): 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 callStack = stackFrame.thread.getCallStack();
if (callStack && callStack.length && stackFrame === callStack[0]) {
result.push({
options: TOP_STACK_FRAME_MARGIN,
range
});
result.push({
options: TOP_STACK_FRAME_DECORATION,
range: columnUntilEOLRange
});
if (topStackFrameRange && topStackFrameRange.startLineNumber === stackFrame.range.startLineNumber && topStackFrameRange.startColumn !== stackFrame.range.startColumn) {
result.push({
options: TOP_STACK_FRAME_INLINE_DECORATION,
range: columnUntilEOLRange
});
}
topStackFrameRange = columnUntilEOLRange;
} else {
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[] = [];
private topStackFrameRange: Range | undefined;
@@ -52,7 +116,7 @@ class CallStackEditorContribution implements IEditorContribution {
}
if (candidateStackFrame && candidateStackFrame.source.uri.toString() === this.editor.getModel()?.uri.toString()) {
decorations.push(...this.createDecorationsForStackFrame(candidateStackFrame));
decorations.push(...createDecorationsForStackFrame(candidateStackFrame, this.topStackFrameRange));
}
}
});
@@ -61,78 +125,6 @@ class CallStackEditorContribution implements IEditorContribution {
return decorations;
}
private createDecorationsForStackFrame(stackFrame: IStackFrame): 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 callStack = stackFrame.thread.getCallStack();
if (callStack && callStack.length && stackFrame === callStack[0]) {
result.push({
options: CallStackEditorContribution.TOP_STACK_FRAME_MARGIN,
range
});
result.push({
options: CallStackEditorContribution.TOP_STACK_FRAME_DECORATION,
range: columnUntilEOLRange
});
if (this.topStackFrameRange && this.topStackFrameRange.startLineNumber === stackFrame.range.startLineNumber && this.topStackFrameRange.startColumn !== stackFrame.range.startColumn) {
result.push({
options: CallStackEditorContribution.TOP_STACK_FRAME_INLINE_DECORATION,
range: columnUntilEOLRange
});
}
this.topStackFrameRange = columnUntilEOLRange;
} else {
result.push({
options: CallStackEditorContribution.FOCUSED_STACK_FRAME_MARGIN,
range
});
result.push({
options: CallStackEditorContribution.FOCUSED_STACK_FRAME_DECORATION,
range: columnUntilEOLRange
});
}
return result;
}
// editor decorations
static readonly 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.
private static TOP_STACK_FRAME_MARGIN: IModelDecorationOptions = {
glyphMarginClassName: 'codicon-debug-stackframe',
stickiness
};
private static FOCUSED_STACK_FRAME_MARGIN: IModelDecorationOptions = {
glyphMarginClassName: 'codicon-debug-stackframe-focused',
stickiness
};
private static TOP_STACK_FRAME_DECORATION: IModelDecorationOptions = {
isWholeLine: true,
className: 'debug-top-stack-frame-line',
stickiness
};
private static TOP_STACK_FRAME_INLINE_DECORATION: IModelDecorationOptions = {
beforeContentClassName: 'debug-top-stack-frame-column'
};
private static FOCUSED_STACK_FRAME_DECORATION: IModelDecorationOptions = {
isWholeLine: true,
className: 'debug-focused-stack-frame-line',
stickiness
};
dispose(): void {
this.editor.deltaDecorations(this.decorationIds, []);
this.toDispose = dispose(this.toDispose);
@@ -154,5 +146,3 @@ registerThemingParticipant((theme, collector) => {
const topStackFrameColor = registerColor('editor.stackFrameHighlightBackground', { dark: '#ffff0033', light: '#ffff6673', hc: '#fff600' }, 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: '#cee7ce' }, localize('focusedStackFrameLineHighlight', 'Background color for the highlight of line at focused stack frame position.'));
registerEditorContribution('editor.contrib.callStack', CallStackEditorContribution);

View File

@@ -40,7 +40,7 @@ const $ = dom.$;
type CallStackItem = IStackFrame | IThread | IDebugSession | string | ThreadAndSessionIds | IStackFrame[];
function getContext(element: CallStackItem | null): any {
export function getContext(element: CallStackItem | null): any {
return element instanceof StackFrame ? {
sessionId: element.thread.session.getId(),
threadId: element.thread.getId(),
@@ -53,6 +53,25 @@ function getContext(element: CallStackItem | null): any {
} : undefined;
}
// Extensions depend on this context, should not be changed even though it is not fully deterministic
export function getContextForContributedActions(element: CallStackItem | null): string | number {
if (element instanceof StackFrame) {
if (element.source.inMemory) {
return element.source.raw.path || element.source.reference || '';
}
return element.source.uri.toString();
}
if (element instanceof Thread) {
return element.threadId;
}
if (isDebugSession(element)) {
return element.getId();
}
return '';
}
export class CallStackView extends ViewPane {
private pauseMessage!: HTMLSpanElement;
private pauseMessageLabel!: HTMLSpanElement;
@@ -72,13 +91,13 @@ export class CallStackView extends ViewPane {
@IContextMenuService contextMenuService: IContextMenuService,
@IDebugService private readonly debugService: IDebugService,
@IKeybindingService keybindingService: IKeybindingService,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IInstantiationService instantiationService: IInstantiationService,
@IEditorService private readonly editorService: IEditorService,
@IConfigurationService configurationService: IConfigurationService,
@IMenuService menuService: IMenuService,
@IContextKeyService readonly contextKeyService: IContextKeyService,
) {
super({ ...(options as IViewPaneOptions), ariaHeaderLabel: nls.localize('callstackSection', "Call Stack Section") }, keybindingService, contextMenuService, configurationService, contextKeyService);
super({ ...(options as IViewPaneOptions), ariaHeaderLabel: nls.localize('callstackSection', "Call Stack Section") }, keybindingService, contextMenuService, configurationService, contextKeyService, instantiationService);
this.callStackItemType = CONTEXT_CALLSTACK_ITEM_TYPE.bindTo(contextKeyService);
this.contributedContextMenu = menuService.createMenu(MenuId.DebugCallStackContext, contextKeyService);
@@ -289,7 +308,13 @@ export class CallStackView extends ViewPane {
this.ignoreSelectionChangedEvent = true;
try {
this.tree.setSelection([element]);
this.tree.reveal(element);
// If the element is outside of the screen bounds,
// position it in the middle
if (this.tree.getRelativeTop(element) === null) {
this.tree.reveal(element, 0.5);
} else {
this.tree.reveal(element);
}
} catch (e) { }
finally {
this.ignoreSelectionChangedEvent = false;
@@ -336,7 +361,7 @@ export class CallStackView extends ViewPane {
}
const actions: IAction[] = [];
const actionsDisposable = createAndFillInContextMenuActions(this.contributedContextMenu, { arg: this.getContextForContributedActions(element), shouldForwardArgs: true }, actions, this.contextMenuService);
const actionsDisposable = createAndFillInContextMenuActions(this.contributedContextMenu, { arg: getContextForContributedActions(element), shouldForwardArgs: true }, actions, this.contextMenuService);
this.contextMenuService.showContextMenu({
getAnchor: () => e.anchor,
@@ -345,24 +370,6 @@ export class CallStackView extends ViewPane {
onHide: () => dispose(actionsDisposable)
});
}
private getContextForContributedActions(element: CallStackItem | null): string | number {
if (element instanceof StackFrame) {
if (element.source.inMemory) {
return element.source.raw.path || element.source.reference || '';
}
return element.source.uri.toString();
}
if (element instanceof Thread) {
return element.threadId;
}
if (isDebugSession(element)) {
return element.getId();
}
return '';
}
}
interface IThreadTemplateData {
@@ -514,7 +521,7 @@ class StackFramesRenderer implements ITreeRenderer<IStackFrame, FuzzyScore, ISta
dom.toggleClass(data.stackFrame, 'disabled', !stackFrame.source || !stackFrame.source.available || isDeemphasized(stackFrame));
dom.toggleClass(data.stackFrame, 'label', stackFrame.presentationHint === 'label');
dom.toggleClass(data.stackFrame, 'subtle', stackFrame.presentationHint === 'subtle');
const hasActions = stackFrame.thread.session.capabilities.supportsRestartFrame && stackFrame.presentationHint !== 'label' && stackFrame.presentationHint !== 'subtle';
const hasActions = !!stackFrame.thread.session.capabilities.supportsRestartFrame && stackFrame.presentationHint !== 'label' && stackFrame.presentationHint !== 'subtle';
dom.toggleClass(data.stackFrame, 'has-actions', hasActions);
data.file.title = stackFrame.source.inMemory ? stackFrame.source.uri.path : this.labelService.getUriLabel(stackFrame.source.uri);

View File

@@ -14,16 +14,14 @@ import { IKeybindings } from 'vs/platform/keybinding/common/keybindingsRegistry'
import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry';
import { IWorkbenchActionRegistry, Extensions as WorkbenchActionRegistryExtensions } from 'vs/workbench/common/actions';
import { ShowViewletAction } from 'vs/workbench/browser/viewlet';
import { TogglePanelAction, Extensions as PanelExtensions, PanelRegistry, PanelDescriptor } from 'vs/workbench/browser/panel';
import { BreakpointsView } from 'vs/workbench/contrib/debug/browser/breakpointsView';
import { CallStackView } from 'vs/workbench/contrib/debug/browser/callStackView';
import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions';
import {
IDebugService, VIEWLET_ID, REPL_ID, CONTEXT_IN_DEBUG_MODE, INTERNAL_CONSOLE_OPTIONS_SCHEMA,
CONTEXT_DEBUG_STATE, VARIABLES_VIEW_ID, CALLSTACK_VIEW_ID, WATCH_VIEW_ID, BREAKPOINTS_VIEW_ID, LOADED_SCRIPTS_VIEW_ID, CONTEXT_LOADED_SCRIPTS_SUPPORTED, CONTEXT_FOCUSED_SESSION_IS_ATTACH, CONTEXT_STEP_BACK_SUPPORTED, CONTEXT_CALLSTACK_ITEM_TYPE, CONTEXT_RESTART_FRAME_SUPPORTED, CONTEXT_JUMP_TO_CURSOR_SUPPORTED, CONTEXT_DEBUG_UX,
IDebugService, VIEWLET_ID, DEBUG_PANEL_ID, CONTEXT_IN_DEBUG_MODE, INTERNAL_CONSOLE_OPTIONS_SCHEMA,
CONTEXT_DEBUG_STATE, VARIABLES_VIEW_ID, CALLSTACK_VIEW_ID, WATCH_VIEW_ID, BREAKPOINTS_VIEW_ID, LOADED_SCRIPTS_VIEW_ID, CONTEXT_LOADED_SCRIPTS_SUPPORTED, CONTEXT_FOCUSED_SESSION_IS_ATTACH, CONTEXT_STEP_BACK_SUPPORTED, CONTEXT_CALLSTACK_ITEM_TYPE, CONTEXT_RESTART_FRAME_SUPPORTED, CONTEXT_JUMP_TO_CURSOR_SUPPORTED, CONTEXT_DEBUG_UX, BREAKPOINT_EDITOR_CONTRIBUTION_ID, REPL_VIEW_ID,
} from 'vs/workbench/contrib/debug/common/debug';
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
import { StartAction, AddFunctionBreakpointAction, ConfigureAction, DisableAllBreakpointsAction, EnableAllBreakpointsAction, RemoveAllBreakpointsAction, RunAction, ReapplyBreakpointsAction, SelectAndStartAction } from 'vs/workbench/contrib/debug/browser/debugActions';
import { DebugToolBar } from 'vs/workbench/contrib/debug/browser/debugToolBar';
import * as service from 'vs/workbench/contrib/debug/browser/debugService';
@@ -31,7 +29,7 @@ import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
import { registerCommands, ADD_CONFIGURATION_ID, TOGGLE_INLINE_BREAKPOINT_ID, COPY_STACK_TRACE_ID, REVERSE_CONTINUE_ID, STEP_BACK_ID, RESTART_SESSION_ID, TERMINATE_THREAD_ID, STEP_OVER_ID, STEP_INTO_ID, STEP_OUT_ID, PAUSE_ID, DISCONNECT_ID, STOP_ID, RESTART_FRAME_ID, CONTINUE_ID, FOCUS_REPL_ID, JUMP_TO_CURSOR_ID, RESTART_LABEL, STEP_INTO_LABEL, STEP_OVER_LABEL, STEP_OUT_LABEL, PAUSE_LABEL, DISCONNECT_LABEL, STOP_LABEL, CONTINUE_LABEL } from 'vs/workbench/contrib/debug/browser/debugCommands';
import { IQuickOpenRegistry, Extensions as QuickOpenExtensions, QuickOpenHandlerDescriptor } from 'vs/workbench/browser/quickopen';
import { StatusBarColorProvider } from 'vs/workbench/contrib/debug/browser/statusbarColorProvider';
import { IViewsRegistry, Extensions as ViewExtensions, IViewContainersRegistry, ViewContainerLocation } from 'vs/workbench/common/views';
import { IViewsRegistry, Extensions as ViewExtensions, IViewContainersRegistry, ViewContainerLocation, ViewContainer } from 'vs/workbench/common/views';
import { isMacintosh } from 'vs/base/common/platform';
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { URI } from 'vs/base/common/uri';
@@ -48,7 +46,12 @@ import { ClearReplAction, Repl } from 'vs/workbench/contrib/debug/browser/repl';
import { DebugContentProvider } from 'vs/workbench/contrib/debug/common/debugContentProvider';
import { StartView } from 'vs/workbench/contrib/debug/browser/startView';
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
import { DebugViewPaneContainer } from 'vs/workbench/contrib/debug/browser/debugViewlet';
import { DebugViewPaneContainer, OpenDebugPanelAction } from 'vs/workbench/contrib/debug/browser/debugViewlet';
import { registerEditorContribution } from 'vs/editor/browser/editorExtensions';
import { CallStackEditorContribution } from 'vs/workbench/contrib/debug/browser/callStackEditorContribution';
import { BreakpointEditorContribution } from 'vs/workbench/contrib/debug/browser/breakpointEditorContribution';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer';
class OpenDebugViewletAction extends ShowViewletAction {
public static readonly ID = VIEWLET_ID;
@@ -65,24 +68,10 @@ class OpenDebugViewletAction extends ShowViewletAction {
}
}
class OpenDebugPanelAction extends TogglePanelAction {
public static readonly ID = 'workbench.debug.action.toggleRepl';
public static readonly LABEL = nls.localize('toggleDebugPanel', "Debug Console");
constructor(
id: string,
label: string,
@IPanelService panelService: IPanelService,
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService
) {
super(id, label, REPL_ID, panelService, layoutService);
}
}
const viewContainer = Registry.as<IViewContainersRegistry>(ViewExtensions.ViewContainersRegistry).registerViewContainer({
id: VIEWLET_ID,
name: nls.localize('debugAndRun', "Debug and Run"),
ctorDescriptor: { ctor: DebugViewPaneContainer },
name: nls.localize('runAndDebug', "Run and Debug"),
ctorDescriptor: new SyncDescriptor(DebugViewPaneContainer),
icon: 'codicon-debug-alt',
order: 13 // {{SQL CARBON EDIT}}
}, ViewContainerLocation.Sidebar);
@@ -95,23 +84,32 @@ const openPanelKb: IKeybindings = {
};
// register repl panel
Registry.as<PanelRegistry>(PanelExtensions.Panels).registerPanel(PanelDescriptor.create(
Repl,
REPL_ID,
nls.localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'debugPanel' }, 'Debug Console'),
'repl',
30,
OpenDebugPanelAction.ID
));
const VIEW_CONTAINER: ViewContainer = Registry.as<IViewContainersRegistry>(ViewExtensions.ViewContainersRegistry).registerViewContainer({
id: DEBUG_PANEL_ID,
name: nls.localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'debugPanel' }, 'Debug Console'),
ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [DEBUG_PANEL_ID, DEBUG_PANEL_ID, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }]),
focusCommand: {
id: OpenDebugPanelAction.ID,
keybindings: openPanelKb
}
}, ViewContainerLocation.Panel);
Registry.as<IViewsRegistry>(ViewExtensions.ViewsRegistry).registerViews([{
id: REPL_VIEW_ID,
name: nls.localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'debugPanel' }, 'Debug Console'),
canToggleVisibility: false,
ctorDescriptor: new SyncDescriptor(Repl),
}], VIEW_CONTAINER);
// Register default debug views
const viewsRegistry = Registry.as<IViewsRegistry>(ViewExtensions.ViewsRegistry);
viewsRegistry.registerViews([{ id: VARIABLES_VIEW_ID, name: nls.localize('variables', "Variables"), ctorDescriptor: { ctor: VariablesView }, order: 10, weight: 40, canToggleVisibility: true, focusCommand: { id: 'workbench.debug.action.focusVariablesView' }, when: CONTEXT_DEBUG_UX.isEqualTo('default') }], viewContainer);
viewsRegistry.registerViews([{ id: WATCH_VIEW_ID, name: nls.localize('watch', "Watch"), ctorDescriptor: { ctor: WatchExpressionsView }, order: 20, weight: 10, canToggleVisibility: true, focusCommand: { id: 'workbench.debug.action.focusWatchView' }, when: CONTEXT_DEBUG_UX.isEqualTo('default') }], viewContainer);
viewsRegistry.registerViews([{ id: CALLSTACK_VIEW_ID, name: nls.localize('callStack', "Call Stack"), ctorDescriptor: { ctor: CallStackView }, order: 30, weight: 30, canToggleVisibility: true, focusCommand: { id: 'workbench.debug.action.focusCallStackView' }, when: CONTEXT_DEBUG_UX.isEqualTo('default') }], viewContainer);
viewsRegistry.registerViews([{ id: BREAKPOINTS_VIEW_ID, name: nls.localize('breakpoints', "Breakpoints"), ctorDescriptor: { ctor: BreakpointsView }, order: 40, weight: 20, canToggleVisibility: true, focusCommand: { id: 'workbench.debug.action.focusBreakpointsView' }, when: CONTEXT_DEBUG_UX.isEqualTo('default') }], viewContainer);
viewsRegistry.registerViews([{ id: StartView.ID, name: StartView.LABEL, ctorDescriptor: { ctor: StartView }, order: 10, weight: 40, canToggleVisibility: true, when: CONTEXT_DEBUG_UX.isEqualTo('simple') }], viewContainer);
viewsRegistry.registerViews([{ id: LOADED_SCRIPTS_VIEW_ID, name: nls.localize('loadedScripts', "Loaded Scripts"), ctorDescriptor: { ctor: LoadedScriptsView }, order: 35, weight: 5, canToggleVisibility: true, collapsed: true, when: ContextKeyExpr.and(CONTEXT_LOADED_SCRIPTS_SUPPORTED, CONTEXT_DEBUG_UX.isEqualTo('default')) }], viewContainer);
viewsRegistry.registerViews([{ id: VARIABLES_VIEW_ID, name: nls.localize('variables', "Variables"), ctorDescriptor: new SyncDescriptor(VariablesView), order: 10, weight: 40, canToggleVisibility: true, focusCommand: { id: 'workbench.debug.action.focusVariablesView' }, when: CONTEXT_DEBUG_UX.isEqualTo('default') }], viewContainer);
viewsRegistry.registerViews([{ id: WATCH_VIEW_ID, name: nls.localize('watch', "Watch"), ctorDescriptor: new SyncDescriptor(WatchExpressionsView), order: 20, weight: 10, canToggleVisibility: true, focusCommand: { id: 'workbench.debug.action.focusWatchView' }, when: CONTEXT_DEBUG_UX.isEqualTo('default') }], viewContainer);
viewsRegistry.registerViews([{ id: CALLSTACK_VIEW_ID, name: nls.localize('callStack', "Call Stack"), ctorDescriptor: new SyncDescriptor(CallStackView), order: 30, weight: 30, canToggleVisibility: true, focusCommand: { id: 'workbench.debug.action.focusCallStackView' }, when: CONTEXT_DEBUG_UX.isEqualTo('default') }], viewContainer);
viewsRegistry.registerViews([{ id: BREAKPOINTS_VIEW_ID, name: nls.localize('breakpoints', "Breakpoints"), ctorDescriptor: new SyncDescriptor(BreakpointsView), order: 40, weight: 20, canToggleVisibility: true, focusCommand: { id: 'workbench.debug.action.focusBreakpointsView' }, when: CONTEXT_DEBUG_UX.isEqualTo('default') }], viewContainer);
viewsRegistry.registerViews([{ id: StartView.ID, name: StartView.LABEL, ctorDescriptor: new SyncDescriptor(StartView), order: 10, weight: 40, canToggleVisibility: true, when: CONTEXT_DEBUG_UX.isEqualTo('simple') }], viewContainer);
viewsRegistry.registerViews([{ id: LOADED_SCRIPTS_VIEW_ID, name: nls.localize('loadedScripts', "Loaded Scripts"), ctorDescriptor: new SyncDescriptor(LoadedScriptsView), order: 35, weight: 5, canToggleVisibility: true, collapsed: true, when: ContextKeyExpr.and(CONTEXT_LOADED_SCRIPTS_SUPPORTED, CONTEXT_DEBUG_UX.isEqualTo('default')) }], viewContainer);
registerCommands();
@@ -212,6 +210,11 @@ configurationRegistry.registerConfiguration({
default: 'onFirstSessionStart'
},
'debug.internalConsoleOptions': INTERNAL_CONSOLE_OPTIONS_SCHEMA,
'debug.console.closeOnEnd': {
type: 'boolean',
description: nls.localize('debug.console.closeOnEnd', "Controls if the debug console should be automatically closed when the debug session ends."),
default: false
},
'debug.openDebug': {
enum: ['neverOpen', 'openOnSessionStart', 'openOnFirstSessionStart', 'openOnDebugBreak'],
default: 'openOnSessionStart',
@@ -331,6 +334,11 @@ registerDebugCallstackItem(TERMINATE_THREAD_ID, nls.localize('terminateThread',
registerDebugCallstackItem(RESTART_FRAME_ID, nls.localize('restartFrame', "Restart Frame"), 10, ContextKeyExpr.and(CONTEXT_CALLSTACK_ITEM_TYPE.isEqualTo('stackFrame'), CONTEXT_RESTART_FRAME_SUPPORTED));
registerDebugCallstackItem(COPY_STACK_TRACE_ID, nls.localize('copyStackTrace', "Copy Call Stack"), 20, CONTEXT_CALLSTACK_ITEM_TYPE.isEqualTo('stackFrame'));
// Editor contributions
registerEditorContribution('editor.contrib.callStack', CallStackEditorContribution);
registerEditorContribution(BREAKPOINT_EDITOR_CONTRIBUTION_ID, BreakpointEditorContribution);
// View menu
// {{SQL CARBON EDIT}} - Disable unused menu item

View File

@@ -161,9 +161,6 @@ export class StartDebugActionViewItem implements IActionViewItem {
let lastGroup: string | undefined;
const disabledIdxs: number[] = [];
manager.getAllConfigurations().forEach(({ launch, name, presentation }) => {
if (name === manager.selectedConfiguration.name && launch === manager.selectedConfiguration.launch) {
this.selected = this.options.length;
}
if (lastGroup !== presentation?.group) {
lastGroup = presentation?.group;
if (this.options.length) {
@@ -171,6 +168,9 @@ export class StartDebugActionViewItem implements IActionViewItem {
disabledIdxs.push(this.options.length - 1);
}
}
if (name === manager.selectedConfiguration.name && launch === manager.selectedConfiguration.launch) {
this.selected = this.options.length;
}
const label = inWorkspace ? `${name} (${launch.name})` : name;
this.options.push({ label, handler: () => { manager.selectConfiguration(launch, name); return true; } });

View File

@@ -9,7 +9,7 @@ import { List } from 'vs/base/browser/ui/list/listWidget';
import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { IListService } from 'vs/platform/list/browser/listService';
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { IDebugService, IEnablement, CONTEXT_BREAKPOINTS_FOCUSED, CONTEXT_WATCH_EXPRESSIONS_FOCUSED, CONTEXT_VARIABLES_FOCUSED, EDITOR_CONTRIBUTION_ID, IDebugEditorContribution, CONTEXT_IN_DEBUG_MODE, CONTEXT_EXPRESSION_SELECTED, CONTEXT_BREAKPOINT_SELECTED, IConfig, IStackFrame, IThread, IDebugSession, CONTEXT_DEBUG_STATE, REPL_ID, IDebugConfiguration, CONTEXT_JUMP_TO_CURSOR_SUPPORTED } from 'vs/workbench/contrib/debug/common/debug';
import { IDebugService, IEnablement, CONTEXT_BREAKPOINTS_FOCUSED, CONTEXT_WATCH_EXPRESSIONS_FOCUSED, CONTEXT_VARIABLES_FOCUSED, EDITOR_CONTRIBUTION_ID, IDebugEditorContribution, CONTEXT_IN_DEBUG_MODE, CONTEXT_EXPRESSION_SELECTED, CONTEXT_BREAKPOINT_SELECTED, IConfig, IStackFrame, IThread, IDebugSession, CONTEXT_DEBUG_STATE, IDebugConfiguration, CONTEXT_JUMP_TO_CURSOR_SUPPORTED, REPL_VIEW_ID } from 'vs/workbench/contrib/debug/common/debug';
import { Expression, Variable, Breakpoint, FunctionBreakpoint, DataBreakpoint } from 'vs/workbench/contrib/debug/common/debugModel';
import { IExtensionsViewPaneContainer, VIEWLET_ID as EXTENSIONS_VIEWLET_ID } from 'vs/workbench/contrib/extensions/common/extensions';
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
@@ -27,9 +27,9 @@ import { CommandsRegistry } from 'vs/platform/commands/common/commands';
import { onUnexpectedError } from 'vs/base/common/errors';
import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
import { IViewsService } from 'vs/workbench/common/views';
export const ADD_CONFIGURATION_ID = 'debug.addConfiguration';
export const TOGGLE_INLINE_BREAKPOINT_ID = 'editor.debug.action.toggleInlineBreakpoint';
@@ -239,7 +239,8 @@ export function registerCommands(): void {
id: STEP_INTO_ID,
weight: KeybindingWeight.WorkbenchContrib + 10, // Have a stronger weight to have priority over full screen when debugging
primary: KeyCode.F11,
when: CONTEXT_DEBUG_STATE.isEqualTo('stopped'),
// Use a more flexible when clause to not allow full screen command to take over when F11 pressed a lot of times
when: CONTEXT_DEBUG_STATE.notEqualsTo('inactive'),
handler: (accessor: ServicesAccessor, _: string, context: CallStackContext | unknown) => {
getThreadAndRun(accessor, context, (thread: IThread) => thread.stepIn());
}
@@ -303,9 +304,14 @@ export function registerCommands(): void {
id: RESTART_FRAME_ID,
handler: async (accessor: ServicesAccessor, _: string, context: CallStackContext | unknown) => {
const debugService = accessor.get(IDebugService);
const notificationService = accessor.get(INotificationService);
let frame = getFrame(debugService, context);
if (frame) {
await frame.restart();
try {
await frame.restart();
} catch (e) {
notificationService.error(e);
}
}
}
});
@@ -322,9 +328,9 @@ export function registerCommands(): void {
CommandsRegistry.registerCommand({
id: FOCUS_REPL_ID,
handler: (accessor) => {
const panelService = accessor.get(IPanelService);
panelService.openPanel(REPL_ID, true);
handler: async (accessor) => {
const viewsService = accessor.get(IViewsService);
await viewsService.openView(REPL_VIEW_ID, true);
}
});

View File

@@ -38,6 +38,7 @@ import { withUndefinedAsNull } from 'vs/base/common/types';
import { sequence } from 'vs/base/common/async';
import { IHistoryService } from 'vs/workbench/services/history/common/history';
import { first } from 'vs/base/common/arrays';
import { getVisibleAndSorted } from 'vs/workbench/contrib/debug/common/debugUtils';
const jsonRegistry = Registry.as<IJSONContributionRegistry>(JSONExtensions.JSONContribution);
jsonRegistry.registerSchema(launchSchemaId, launchSchema);
@@ -207,6 +208,22 @@ export class ConfigurationManager implements IConfigurationManager {
return result;
}
async resolveDebugConfigurationWithSubstitutedVariables(folderUri: uri | undefined, type: string | undefined, config: IConfig, token: CancellationToken): Promise<IConfig | null | undefined> {
// pipe the config through the promises sequentially. Append at the end the '*' types
const providers = this.configProviders.filter(p => p.type === type && p.resolveDebugConfigurationWithSubstitutedVariables)
.concat(this.configProviders.filter(p => p.type === '*' && p.resolveDebugConfigurationWithSubstitutedVariables));
let result: IConfig | null | undefined = config;
await sequence(providers.map(provider => async () => {
// If any provider returned undefined or null make sure to respect that and do not pass the result to more resolver
if (result) {
result = await provider.resolveDebugConfigurationWithSubstitutedVariables!(folderUri, result, token);
}
}));
return result;
}
async provideDebugConfigurations(folderUri: uri | undefined, type: string, token: CancellationToken): Promise<any[]> {
await this.activateDebuggers('onDebugInitialConfigurations');
const results = await Promise.all(this.configProviders.filter(p => p.type === type && p.provideDebugConfigurations).map(p => p.provideDebugConfigurations!(folderUri, token)));
@@ -219,36 +236,13 @@ export class ConfigurationManager implements IConfigurationManager {
for (let l of this.launches) {
for (let name of l.getConfigurationNames()) {
const config = l.getConfiguration(name) || l.getCompound(name);
if (config && !config.presentation?.hidden) {
if (config) {
all.push({ launch: l, name, presentation: config.presentation });
}
}
}
return all.sort((first, second) => {
if (!first.presentation) {
return 1;
}
if (!second.presentation) {
return -1;
}
if (!first.presentation.group) {
return 1;
}
if (!second.presentation.group) {
return -1;
}
if (first.presentation.group !== second.presentation.group) {
return first.presentation.group.localeCompare(second.presentation.group);
}
if (typeof first.presentation.order !== 'number') {
return 1;
}
if (typeof second.presentation.order !== 'number') {
return -1;
}
return first.presentation.order - second.presentation.order;
});
return getVisibleAndSorted(all);
}
private registerListeners(): void {

View File

@@ -9,14 +9,14 @@ import { Range } from 'vs/editor/common/core/range';
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
import { ServicesAccessor, registerEditorAction, EditorAction, IActionOptions } from 'vs/editor/browser/editorExtensions';
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { IDebugService, CONTEXT_IN_DEBUG_MODE, CONTEXT_DEBUG_STATE, State, REPL_ID, VIEWLET_ID, IDebugEditorContribution, EDITOR_CONTRIBUTION_ID, BreakpointWidgetContext, IBreakpoint, BREAKPOINT_EDITOR_CONTRIBUTION_ID, IBreakpointEditorContribution } from 'vs/workbench/contrib/debug/common/debug';
import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
import { IDebugService, CONTEXT_IN_DEBUG_MODE, CONTEXT_DEBUG_STATE, State, VIEWLET_ID, IDebugEditorContribution, EDITOR_CONTRIBUTION_ID, BreakpointWidgetContext, IBreakpoint, BREAKPOINT_EDITOR_CONTRIBUTION_ID, IBreakpointEditorContribution, REPL_VIEW_ID } from 'vs/workbench/contrib/debug/common/debug';
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { openBreakpointSource } from 'vs/workbench/contrib/debug/browser/breakpointsView';
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { PanelFocusContext } from 'vs/workbench/common/panel';
import { IViewsService } from 'vs/workbench/common/views';
export const TOGGLE_BREAKPOINT_ID = 'editor.debug.action.toggleBreakpoint';
class ToggleBreakpointAction extends EditorAction {
@@ -169,7 +169,7 @@ class SelectionToReplAction extends EditorAction {
async run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> {
const debugService = accessor.get(IDebugService);
const panelService = accessor.get(IPanelService);
const viewsService = accessor.get(IViewsService);
const viewModel = debugService.getViewModel();
const session = viewModel.focusedSession;
if (!editor.hasModel() || !session) {
@@ -178,7 +178,7 @@ class SelectionToReplAction extends EditorAction {
const text = editor.getModel().getValueInRange(editor.getSelection());
await session.addReplExpression(viewModel.focusedStackFrame!, text);
await panelService.openPanel(REPL_ID, true);
await viewsService.openView(REPL_VIEW_ID, true);
}
}

View File

@@ -27,7 +27,7 @@ import { FloatingClickWidget } from 'vs/workbench/browser/parts/editor/editorWid
import { Position } from 'vs/editor/common/core/position';
import { CoreEditingCommands } from 'vs/editor/browser/controller/coreCommands';
import { first } from 'vs/base/common/arrays';
import { memoize } from 'vs/base/common/decorators';
import { memoize, createMemoizer } from 'vs/base/common/decorators';
import { IEditorHoverOptions, EditorOption } from 'vs/editor/common/config/editorOptions';
import { CancellationToken } from 'vs/base/common/cancellation';
import { DebugHoverWidget } from 'vs/workbench/contrib/debug/browser/debugHover';
@@ -36,12 +36,131 @@ import { getHover } from 'vs/editor/contrib/hover/getHover';
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
const HOVER_DELAY = 300;
const LAUNCH_JSON_REGEX = /launch\.json$/;
const LAUNCH_JSON_REGEX = /\.vscode\/launch\.json$/;
const INLINE_VALUE_DECORATION_KEY = 'inlinevaluedecoration';
const MAX_NUM_INLINE_VALUES = 100; // JS Global scope can have 700+ entries. We want to limit ourselves for perf reasons
const MAX_INLINE_DECORATOR_LENGTH = 150; // Max string length of each inline decorator when debugging. If exceeded ... is added
const MAX_TOKENIZATION_LINE_LEN = 500; // If line is too long, then inline values for the line are skipped
function createInlineValueDecoration(lineNumber: number, contentText: string): IDecorationOptions {
// If decoratorText is too long, trim and add ellipses. This could happen for minified files with everything on a single line
if (contentText.length > MAX_INLINE_DECORATOR_LENGTH) {
contentText = contentText.substr(0, MAX_INLINE_DECORATOR_LENGTH) + '...';
}
return {
range: {
startLineNumber: lineNumber,
endLineNumber: lineNumber,
startColumn: Constants.MAX_SAFE_SMALL_INTEGER,
endColumn: Constants.MAX_SAFE_SMALL_INTEGER
},
renderOptions: {
after: {
contentText,
backgroundColor: 'rgba(255, 200, 0, 0.2)',
margin: '10px'
},
dark: {
after: {
color: 'rgba(255, 255, 255, 0.5)',
}
},
light: {
after: {
color: 'rgba(0, 0, 0, 0.5)',
}
}
}
};
}
function createInlineValueDecorationsInsideRange(expressions: ReadonlyArray<IExpression>, range: Range, model: ITextModel, wordToLineNumbersMap: Map<string, number[]>): IDecorationOptions[] {
const nameValueMap = new Map<string, string>();
for (let expr of expressions) {
nameValueMap.set(expr.name, expr.value);
// Limit the size of map. Too large can have a perf impact
if (nameValueMap.size >= MAX_NUM_INLINE_VALUES) {
break;
}
}
const lineToNamesMap: Map<number, string[]> = new Map<number, string[]>();
// Compute unique set of names on each line
nameValueMap.forEach((_value, name) => {
const lineNumbers = wordToLineNumbersMap.get(name);
if (lineNumbers) {
for (let lineNumber of lineNumbers) {
if (range.containsPosition(new Position(lineNumber, 0))) {
if (!lineToNamesMap.has(lineNumber)) {
lineToNamesMap.set(lineNumber, []);
}
if (lineToNamesMap.get(lineNumber)!.indexOf(name) === -1) {
lineToNamesMap.get(lineNumber)!.push(name);
}
}
}
}
});
const decorations: IDecorationOptions[] = [];
// Compute decorators for each line
lineToNamesMap.forEach((names, line) => {
const contentText = names.sort((first, second) => {
const content = model.getLineContent(line);
return content.indexOf(first) - content.indexOf(second);
}).map(name => `${name} = ${nameValueMap.get(name)}`).join(', ');
decorations.push(createInlineValueDecoration(line, contentText));
});
return decorations;
}
function getWordToLineNumbersMap(model: ITextModel | null): Map<string, number[]> {
const result = new Map<string, number[]>();
if (!model) {
return result;
}
// For every word in every line, map its ranges for fast lookup
for (let lineNumber = 1, len = model.getLineCount(); lineNumber <= len; ++lineNumber) {
const lineContent = model.getLineContent(lineNumber);
// If line is too long then skip the line
if (lineContent.length > MAX_TOKENIZATION_LINE_LEN) {
continue;
}
model.forceTokenization(lineNumber);
const lineTokens = model.getLineTokens(lineNumber);
for (let tokenIndex = 0, tokenCount = lineTokens.getCount(); tokenIndex < tokenCount; tokenIndex++) {
const tokenStartOffset = lineTokens.getStartOffset(tokenIndex);
const tokenEndOffset = lineTokens.getEndOffset(tokenIndex);
const tokenType = lineTokens.getStandardTokenType(tokenIndex);
const tokenStr = lineContent.substring(tokenStartOffset, tokenEndOffset);
// Token is a word and not a comment
if (tokenType === StandardTokenType.Other) {
DEFAULT_WORD_REGEXP.lastIndex = 0; // We assume tokens will usually map 1:1 to words if they match
const wordMatch = DEFAULT_WORD_REGEXP.exec(tokenStr);
if (wordMatch) {
const word = wordMatch[0];
if (!result.has(word)) {
result.set(word, []);
}
result.get(word)!.push(lineNumber);
}
}
}
}
return result;
}
class DebugEditorContribution implements IDebugEditorContribution {
private toDispose: IDisposable[];
@@ -49,8 +168,7 @@ class DebugEditorContribution implements IDebugEditorContribution {
private nonDebugHoverPosition: Position | undefined;
private hoverRange: Range | null = null;
private mouseDown = false;
private wordToLineNumbersMap: Map<string, Position[]> | undefined;
private static readonly MEMOIZER = createMemoizer();
private exceptionWidget: ExceptionWidget | undefined;
@@ -95,7 +213,7 @@ class DebugEditorContribution implements IDebugEditorContribution {
}));
this.toDispose.push(this.editor.onKeyDown((e: IKeyboardEvent) => this.onKeyDown(e)));
this.toDispose.push(this.editor.onDidChangeModelContent(() => {
this.wordToLineNumbersMap = undefined;
DebugEditorContribution.MEMOIZER.clear();
this.updateInlineValuesScheduler.schedule();
}));
this.toDispose.push(this.editor.onDidChangeModel(async () => {
@@ -107,7 +225,7 @@ class DebugEditorContribution implements IDebugEditorContribution {
this.toggleExceptionWidget();
this.hideHoverWidget();
this.updateConfigurationWidgetVisibility();
this.wordToLineNumbersMap = undefined;
DebugEditorContribution.MEMOIZER.clear();
await this.updateInlineValueDecorations(stackFrame);
}));
this.toDispose.push(this.editor.onDidScrollChange(() => this.hideHoverWidget));
@@ -118,6 +236,11 @@ class DebugEditorContribution implements IDebugEditorContribution {
}));
}
@DebugEditorContribution.MEMOIZER
private get wordToLineNumbersMap(): Map<string, number[]> {
return getWordToLineNumbersMap(this.editor.getModel());
}
private _applyHoverConfiguration(model: ITextModel, stackFrame: IStackFrame | undefined): void {
if (stackFrame && model.uri.toString() === stackFrame.source.uri.toString()) {
this.editor.updateOptions({
@@ -237,6 +360,7 @@ class DebugEditorContribution implements IDebugEditorContribution {
if (targetType === MouseTargetType.CONTENT_TEXT) {
if (mouseEvent.target.range && !mouseEvent.target.range.equalsRange(this.hoverRange)) {
this.hoverRange = mouseEvent.target.range;
this.hideHoverScheduler.cancel();
this.showHoverScheduler.schedule();
}
} else if (!this.mouseDown) {
@@ -400,136 +524,14 @@ class DebugEditorContribution implements IDebugEditorContribution {
range = range.setStartPosition(scope.range.startLineNumber, scope.range.startColumn);
}
return this.createInlineValueDecorationsInsideRange(children, range, model);
return createInlineValueDecorationsInsideRange(children, range, model, this.wordToLineNumbersMap);
}));
const allDecorations = decorationsPerScope.reduce((previous, current) => previous.concat(current), []);
this.editor.setDecorations(INLINE_VALUE_DECORATION_KEY, allDecorations);
}
private createInlineValueDecorationsInsideRange(expressions: ReadonlyArray<IExpression>, range: Range, model: ITextModel): IDecorationOptions[] {
const nameValueMap = new Map<string, string>();
for (let expr of expressions) {
nameValueMap.set(expr.name, expr.value);
// Limit the size of map. Too large can have a perf impact
if (nameValueMap.size >= MAX_NUM_INLINE_VALUES) {
break;
}
}
const lineToNamesMap: Map<number, string[]> = new Map<number, string[]>();
const wordToPositionsMap = this.getWordToPositionsMap();
// Compute unique set of names on each line
nameValueMap.forEach((value, name) => {
const positions = wordToPositionsMap.get(name);
if (positions) {
for (let position of positions) {
if (range.containsPosition(position)) {
if (!lineToNamesMap.has(position.lineNumber)) {
lineToNamesMap.set(position.lineNumber, []);
}
if (lineToNamesMap.get(position.lineNumber)!.indexOf(name) === -1) {
lineToNamesMap.get(position.lineNumber)!.push(name);
}
}
}
}
});
const decorations: IDecorationOptions[] = [];
// Compute decorators for each line
lineToNamesMap.forEach((names, line) => {
const contentText = names.sort((first, second) => {
const content = model.getLineContent(line);
return content.indexOf(first) - content.indexOf(second);
}).map(name => `${name} = ${nameValueMap.get(name)}`).join(', ');
decorations.push(this.createInlineValueDecoration(line, contentText));
});
return decorations;
}
private createInlineValueDecoration(lineNumber: number, contentText: string): IDecorationOptions {
// If decoratorText is too long, trim and add ellipses. This could happen for minified files with everything on a single line
if (contentText.length > MAX_INLINE_DECORATOR_LENGTH) {
contentText = contentText.substr(0, MAX_INLINE_DECORATOR_LENGTH) + '...';
}
return {
range: {
startLineNumber: lineNumber,
endLineNumber: lineNumber,
startColumn: Constants.MAX_SAFE_SMALL_INTEGER,
endColumn: Constants.MAX_SAFE_SMALL_INTEGER
},
renderOptions: {
after: {
contentText,
backgroundColor: 'rgba(255, 200, 0, 0.2)',
margin: '10px'
},
dark: {
after: {
color: 'rgba(255, 255, 255, 0.5)',
}
},
light: {
after: {
color: 'rgba(0, 0, 0, 0.5)',
}
}
}
};
}
private getWordToPositionsMap(): Map<string, Position[]> {
if (!this.wordToLineNumbersMap) {
this.wordToLineNumbersMap = new Map<string, Position[]>();
const model = this.editor.getModel();
if (!model) {
return this.wordToLineNumbersMap;
}
// For every word in every line, map its ranges for fast lookup
for (let lineNumber = 1, len = model.getLineCount(); lineNumber <= len; ++lineNumber) {
const lineContent = model.getLineContent(lineNumber);
// If line is too long then skip the line
if (lineContent.length > MAX_TOKENIZATION_LINE_LEN) {
continue;
}
model.forceTokenization(lineNumber);
const lineTokens = model.getLineTokens(lineNumber);
for (let tokenIndex = 0, tokenCount = lineTokens.getCount(); tokenIndex < tokenCount; tokenIndex++) {
const tokenStartOffset = lineTokens.getStartOffset(tokenIndex);
const tokenEndOffset = lineTokens.getEndOffset(tokenIndex);
const tokenType = lineTokens.getStandardTokenType(tokenIndex);
const tokenStr = lineContent.substring(tokenStartOffset, tokenEndOffset);
// Token is a word and not a comment
if (tokenType === StandardTokenType.Other) {
DEFAULT_WORD_REGEXP.lastIndex = 0; // We assume tokens will usually map 1:1 to words if they match
const wordMatch = DEFAULT_WORD_REGEXP.exec(tokenStr);
if (wordMatch) {
const word = wordMatch[0];
if (!this.wordToLineNumbersMap.has(word)) {
this.wordToLineNumbersMap.set(word, []);
}
this.wordToLineNumbersMap.get(word)!.push(new Position(lineNumber, tokenStartOffset));
}
}
}
}
}
return this.wordToLineNumbersMap;
}
dispose(): void {
if (this.hoverWidget) {
this.hoverWidget.dispose();

View File

@@ -14,7 +14,7 @@ import { Position } from 'vs/editor/common/core/position';
import { Range } from 'vs/editor/common/core/range';
import { IContentWidget, ICodeEditor, IContentWidgetPosition, ContentWidgetPositionPreference } from 'vs/editor/browser/editorBrowser';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IDebugService, IExpression, IExpressionContainer } from 'vs/workbench/contrib/debug/common/debug';
import { IDebugService, IExpression, IExpressionContainer, IStackFrame } from 'vs/workbench/contrib/debug/common/debug';
import { Expression } from 'vs/workbench/contrib/debug/common/debugModel';
import { renderExpressionValue, replaceWhitespace } from 'vs/workbench/contrib/debug/browser/baseDebugView';
import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
@@ -34,6 +34,34 @@ import { VariablesRenderer } from 'vs/workbench/contrib/debug/browser/variablesV
const $ = dom.$;
const MAX_TREE_HEIGHT = 324;
async function doFindExpression(container: IExpressionContainer, namesToFind: string[]): Promise<IExpression | null> {
if (!container) {
return Promise.resolve(null);
}
const children = await container.getChildren();
// look for our variable in the list. First find the parents of the hovered variable if there are any.
const filtered = children.filter(v => namesToFind[0] === v.name);
if (filtered.length !== 1) {
return null;
}
if (namesToFind.length === 1) {
return filtered[0];
} else {
return doFindExpression(filtered[0], namesToFind.slice(1));
}
}
export async function findExpressionInStackFrame(stackFrame: IStackFrame, namesToFind: string[]): Promise<IExpression | undefined> {
const scopes = await stackFrame.getScopes();
const nonExpensive = scopes.filter(s => !s.expensive);
const expressions = coalesce(await Promise.all(nonExpensive.map(scope => doFindExpression(scope, namesToFind))));
// only show if all expressions found have the same value
return expressions.length > 0 && expressions.every(e => e.value === expressions[0].value) ? expressions[0] : undefined;
}
export class DebugHoverWidget implements IContentWidget {
static readonly ID = 'debug.hoverWidget';
@@ -107,7 +135,7 @@ export class DebugHoverWidget implements IContentWidget {
if (colors.editorHoverForeground) {
this.domNode.style.color = colors.editorHoverForeground.toString();
} else {
this.domNode.style.color = null;
this.domNode.style.color = '';
}
}));
this.toDispose.push(this.tree.onDidChangeContentHeight(() => this.layoutTreeAndContainer()));
@@ -166,7 +194,10 @@ export class DebugHoverWidget implements IContentWidget {
expression = new Expression(matchingExpression);
await expression.evaluate(session, this.debugService.getViewModel().focusedStackFrame, 'hover');
} else {
expression = await this.findExpressionInStackFrame(coalesce(matchingExpression.split('.').map(word => word.trim())));
const focusedStackFrame = this.debugService.getViewModel().focusedStackFrame;
if (focusedStackFrame) {
expression = await findExpressionInStackFrame(focusedStackFrame, coalesce(matchingExpression.split('.').map(word => word.trim())));
}
}
if (!expression || (expression instanceof Expression && !expression.available)) {
@@ -186,38 +217,6 @@ export class DebugHoverWidget implements IContentWidget {
className: 'hoverHighlight'
});
private async doFindExpression(container: IExpressionContainer, namesToFind: string[]): Promise<IExpression | null> {
if (!container) {
return Promise.resolve(null);
}
const children = await container.getChildren();
// look for our variable in the list. First find the parents of the hovered variable if there are any.
const filtered = children.filter(v => namesToFind[0] === v.name);
if (filtered.length !== 1) {
return null;
}
if (namesToFind.length === 1) {
return filtered[0];
} else {
return this.doFindExpression(filtered[0], namesToFind.slice(1));
}
}
private async findExpressionInStackFrame(namesToFind: string[]): Promise<IExpression | undefined> {
const focusedStackFrame = this.debugService.getViewModel().focusedStackFrame;
if (!focusedStackFrame) {
return undefined;
}
const scopes = await focusedStackFrame.getScopes();
const nonExpensive = scopes.filter(s => !s.expensive);
const expressions = coalesce(await Promise.all(nonExpensive.map(scope => this.doFindExpression(scope, namesToFind))));
// only show if all expressions found have the same value
return expressions.length > 0 && expressions.every(e => e.value === expressions[0].value) ? expressions[0] : undefined;
}
private async doShow(position: Position, expression: IExpression, focus: boolean, forceValueHover = false): Promise<void> {
if (!this.domNode) {
this.create();

View File

@@ -36,7 +36,7 @@ import { IAction } from 'vs/base/common/actions';
import { deepClone, equals } from 'vs/base/common/objects';
import { DebugSession } from 'vs/workbench/contrib/debug/browser/debugSession';
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
import { IDebugService, State, IDebugSession, CONTEXT_DEBUG_TYPE, CONTEXT_DEBUG_STATE, CONTEXT_IN_DEBUG_MODE, IThread, IDebugConfiguration, VIEWLET_ID, REPL_ID, IConfig, ILaunch, IViewModel, IConfigurationManager, IDebugModel, IEnablement, IBreakpoint, IBreakpointData, ICompound, IGlobalConfig, IStackFrame, AdapterEndEvent, getStateLabel, IDebugSessionOptions, CONTEXT_DEBUG_UX } from 'vs/workbench/contrib/debug/common/debug';
import { IDebugService, State, IDebugSession, CONTEXT_DEBUG_TYPE, CONTEXT_DEBUG_STATE, CONTEXT_IN_DEBUG_MODE, IThread, IDebugConfiguration, VIEWLET_ID, DEBUG_PANEL_ID, IConfig, ILaunch, IViewModel, IConfigurationManager, IDebugModel, IEnablement, IBreakpoint, IBreakpointData, ICompound, IGlobalConfig, IStackFrame, AdapterEndEvent, getStateLabel, IDebugSessionOptions, CONTEXT_DEBUG_UX, REPL_VIEW_ID } from 'vs/workbench/contrib/debug/common/debug';
import { getExtensionHostDebugSession } from 'vs/workbench/contrib/debug/common/debugUtils';
import { isErrorWithActions } from 'vs/base/common/errorsWithActions';
import { RunOnceScheduler } from 'vs/base/common/async';
@@ -45,6 +45,7 @@ import { isCodeEditor } from 'vs/editor/browser/editorBrowser';
import { CancellationTokenSource } from 'vs/base/common/cancellation';
import { TaskRunResult, DebugTaskRunner } from 'vs/workbench/contrib/debug/browser/debugTaskRunner';
import { IActivityService, NumberBadge } from 'vs/workbench/services/activity/common/activity';
import { IViewsService } from 'vs/workbench/common/views';
const DEBUG_BREAKPOINTS_KEY = 'debug.breakpoint';
const DEBUG_FUNCTION_BREAKPOINTS_KEY = 'debug.functionbreakpoint';
@@ -80,6 +81,7 @@ export class DebugService implements IDebugService {
@ITextFileService private readonly textFileService: ITextFileService,
@IViewletService private readonly viewletService: IViewletService,
@IPanelService private readonly panelService: IPanelService,
@IViewsService private readonly viewsService: IViewsService,
@INotificationService private readonly notificationService: INotificationService,
@IDialogService private readonly dialogService: IDialogService,
@IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService,
@@ -290,7 +292,7 @@ export class DebugService implements IDebugService {
"Compound must have \"configurations\" attribute set in order to start multiple configurations."));
}
if (compound.preLaunchTask) {
const taskResult = await this.taskRunner.runTaskAndCheckErrors(launch?.workspace || this.contextService.getWorkspace(), compound.preLaunchTask, this.showError);
const taskResult = await this.taskRunner.runTaskAndCheckErrors(launch?.workspace || this.contextService.getWorkspace(), compound.preLaunchTask, (msg, actions) => this.showError(msg, actions));
if (taskResult === TaskRunResult.Failure) {
this.endInitializingState();
return false;
@@ -379,13 +381,26 @@ export class DebugService implements IDebugService {
// a falsy config indicates an aborted launch
if (configByProviders && configByProviders.type) {
try {
const resolvedConfig = await this.substituteVariables(launch, configByProviders);
let resolvedConfig = await this.substituteVariables(launch, configByProviders);
if (!resolvedConfig) {
// User canceled resolving of interactive variables, silently return
// User cancelled resolving of interactive variables, silently return
return false;
}
if (!this.initCancellationToken) {
// User cancelled, silently return
return false;
}
const cfg = await this.configurationManager.resolveDebugConfigurationWithSubstitutedVariables(launch && launch.workspace ? launch.workspace.uri : undefined, type, resolvedConfig, this.initCancellationToken.token);
if (!cfg) {
if (launch && type && cfg === null && this.initCancellationToken) { // show launch.json only for "config" being "null".
await launch.openConfigFile(false, true, type, this.initCancellationToken.token);
}
return false;
}
resolvedConfig = cfg;
if (!this.configurationManager.getDebugger(resolvedConfig.type) || (configByProviders.request !== 'attach' && configByProviders.request !== 'launch')) {
let message: string;
if (configByProviders.request !== 'attach' && configByProviders.request !== 'launch') {
@@ -401,8 +416,8 @@ export class DebugService implements IDebugService {
return false;
}
const workspace = launch ? launch.workspace : this.contextService.getWorkspace();
const taskResult = await this.taskRunner.runTaskAndCheckErrors(workspace, resolvedConfig.preLaunchTask, this.showError);
const workspace = launch?.workspace || this.contextService.getWorkspace();
const taskResult = await this.taskRunner.runTaskAndCheckErrors(workspace, resolvedConfig.preLaunchTask, (msg, actions) => this.showError(msg, actions));
if (taskResult === TaskRunResult.Success) {
return this.doCreateSession(launch?.workspace, { resolved: resolvedConfig, unresolved: unresolvedConfig }, options);
}
@@ -413,16 +428,16 @@ export class DebugService implements IDebugService {
} else if (this.contextService.getWorkbenchState() === WorkbenchState.EMPTY) {
await this.showError(nls.localize('noFolderWorkspaceDebugError', "The active file can not be debugged. Make sure it is saved and that you have a debug extension installed for that file type."));
}
if (launch) {
await launch.openConfigFile(false, true, undefined, this.initCancellationToken ? this.initCancellationToken.token : undefined);
if (launch && this.initCancellationToken) {
await launch.openConfigFile(false, true, undefined, this.initCancellationToken.token);
}
return false;
}
}
if (launch && type && configByProviders === null) { // show launch.json only for "config" being "null".
await launch.openConfigFile(false, true, type, this.initCancellationToken ? this.initCancellationToken.token : undefined);
if (launch && type && configByProviders === null && this.initCancellationToken) { // show launch.json only for "config" being "null".
await launch.openConfigFile(false, true, type, this.initCancellationToken.token);
}
return false;
@@ -453,7 +468,7 @@ export class DebugService implements IDebugService {
const internalConsoleOptions = session.configuration.internalConsoleOptions || this.configurationService.getValue<IDebugConfiguration>('debug').internalConsoleOptions;
if (internalConsoleOptions === 'openOnSessionStart' || (this.viewModel.firstSessionStart && internalConsoleOptions === 'openOnFirstSessionStart')) {
this.panelService.openPanel(REPL_ID, false);
this.viewsService.openView(REPL_VIEW_ID, false);
}
this.viewModel.firstSessionStart = false;
@@ -479,7 +494,7 @@ export class DebugService implements IDebugService {
// Show the repl if some error got logged there #5870
if (session && session.getReplElements().length > 0) {
this.panelService.openPanel(REPL_ID, false);
this.viewsService.openView(REPL_VIEW_ID, false);
}
if (session.configuration && session.configuration.request === 'attach' && session.configuration.__autoAttach) {
@@ -563,8 +578,11 @@ export class DebugService implements IDebugService {
// Data breakpoints that can not be persisted should be cleared when a session ends
const dataBreakpoints = this.model.getDataBreakpoints().filter(dbp => !dbp.canPersist);
dataBreakpoints.forEach(dbp => this.model.removeDataBreakpoints(dbp.getId()));
}
if (this.panelService.getLastActivePanelId() === DEBUG_PANEL_ID && this.configurationService.getValue<IDebugConfiguration>('debug').console.closeOnEnd) {
this.panelService.hideActivePanel();
}
}
}));
}
@@ -579,7 +597,7 @@ export class DebugService implements IDebugService {
}
await this.taskRunner.runTask(session.root, session.configuration.postDebugTask);
return this.taskRunner.runTaskAndCheckErrors(session.root, session.configuration.preLaunchTask, this.showError);
return this.taskRunner.runTaskAndCheckErrors(session.root, session.configuration.preLaunchTask, (msg, actions) => this.showError(msg, actions));
};
const extensionDebugSession = getExtensionHostDebugSession(session);
@@ -636,6 +654,9 @@ export class DebugService implements IDebugService {
const resolvedByProviders = await this.configurationManager.resolveConfigurationByProviders(launch.workspace ? launch.workspace.uri : undefined, unresolved.type, unresolved, this.initCancellationToken.token);
if (resolvedByProviders) {
resolved = await this.substituteVariables(launch, resolvedByProviders);
if (resolved && this.initCancellationToken) {
resolved = await this.configurationManager.resolveDebugConfigurationWithSubstitutedVariables(launch && launch.workspace ? launch.workspace.uri : undefined, unresolved.type, resolved, this.initCancellationToken.token);
}
} else {
resolved = resolvedByProviders;
}
@@ -660,13 +681,13 @@ export class DebugService implements IDebugService {
}
stopSession(session: IDebugSession): Promise<any> {
if (session) {
return session.terminate();
}
const sessions = this.model.getSessions();
if (sessions.length === 0) {
this.taskRunner.cancel();
this.endInitializingState();
}
@@ -708,33 +729,8 @@ export class DebugService implements IDebugService {
//---- focus management
async focusStackFrame(stackFrame: IStackFrame | undefined, thread?: IThread, session?: IDebugSession, explicit?: boolean): Promise<void> {
if (!session) {
if (stackFrame || thread) {
session = stackFrame ? stackFrame.thread.session : thread!.session;
} else {
const sessions = this.model.getSessions();
const stoppedSession = sessions.filter(s => s.state === State.Stopped).shift();
session = stoppedSession || (sessions.length ? sessions[0] : undefined);
}
}
if (!thread) {
if (stackFrame) {
thread = stackFrame.thread;
} else {
const threads = session ? session.getAllThreads() : undefined;
const stoppedThread = threads && threads.filter(t => t.stopped).shift();
thread = stoppedThread || (threads && threads.length ? threads[0] : undefined);
}
}
if (!stackFrame) {
if (thread) {
const callStack = thread.getCallStack();
stackFrame = first(callStack, sf => !!(sf && sf.source && sf.source.available && sf.source.presentationHint !== 'deemphasize'), undefined);
}
}
async focusStackFrame(_stackFrame: IStackFrame | undefined, _thread?: IThread, _session?: IDebugSession, explicit?: boolean): Promise<void> {
const { stackFrame, thread, session } = getStackFrameThreadAndSessionToFocus(this.model, _stackFrame, _thread, _session);
if (stackFrame) {
const editor = await stackFrame.openInEditor(this.editorService, true);
@@ -760,9 +756,11 @@ export class DebugService implements IDebugService {
//---- watches
addWatchExpression(name: string): void {
addWatchExpression(name?: string): void {
const we = this.model.addWatchExpression(name);
this.viewModel.setSelectedExpression(we);
if (!name) {
this.viewModel.setSelectedExpression(we);
}
this.storeWatchExpressions();
}
@@ -1096,3 +1094,34 @@ export class DebugService implements IDebugService {
});
}
}
export function getStackFrameThreadAndSessionToFocus(model: IDebugModel, stackFrame: IStackFrame | undefined, thread?: IThread, session?: IDebugSession): { stackFrame: IStackFrame | undefined, thread: IThread | undefined, session: IDebugSession | undefined } {
if (!session) {
if (stackFrame || thread) {
session = stackFrame ? stackFrame.thread.session : thread!.session;
} else {
const sessions = model.getSessions();
const stoppedSession = sessions.filter(s => s.state === State.Stopped).shift();
session = stoppedSession || (sessions.length ? sessions[0] : undefined);
}
}
if (!thread) {
if (stackFrame) {
thread = stackFrame.thread;
} else {
const threads = session ? session.getAllThreads() : undefined;
const stoppedThread = threads && threads.filter(t => t.stopped).shift();
thread = stoppedThread || (threads && threads.length ? threads[0] : undefined);
}
}
if (!stackFrame) {
if (thread) {
const callStack = thread.getCallStack();
stackFrame = first(callStack, sf => !!(sf && sf.source && sf.source.available && sf.source.presentationHint !== 'deemphasize'), undefined);
}
}
return { session, thread, stackFrame };
}

View File

@@ -9,7 +9,6 @@ import * as nls from 'vs/nls';
import * as platform from 'vs/base/common/platform';
import severity from 'vs/base/common/severity';
import { Event, Emitter } from 'vs/base/common/event';
import { CompletionItem, completionKindFromString } from 'vs/editor/common/modes';
import { Position, IPosition } from 'vs/editor/common/core/position';
import * as aria from 'vs/base/browser/ui/aria/aria';
import { IDebugSession, IConfig, IThread, IRawModelUpdate, IDebugService, IRawStoppedDetails, State, LoadedSourceEvent, IFunctionBreakpoint, IExceptionBreakpoint, IBreakpoint, IExceptionInfo, AdapterEndEvent, IDebugger, VIEWLET_ID, IDebugConfiguration, IReplElement, IStackFrame, IExpression, IReplElementSource, IDataBreakpoint, IDebugSessionOptions } from 'vs/workbench/contrib/debug/common/debug';
@@ -26,7 +25,6 @@ import { IHostService } from 'vs/workbench/services/host/browser/host';
import { IExtensionHostDebugService } from 'vs/platform/debug/common/extensionHostDebug';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { normalizeDriveLetter } from 'vs/base/common/labels';
import { Range } from 'vs/editor/common/core/range';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
import { ReplModel } from 'vs/workbench/contrib/debug/common/replModel';
@@ -34,6 +32,7 @@ import { IOpenerService } from 'vs/platform/opener/common/opener';
import { variableSetEmitter } from 'vs/workbench/contrib/debug/browser/variablesView';
import { CancellationTokenSource, CancellationToken } from 'vs/base/common/cancellation';
import { distinct } from 'vs/base/common/arrays';
import { INotificationService } from 'vs/platform/notification/common/notification';
export class DebugSession implements IDebugSession {
@@ -74,7 +73,8 @@ export class DebugSession implements IDebugSession {
@IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService,
@IProductService private readonly productService: IProductService,
@IExtensionHostDebugService private readonly extensionHostDebugService: IExtensionHostDebugService,
@IOpenerService private readonly openerService: IOpenerService
@IOpenerService private readonly openerService: IOpenerService,
@INotificationService private readonly notificationService: INotificationService
) {
this.id = generateUuid();
this._options = options || {};
@@ -565,35 +565,17 @@ export class DebugSession implements IDebugSession {
}
}
async completions(frameId: number | undefined, text: string, position: Position, overwriteBefore: number, token: CancellationToken): Promise<CompletionItem[]> {
async completions(frameId: number | undefined, text: string, position: Position, overwriteBefore: number, token: CancellationToken): Promise<DebugProtocol.CompletionsResponse> {
if (!this.raw) {
return Promise.reject(new Error('no debug adapter'));
}
const response = await this.raw.completions({
return this.raw.completions({
frameId,
text,
column: position.column,
line: position.lineNumber,
}, token);
const result: CompletionItem[] = [];
if (response && response.body && response.body.targets) {
response.body.targets.forEach(item => {
if (item && item.label) {
result.push({
label: item.label,
insertText: item.text || item.label,
kind: completionKindFromString(item.type || 'property'),
filterText: (item.start && item.length) ? text.substr(item.start, item.length).concat(item.label) : undefined,
range: Range.fromPositions(position.delta(0, -(item.length || overwriteBefore)), position),
sortText: item.sortText
});
}
});
}
return result;
}
//---- threads
@@ -711,6 +693,7 @@ export class DebugSession implements IDebugSession {
await this.raw.configurationDone();
} catch (e) {
// Disconnect the debug session on configuration done error #10596
this.notificationService.error(e);
if (this.raw) {
this.raw.disconnect();
}
@@ -921,6 +904,7 @@ export class DebugSession implements IDebugSession {
this.raw.dispose();
}
this.raw = undefined;
this.fetchThreadsScheduler = undefined;
this.model.clearThreads(this.getId(), true);
this._onDidChangeState.fire();
}

View File

@@ -10,7 +10,6 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
import { IStatusbarEntry, IStatusbarService, StatusbarAlignment, IStatusbarEntryAccessor } from 'vs/workbench/services/statusbar/common/statusbar';
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
export class DebugStatusContribution implements IWorkbenchContribution {
private showInStatusBar!: 'never' | 'always' | 'onFirstSessionStart';
@@ -56,20 +55,17 @@ export class DebugStatusContribution implements IWorkbenchContribution {
}));
}
private getText(): string {
private get entry(): IStatusbarEntry {
let text = '';
const manager = this.debugService.getConfigurationManager();
const name = manager.selectedConfiguration.name || '';
const nameAndLaunchPresent = name && manager.selectedConfiguration.launch;
if (nameAndLaunchPresent) {
return '$(play) ' + (manager.getLaunches().length > 1 ? `${name} (${manager.selectedConfiguration.launch!.name})` : name);
text = '$(play) ' + (manager.getLaunches().length > 1 ? `${name} (${manager.selectedConfiguration.launch!.name})` : name);
}
return '';
}
private get entry(): IStatusbarEntry {
return {
text: this.getText(),
text: text,
tooltip: nls.localize('selectAndStartDebug', "Select and start debug configuration"),
command: 'workbench.action.debug.selectandstart'
};

View File

@@ -38,6 +38,8 @@ export const enum TaskRunResult {
export class DebugTaskRunner {
private canceled = false;
constructor(
@ITaskService private readonly taskService: ITaskService,
@IMarkerService private readonly markerService: IMarkerService,
@@ -46,9 +48,17 @@ export class DebugTaskRunner {
@IDialogService private readonly dialogService: IDialogService,
) { }
cancel(): void {
this.canceled = true;
}
async runTaskAndCheckErrors(root: IWorkspaceFolder | IWorkspace | undefined, taskId: string | TaskIdentifier | undefined, onError: (msg: string, actions: IAction[]) => Promise<void>): Promise<TaskRunResult> {
try {
this.canceled = false;
const taskSummary = await this.runTask(root, taskId);
if (this.canceled) {
return TaskRunResult.Failure;
}
const errorCount = taskId ? this.markerService.getStatistics().errors : 0;
const successExitCode = taskSummary && taskSummary.exitCode === 0;

View File

@@ -36,78 +36,6 @@ import { IDisposable, dispose } from 'vs/base/common/lifecycle';
const DEBUG_TOOLBAR_POSITION_KEY = 'debug.actionswidgetposition';
const DEBUG_TOOLBAR_Y_KEY = 'debug.actionswidgety';
export const debugToolBarBackground = registerColor('debugToolBar.background', {
dark: '#333333',
light: '#F3F3F3',
hc: '#000000'
}, localize('debugToolBarBackground', "Debug toolbar background color."));
export const debugToolBarBorder = registerColor('debugToolBar.border', {
dark: null,
light: null,
hc: null
}, localize('debugToolBarBorder', "Debug toolbar border color."));
export const debugIconStartForeground = registerColor('debugIcon.startForeground', {
dark: '#89D185',
light: '#388A34',
hc: '#89D185'
}, localize('debugIcon.startForeground', "Debug toolbar icon for start debugging."));
export const debugIconPauseForeground = registerColor('debugIcon.pauseForeground', {
dark: '#75BEFF',
light: '#007ACC',
hc: '#75BEFF'
}, localize('debugIcon.pauseForeground', "Debug toolbar icon for pause."));
export const debugIconStopForeground = registerColor('debugIcon.stopForeground', {
dark: '#F48771',
light: '#A1260D',
hc: '#F48771'
}, localize('debugIcon.stopForeground', "Debug toolbar icon for stop."));
export const debugIconDisconnectForeground = registerColor('debugIcon.disconnectForeground', {
dark: '#F48771',
light: '#A1260D',
hc: '#F48771'
}, localize('debugIcon.disconnectForeground', "Debug toolbar icon for disconnect."));
export const debugIconRestartForeground = registerColor('debugIcon.restartForeground', {
dark: '#89D185',
light: '#388A34',
hc: '#89D185'
}, localize('debugIcon.restartForeground', "Debug toolbar icon for restart."));
export const debugIconStepOverForeground = registerColor('debugIcon.stepOverForeground', {
dark: '#75BEFF',
light: '#007ACC',
hc: '#75BEFF'
}, localize('debugIcon.stepOverForeground', "Debug toolbar icon for step over."));
export const debugIconStepIntoForeground = registerColor('debugIcon.stepIntoForeground', {
dark: '#75BEFF',
light: '#007ACC',
hc: '#75BEFF'
}, localize('debugIcon.stepIntoForeground', "Debug toolbar icon for step into."));
export const debugIconStepOutForeground = registerColor('debugIcon.stepOutForeground', {
dark: '#75BEFF',
light: '#007ACC',
hc: '#75BEFF'
}, localize('debugIcon.stepOutForeground', "Debug toolbar icon for step over."));
export const debugIconContinueForeground = registerColor('debugIcon.continueForeground', {
dark: '#75BEFF',
light: '#007ACC',
hc: '#75BEFF'
}, localize('debugIcon.continueForeground', "Debug toolbar icon for continue."));
export const debugIconStepBackForeground = registerColor('debugIcon.stepBackForeground', {
dark: '#75BEFF',
light: '#007ACC',
hc: '#75BEFF'
}, localize('debugIcon.stepBackForeground', "Debug toolbar icon for step back."));
export class DebugToolBar extends Themable implements IWorkbenchContribution {
private $el: HTMLElement;
@@ -184,9 +112,7 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution {
}, 20));
this.updateStyles();
this.registerListeners();
this.hide();
}
@@ -327,7 +253,7 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution {
dom.hide(this.$el);
}
public static getActions(menu: IMenu, debugService: IDebugService, instantiationService: IInstantiationService): { actions: IAction[], disposable: IDisposable } {
static getActions(menu: IMenu, debugService: IDebugService, instantiationService: IInstantiationService): { actions: IAction[], disposable: IDisposable } {
const actions: IAction[] = [];
const disposable = createAndFillInActionBarActions(menu, undefined, actions, () => false);
if (debugService.getViewModel().isMultiSessionView()) {
@@ -340,7 +266,7 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution {
};
}
public dispose(): void {
dispose(): void {
super.dispose();
if (this.$el) {
@@ -353,6 +279,78 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution {
}
}
export const debugToolBarBackground = registerColor('debugToolBar.background', {
dark: '#333333',
light: '#F3F3F3',
hc: '#000000'
}, localize('debugToolBarBackground', "Debug toolbar background color."));
export const debugToolBarBorder = registerColor('debugToolBar.border', {
dark: null,
light: null,
hc: null
}, localize('debugToolBarBorder', "Debug toolbar border color."));
export const debugIconStartForeground = registerColor('debugIcon.startForeground', {
dark: '#89D185',
light: '#388A34',
hc: '#89D185'
}, localize('debugIcon.startForeground', "Debug toolbar icon for start debugging."));
export const debugIconPauseForeground = registerColor('debugIcon.pauseForeground', {
dark: '#75BEFF',
light: '#007ACC',
hc: '#75BEFF'
}, localize('debugIcon.pauseForeground', "Debug toolbar icon for pause."));
export const debugIconStopForeground = registerColor('debugIcon.stopForeground', {
dark: '#F48771',
light: '#A1260D',
hc: '#F48771'
}, localize('debugIcon.stopForeground', "Debug toolbar icon for stop."));
export const debugIconDisconnectForeground = registerColor('debugIcon.disconnectForeground', {
dark: '#F48771',
light: '#A1260D',
hc: '#F48771'
}, localize('debugIcon.disconnectForeground', "Debug toolbar icon for disconnect."));
export const debugIconRestartForeground = registerColor('debugIcon.restartForeground', {
dark: '#89D185',
light: '#388A34',
hc: '#89D185'
}, localize('debugIcon.restartForeground', "Debug toolbar icon for restart."));
export const debugIconStepOverForeground = registerColor('debugIcon.stepOverForeground', {
dark: '#75BEFF',
light: '#007ACC',
hc: '#75BEFF'
}, localize('debugIcon.stepOverForeground', "Debug toolbar icon for step over."));
export const debugIconStepIntoForeground = registerColor('debugIcon.stepIntoForeground', {
dark: '#75BEFF',
light: '#007ACC',
hc: '#75BEFF'
}, localize('debugIcon.stepIntoForeground', "Debug toolbar icon for step into."));
export const debugIconStepOutForeground = registerColor('debugIcon.stepOutForeground', {
dark: '#75BEFF',
light: '#007ACC',
hc: '#75BEFF'
}, localize('debugIcon.stepOutForeground', "Debug toolbar icon for step over."));
export const debugIconContinueForeground = registerColor('debugIcon.continueForeground', {
dark: '#75BEFF',
light: '#007ACC',
hc: '#75BEFF'
}, localize('debugIcon.continueForeground', "Debug toolbar icon for continue."));
export const debugIconStepBackForeground = registerColor('debugIcon.stepBackForeground', {
dark: '#75BEFF',
light: '#007ACC',
hc: '#75BEFF'
}, localize('debugIcon.stepBackForeground', "Debug toolbar icon for step back."));
registerThemingParticipant((theme, collector) => {
const debugIconStartColor = theme.getColor(debugIconStartForeground);

View File

@@ -8,7 +8,7 @@ import * as nls from 'vs/nls';
import { IAction } from 'vs/base/common/actions';
import * as DOM from 'vs/base/browser/dom';
import { IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar';
import { IDebugService, VIEWLET_ID, State, BREAKPOINTS_VIEW_ID, IDebugConfiguration, REPL_ID, CONTEXT_DEBUG_UX, CONTEXT_DEBUG_UX_KEY } from 'vs/workbench/contrib/debug/common/debug';
import { IDebugService, VIEWLET_ID, State, BREAKPOINTS_VIEW_ID, IDebugConfiguration, DEBUG_PANEL_ID, CONTEXT_DEBUG_UX, CONTEXT_DEBUG_UX_KEY } from 'vs/workbench/contrib/debug/common/debug';
import { StartAction, ConfigureAction, SelectAndStartAction, FocusSessionAction } from 'vs/workbench/contrib/debug/browser/debugActions';
import { StartDebugActionViewItem, FocusSessionActionViewItem } from 'vs/workbench/contrib/debug/browser/debugActionViewItems';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
@@ -105,8 +105,8 @@ export class DebugViewPaneContainer extends ViewPaneContainer {
}
@memoize
private get toggleReplAction(): ToggleReplAction {
return this._register(this.instantiationService.createInstance(ToggleReplAction, ToggleReplAction.ID, ToggleReplAction.LABEL));
private get toggleReplAction(): OpenDebugPanelAction {
return this._register(this.instantiationService.createInstance(OpenDebugPanelAction, OpenDebugPanelAction.ID, OpenDebugPanelAction.LABEL));
}
@memoize
@@ -228,14 +228,16 @@ export class DebugViewPaneContainer extends ViewPaneContainer {
}
}
class ToggleReplAction extends TogglePanelAction {
static readonly ID = 'debug.toggleRepl';
static readonly LABEL = nls.localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'debugConsoleAction' }, 'Debug Console');
export class OpenDebugPanelAction extends TogglePanelAction {
public static readonly ID = 'workbench.debug.action.toggleRepl';
public static readonly LABEL = nls.localize('toggleDebugPanel', "Debug Console");
constructor(id: string, label: string,
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,
@IPanelService panelService: IPanelService
constructor(
id: string,
label: string,
@IPanelService panelService: IPanelService,
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService
) {
super(id, label, REPL_ID, panelService, layoutService, 'debug-action codicon-terminal');
super(id, label, DEBUG_PANEL_ID, panelService, layoutService);
}
}

View File

@@ -13,7 +13,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { renderViewTree } from 'vs/workbench/contrib/debug/browser/baseDebugView';
import { IDebugSession, IDebugService, IDebugModel, CONTEXT_LOADED_SCRIPTS_ITEM_TYPE } from 'vs/workbench/contrib/debug/common/debug';
import { IDebugSession, IDebugService, CONTEXT_LOADED_SCRIPTS_ITEM_TYPE } from 'vs/workbench/contrib/debug/common/debug';
import { Source } from 'vs/workbench/contrib/debug/common/debugSource';
import { IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
@@ -26,17 +26,19 @@ import { RunOnceScheduler } from 'vs/base/common/async';
import { ResourceLabels, IResourceLabelProps, IResourceLabelOptions, IResourceLabel } from 'vs/workbench/browser/labels';
import { FileKind } from 'vs/platform/files/common/files';
import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
import { ITreeRenderer, ITreeNode, ITreeFilter, TreeVisibility, TreeFilterResult, IAsyncDataSource } from 'vs/base/browser/ui/tree/tree';
import { ITreeNode, ITreeFilter, TreeVisibility, TreeFilterResult, ITreeElement } from 'vs/base/browser/ui/tree/tree';
import { IAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { WorkbenchAsyncDataTree, TreeResourceNavigator2 } from 'vs/platform/list/browser/listService';
import { TreeResourceNavigator2, WorkbenchCompressibleObjectTree } from 'vs/platform/list/browser/listService';
import { dispose } from 'vs/base/common/lifecycle';
import { createMatches, FuzzyScore } from 'vs/base/common/filters';
import { DebugContentProvider } from 'vs/workbench/contrib/debug/common/debugContentProvider';
import { ILabelService } from 'vs/platform/label/common/label';
import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme';
import type { ICompressedTreeNode } from 'vs/base/browser/ui/tree/compressedObjectTreeModel';
import type { ICompressibleTreeRenderer } from 'vs/base/browser/ui/tree/objectTree';
const SMART = true;
const NEW_STYLE_COMPRESS = true;
// RFC 2396, Appendix A: https://www.ietf.org/rfc/rfc2396.txt
const URI_SCHEMA_PATTERN = /^[a-zA-Z][a-zA-Z0-9\+\-\.]+:/;
@@ -49,7 +51,7 @@ class BaseTreeItem {
private _children = new Map<string, BaseTreeItem>();
private _source: Source | undefined;
constructor(private _parent: BaseTreeItem | undefined, private _label: string) {
constructor(private _parent: BaseTreeItem | undefined, private _label: string, public readonly isIncompressible = false) {
this._showedMoreThanOne = false;
}
@@ -150,7 +152,7 @@ class BaseTreeItem {
}
// skips intermediate single-child nodes
getChildren(): Promise<BaseTreeItem[]> {
getChildren(): BaseTreeItem[] {
const child = this.oneChild();
if (child) {
return child.getChildren();
@@ -159,7 +161,7 @@ class BaseTreeItem {
for (let child of this._children.values()) {
array.push(child);
}
return Promise.resolve(array.sort((a, b) => this.compare(a, b)));
return array.sort((a, b) => this.compare(a, b));
}
// skips intermediate single-child nodes
@@ -205,7 +207,7 @@ class BaseTreeItem {
}
private oneChild(): BaseTreeItem | undefined {
if (SMART && !this._source && !this._showedMoreThanOne && !(this instanceof RootFolderTreeItem) && !(this instanceof SessionTreeItem)) {
if (!this._source && !this._showedMoreThanOne && this.skipOneChild()) {
if (this._children.size === 1) {
return this._children.values().next().value;
}
@@ -216,22 +218,28 @@ class BaseTreeItem {
}
return undefined;
}
private skipOneChild(): boolean {
if (NEW_STYLE_COMPRESS) {
// if the root node has only one Session, don't show the session
return this instanceof RootTreeItem;
} else {
return !(this instanceof RootFolderTreeItem) && !(this instanceof SessionTreeItem);
}
}
}
class RootFolderTreeItem extends BaseTreeItem {
constructor(parent: BaseTreeItem, public folder: IWorkspaceFolder) {
super(parent, folder.name);
super(parent, folder.name, true);
}
}
class RootTreeItem extends BaseTreeItem {
constructor(private _debugModel: IDebugModel, private _environmentService: IEnvironmentService, private _contextService: IWorkspaceContextService, private _labelService: ILabelService) {
constructor(private _environmentService: IEnvironmentService, private _contextService: IWorkspaceContextService, private _labelService: ILabelService) {
super(undefined, 'Root');
this._debugModel.getSessions().forEach(session => {
this.add(session);
});
}
add(session: IDebugSession): SessionTreeItem {
@@ -248,14 +256,12 @@ class SessionTreeItem extends BaseTreeItem {
private static readonly URL_REGEXP = /^(https?:\/\/[^/]+)(\/.*)$/;
private _session: IDebugSession;
private _initialized: boolean;
private _map = new Map<string, BaseTreeItem>();
private _labelService: ILabelService;
constructor(labelService: ILabelService, parent: BaseTreeItem, session: IDebugSession, private _environmentService: IEnvironmentService, private rootProvider: IWorkspaceContextService) {
super(parent, session.getLabel());
super(parent, session.getLabel(), true);
this._labelService = labelService;
this._initialized = false;
this._session = session;
}
@@ -275,19 +281,6 @@ class SessionTreeItem extends BaseTreeItem {
return true;
}
getChildren(): Promise<BaseTreeItem[]> {
if (!this._initialized) {
this._initialized = true;
return this._session.getLoadedSources().then(paths => {
paths.forEach(path => this.addPath(path));
return super.getChildren();
});
}
return super.getChildren();
}
protected compare(a: BaseTreeItem, b: BaseTreeItem): number {
const acat = this.category(a);
const bcat = this.category(b);
@@ -388,11 +381,30 @@ class SessionTreeItem extends BaseTreeItem {
}
}
interface IViewState {
readonly expanded: Set<string>;
}
/**
* This maps a model item into a view model item.
*/
function asTreeElement(item: BaseTreeItem, viewState?: IViewState): ITreeElement<LoadedScriptsItem> {
const children = item.getChildren();
const collapsed = viewState ? !viewState.expanded.has(item.getId()) : !(item instanceof SessionTreeItem);
return {
element: item,
collapsed,
collapsible: item.hasChildren(),
children: children.map(i => asTreeElement(i, viewState))
};
}
export class LoadedScriptsView extends ViewPane {
private treeContainer!: HTMLElement;
private loadedScriptsItemType: IContextKey<string>;
private tree!: WorkbenchAsyncDataTree<LoadedScriptsItem, LoadedScriptsItem, FuzzyScore>;
private tree!: WorkbenchCompressibleObjectTree<LoadedScriptsItem, FuzzyScore>;
private treeLabels!: ResourceLabels;
private changeScheduler!: RunOnceScheduler;
private treeNeedsRefreshOnVisible = false;
@@ -402,7 +414,7 @@ export class LoadedScriptsView extends ViewPane {
options: IViewletViewOptions,
@IContextMenuService contextMenuService: IContextMenuService,
@IKeybindingService keybindingService: IKeybindingService,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IInstantiationService instantiationService: IInstantiationService,
@IConfigurationService configurationService: IConfigurationService,
@IEditorService private readonly editorService: IEditorService,
@IContextKeyService readonly contextKeyService: IContextKeyService,
@@ -411,7 +423,7 @@ export class LoadedScriptsView extends ViewPane {
@IDebugService private readonly debugService: IDebugService,
@ILabelService private readonly labelService: ILabelService
) {
super({ ...(options as IViewPaneOptions), ariaHeaderLabel: nls.localize('loadedScriptsSection', "Loaded Scripts Section") }, keybindingService, contextMenuService, configurationService, contextKeyService);
super({ ...(options as IViewPaneOptions), ariaHeaderLabel: nls.localize('loadedScriptsSection', "Loaded Scripts Section") }, keybindingService, contextMenuService, configurationService, contextKeyService, instantiationService);
this.loadedScriptsItemType = CONTEXT_LOADED_SCRIPTS_ITEM_TYPE.bindTo(contextKeyService);
}
@@ -423,20 +435,30 @@ export class LoadedScriptsView extends ViewPane {
this.filter = new LoadedScriptsFilter();
const root = new RootTreeItem(this.debugService.getModel(), this.environmentService, this.contextService, this.labelService);
const root = new RootTreeItem(this.environmentService, this.contextService, this.labelService);
this.treeLabels = this.instantiationService.createInstance(ResourceLabels, { onDidChangeVisibility: this.onDidChangeBodyVisibility });
this._register(this.treeLabels);
this.tree = this.instantiationService.createInstance<typeof WorkbenchAsyncDataTree, WorkbenchAsyncDataTree<LoadedScriptsItem, LoadedScriptsItem, FuzzyScore>>(WorkbenchAsyncDataTree, 'LoadedScriptsView', this.treeContainer, new LoadedScriptsDelegate(),
this.tree = this.instantiationService.createInstance(WorkbenchCompressibleObjectTree,
'LoadedScriptsView',
this.treeContainer,
new LoadedScriptsDelegate(),
[new LoadedScriptsRenderer(this.treeLabels)],
new LoadedScriptsDataSource(),
{
compressionEnabled: NEW_STYLE_COMPRESS,
collapseByDefault: true,
hideTwistiesOfChildlessElements: true,
identityProvider: {
getId: (element: LoadedScriptsItem) => element.getId()
},
keyboardNavigationLabelProvider: {
getKeyboardNavigationLabel: (element: LoadedScriptsItem) => element.getLabel()
getKeyboardNavigationLabel: (element: LoadedScriptsItem) => {
return element.getLabel();
},
getCompressedNodeKeyboardNavigationLabel: (elements: LoadedScriptsItem[]) => {
return elements.map(e => e.getLabel()).join('/');
}
},
filter: this.filter,
accessibilityProvider: new LoadedSciptsAccessibilityProvider(),
@@ -447,12 +469,14 @@ export class LoadedScriptsView extends ViewPane {
}
);
this.tree.setInput(root);
const updateView = (viewState?: IViewState) => this.tree.setChildren(null, asTreeElement(root, viewState).children);
updateView();
this.changeScheduler = new RunOnceScheduler(() => {
this.treeNeedsRefreshOnVisible = false;
if (this.tree) {
this.tree.updateChildren();
updateView();
}
}, 300);
this._register(this.changeScheduler);
@@ -486,12 +510,19 @@ export class LoadedScriptsView extends ViewPane {
}
};
const addSourcePathsToSession = (session: IDebugSession) => {
const sessionNode = root.add(session);
return session.getLoadedSources().then(paths => {
paths.forEach(path => sessionNode.addPath(path));
scheduleRefreshOnVisible();
});
};
const registerSessionListeners = (session: IDebugSession) => {
this._register(session.onDidChangeName(() => {
// Re-add session, this will trigger proper sorting and id recalculation.
root.remove(session.getId());
root.add(session);
scheduleRefreshOnVisible();
addSourcePathsToSession(session);
}));
this._register(session.onDidLoadedSource(event => {
let sessionRoot: SessionTreeItem;
@@ -534,6 +565,38 @@ export class LoadedScriptsView extends ViewPane {
this.changeScheduler.schedule();
}
}));
// feature: expand all nodes when filtering (not when finding)
let viewState: IViewState | undefined;
this._register(this.tree.onDidChangeTypeFilterPattern(pattern => {
if (!this.tree.options.filterOnType) {
return;
}
if (!viewState && pattern) {
const expanded = new Set<string>();
const visit = (node: ITreeNode<BaseTreeItem | null, FuzzyScore>) => {
if (node.element && !node.collapsed) {
expanded.add(node.element.getId());
}
for (const child of node.children) {
visit(child);
}
};
visit(this.tree.getNode());
viewState = { expanded };
this.tree.expandAll();
} else if (!pattern && viewState) {
this.tree.setFocus([]);
updateView(viewState);
viewState = undefined;
}
}));
// populate tree model with source paths from all debug sessions
this.debugService.getModel().getSessions().forEach(session => addSourcePathsToSession(session));
}
layoutBody(height: number, width: number): void {
@@ -558,22 +621,11 @@ class LoadedScriptsDelegate implements IListVirtualDelegate<LoadedScriptsItem> {
}
}
class LoadedScriptsDataSource implements IAsyncDataSource<LoadedScriptsItem, LoadedScriptsItem> {
hasChildren(element: LoadedScriptsItem): boolean {
return element.hasChildren();
}
getChildren(element: LoadedScriptsItem): Promise<LoadedScriptsItem[]> {
return element.getChildren();
}
}
interface ILoadedScriptsItemTemplateData {
label: IResourceLabel;
}
class LoadedScriptsRenderer implements ITreeRenderer<BaseTreeItem, FuzzyScore, ILoadedScriptsItemTemplateData> {
class LoadedScriptsRenderer implements ICompressibleTreeRenderer<BaseTreeItem, FuzzyScore, ILoadedScriptsItemTemplateData> {
static readonly ID = 'lsrenderer';
@@ -594,9 +646,23 @@ class LoadedScriptsRenderer implements ITreeRenderer<BaseTreeItem, FuzzyScore, I
renderElement(node: ITreeNode<BaseTreeItem, FuzzyScore>, index: number, data: ILoadedScriptsItemTemplateData): void {
const element = node.element;
const label = element.getLabel();
this.render(element, label, data, node.filterData);
}
renderCompressedElements(node: ITreeNode<ICompressedTreeNode<BaseTreeItem>, FuzzyScore>, index: number, data: ILoadedScriptsItemTemplateData, height: number | undefined): void {
const element = node.element.elements[node.element.elements.length - 1];
const labels = node.element.elements.map(e => e.getLabel());
this.render(element, labels, data, node.filterData);
}
private render(element: BaseTreeItem, labels: string | string[], data: ILoadedScriptsItemTemplateData, filterData: FuzzyScore | undefined) {
const label: IResourceLabelProps = {
name: element.getLabel()
name: labels
};
const options: IResourceLabelOptions = {
title: element.getHoverLabel()
@@ -621,7 +687,7 @@ class LoadedScriptsRenderer implements ITreeRenderer<BaseTreeItem, FuzzyScore, I
options.fileKind = FileKind.FOLDER;
}
}
options.matches = createMatches(node.filterData);
options.matches = createMatches(filterData);
data.label.setResource(label, options);
}

View File

@@ -387,13 +387,3 @@
overflow: hidden;
text-overflow: ellipsis
}
/* No workspace view */
.debug-viewlet > .noworkspace-view {
padding: 0 20px 0 20px;
}
.debug-viewlet > .noworkspace-view > p {
line-height: 1.5em;
}

View File

@@ -12,22 +12,21 @@ import { CancellationToken } from 'vs/base/common/cancellation';
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import { SuggestController } from 'vs/editor/contrib/suggest/suggestController';
import { ITextModel } from 'vs/editor/common/model';
import { Range } from 'vs/editor/common/core/range';
import { Position } from 'vs/editor/common/core/position';
import { registerEditorAction, ServicesAccessor, EditorAction } from 'vs/editor/browser/editorExtensions';
import { IModelService } from 'vs/editor/common/services/modelService';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IInstantiationService, createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { Panel } from 'vs/workbench/browser/panel';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { ICodeEditor, isCodeEditor } from 'vs/editor/browser/editorBrowser';
import { memoize } from 'vs/base/common/decorators';
import { dispose, IDisposable, Disposable } from 'vs/base/common/lifecycle';
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';
import { IDebugService, REPL_ID, DEBUG_SCHEME, CONTEXT_IN_DEBUG_REPL, IDebugSession, State, IReplElement, IDebugConfiguration } from 'vs/workbench/contrib/debug/common/debug';
import { IDebugService, DEBUG_SCHEME, CONTEXT_IN_DEBUG_REPL, IDebugSession, State, IReplElement, IDebugConfiguration, REPL_VIEW_ID } from 'vs/workbench/contrib/debug/common/debug';
import { HistoryNavigator } from 'vs/base/common/history';
import { IHistoryNavigationWidget } from 'vs/base/browser/history';
import { createAndBindHistoryNavigationWidgetScopedContextKeyService } from 'vs/platform/browser/contextScopedHistoryWidget';
@@ -37,9 +36,8 @@ import { IDecorationOptions } from 'vs/editor/common/editorCommon';
import { transparent, editorForeground } from 'vs/platform/theme/common/colorRegistry';
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
import { FocusSessionActionViewItem } from 'vs/workbench/contrib/debug/browser/debugActionViewItems';
import { CompletionContext, CompletionList, CompletionProviderRegistry } from 'vs/editor/common/modes';
import { CompletionContext, CompletionList, CompletionProviderRegistry, CompletionItem, completionKindFromString, CompletionItemKind } from 'vs/editor/common/modes';
import { first } from 'vs/base/common/arrays';
import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
import { ITreeNode, ITreeContextMenuEvent, IAsyncDataSource } from 'vs/base/browser/ui/tree/tree';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { LinkDetector } from 'vs/workbench/contrib/debug/browser/linkDetector';
@@ -55,6 +53,9 @@ import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService
import { PANEL_BACKGROUND } from 'vs/workbench/common/theme';
import { ReplDelegate, ReplVariablesRenderer, ReplSimpleElementsRenderer, ReplEvaluationInputsRenderer, ReplEvaluationResultsRenderer, ReplRawObjectsRenderer, ReplDataSource, ReplAccessibilityProvider } from 'vs/workbench/contrib/debug/browser/replViewer';
import { localize } from 'vs/nls';
import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IViewsService } from 'vs/workbench/common/views';
const $ = dom.$;
@@ -76,7 +77,8 @@ function revealLastElement(tree: WorkbenchAsyncDataTree<any, any, any>) {
}
const sessionsToIgnore = new Set<IDebugSession>();
export class Repl extends Panel implements IPrivateReplService, IHistoryNavigationWidget {
export class Repl extends ViewPane implements IPrivateReplService, IHistoryNavigationWidget {
_serviceBrand: undefined;
private static readonly REFRESH_DELAY = 100; // delay in ms to refresh the repl for new elements to show
@@ -99,21 +101,22 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati
private modelChangeListener: IDisposable = Disposable.None;
constructor(
options: IViewPaneOptions,
@IDebugService private readonly debugService: IDebugService,
@ITelemetryService telemetryService: ITelemetryService,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IInstantiationService instantiationService: IInstantiationService,
@IStorageService private readonly storageService: IStorageService,
@IThemeService protected themeService: IThemeService,
@IModelService private readonly modelService: IModelService,
@IContextKeyService private readonly contextKeyService: IContextKeyService,
@ICodeEditorService codeEditorService: ICodeEditorService,
@IContextMenuService private readonly contextMenuService: IContextMenuService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IContextMenuService contextMenuService: IContextMenuService,
@IConfigurationService configurationService: IConfigurationService,
@ITextResourcePropertiesService private readonly textResourcePropertiesService: ITextResourcePropertiesService,
@IClipboardService private readonly clipboardService: IClipboardService,
@IEditorService private readonly editorService: IEditorService
@IEditorService private readonly editorService: IEditorService,
@IKeybindingService keybindingService: IKeybindingService
) {
super(REPL_ID, telemetryService, themeService, storageService);
super({ ...(options as IViewPaneOptions), id: REPL_VIEW_ID, ariaHeaderLabel: localize('debugConsole', "Debug Console") }, keybindingService, contextMenuService, configurationService, contextKeyService, instantiationService);
this.history = new HistoryNavigator(JSON.parse(this.storageService.get(HISTORY_STORAGE_KEY, StorageScope.WORKSPACE, '[]')), 50);
codeEditorService.registerDecorationType(DECORATION_KEY, {});
@@ -141,7 +144,34 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati
const text = model.getValue();
const focusedStackFrame = this.debugService.getViewModel().focusedStackFrame;
const frameId = focusedStackFrame ? focusedStackFrame.frameId : undefined;
const suggestions = await session.completions(frameId, text, position, overwriteBefore, token);
const response = await session.completions(frameId, text, position, overwriteBefore, token);
const suggestions: CompletionItem[] = [];
const computeRange = (length: number) => Range.fromPositions(position.delta(0, -length), position);
if (response && response.body && response.body.targets) {
response.body.targets.forEach(item => {
if (item && item.label) {
suggestions.push({
label: item.label,
insertText: item.text || item.label,
kind: completionKindFromString(item.type || 'property'),
filterText: (item.start && item.length) ? text.substr(item.start, item.length).concat(item.label) : undefined,
range: computeRange(item.length || overwriteBefore),
sortText: item.sortText
});
}
});
}
const history = this.history.getHistory();
history.forEach(h => suggestions.push({
label: h,
insertText: h,
kind: CompletionItemKind.Text,
range: computeRange(h.length),
sortText: 'ZZZ'
}));
return { suggestions };
}
@@ -159,7 +189,7 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati
if (!input || input.state === State.Inactive) {
await this.selectSession(newSession);
}
this.updateTitleArea();
this.updateActions();
}));
this._register(this.themeService.onThemeChange(() => {
this.refreshReplElements(false);
@@ -167,7 +197,7 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati
this.updateInputDecoration();
}
}));
this._register(this.onDidChangeVisibility(visible => {
this._register(this.onDidChangeBodyVisibility(visible => {
if (!visible) {
dispose(this.model);
} else {
@@ -300,7 +330,7 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati
// Ignore inactive sessions which got cleared - so they are not shown any more
sessionsToIgnore.add(session);
await this.selectSession();
this.updateTitleArea();
this.updateActions();
}
}
this.replInput.focus();
@@ -317,7 +347,7 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati
this.replInputLineCount = 1;
if (shouldRelayout) {
// Trigger a layout to shrink a potential multi line input
this.layout(this.dimension);
this.layoutBody(this.dimension.height, this.dimension.width);
}
}
}
@@ -338,21 +368,21 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati
return removeAnsiEscapeCodes(text);
}
layout(dimension: dom.Dimension): void {
this.dimension = dimension;
protected layoutBody(height: number, width: number): void {
this.dimension = new dom.Dimension(width, height);
const replInputHeight = Repl.REPL_INPUT_LINE_HEIGHT * this.replInputLineCount;
if (this.tree) {
const lastElementVisible = this.tree.scrollTop + this.tree.renderHeight >= this.tree.scrollHeight;
const treeHeight = dimension.height - replInputHeight;
const treeHeight = height - replInputHeight;
this.tree.getHTMLElement().style.height = `${treeHeight}px`;
this.tree.layout(treeHeight, dimension.width);
this.tree.layout(treeHeight, width);
if (lastElementVisible) {
revealLastElement(this.tree);
}
}
this.replInputContainer.style.height = `${replInputHeight}px`;
this.replInput.layout({ width: dimension.width - 20, height: replInputHeight });
this.replInput.layout({ width: width - 20, height: replInputHeight });
}
focus(): void {
@@ -382,12 +412,12 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati
// --- Cached locals
@memoize
private get selectReplAction(): SelectReplAction {
return this.scopedInstantiationService.createInstance(SelectReplAction, SelectReplAction.ID, SelectReplAction.LABEL);
return this.instantiationService.createInstance(SelectReplAction, SelectReplAction.ID, SelectReplAction.LABEL);
}
@memoize
private get clearReplAction(): ClearReplAction {
return this.scopedInstantiationService.createInstance(ClearReplAction, ClearReplAction.ID, ClearReplAction.LABEL);
return this.instantiationService.createInstance(ClearReplAction, ClearReplAction.ID, ClearReplAction.LABEL);
}
@memoize
@@ -408,8 +438,7 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati
// --- Creation
create(parent: HTMLElement): void {
super.create(parent);
protected renderBody(parent: HTMLElement): void {
this.container = dom.append(parent, $('.repl'));
const treeContainer = dom.append(this.container, $('.repl-tree'));
this.createReplInput(this.container);
@@ -483,7 +512,7 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati
const lineCount = model ? Math.min(10, model.getLineCount()) : 1;
if (lineCount !== this.replInputLineCount) {
this.replInputLineCount = lineCount;
this.layout(this.dimension);
this.layoutBody(this.dimension.height, this.dimension.width);
}
}));
// We add the input decoration only when the focus is in the input #61126
@@ -562,7 +591,7 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati
this.replInput.setDecorations(DECORATION_KEY, decorations);
}
protected saveState(): void {
saveState(): void {
const replHistory = this.history.getHistory();
if (replHistory.length) {
this.storageService.store(HISTORY_STORAGE_KEY, JSON.stringify(replHistory), StorageScope.WORKSPACE);
@@ -684,8 +713,6 @@ class SelectReplAction extends Action {
} else {
await this.replService.selectSession(session);
}
return Promise.resolve(undefined);
}
}
@@ -694,14 +721,14 @@ export class ClearReplAction extends Action {
static readonly LABEL = localize('clearRepl', "Clear Console");
constructor(id: string, label: string,
@IPanelService private readonly panelService: IPanelService
@IViewsService private readonly viewsService: IViewsService
) {
super(id, label, 'debug-action codicon-clear-all');
}
async run(): Promise<any> {
const repl = <Repl>this.panelService.openPanel(REPL_ID);
await repl.clearRepl();
const view = await this.viewsService.openView(REPL_VIEW_ID) as Repl;
await view.clearRepl();
aria.status(localize('debugConsoleCleared', "Debug console was cleared"));
}
}

View File

@@ -14,7 +14,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { localize } from 'vs/nls';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { StartAction, RunAction, ConfigureAction } from 'vs/workbench/contrib/debug/browser/debugActions';
import { StartAction, ConfigureAction } from 'vs/workbench/contrib/debug/browser/debugActions';
import { IDebugService } from 'vs/workbench/contrib/debug/common/debug';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
@@ -23,15 +23,38 @@ import { equals } from 'vs/base/common/arrays';
import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { KeyCode } from 'vs/base/common/keyCodes';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
const $ = dom.$;
interface DebugStartMetrics {
debuggers?: string[];
}
type DebugStartMetricsClassification = {
debuggers?: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
};
function createClickElement(textContent: string, action: () => any): HTMLSpanElement {
const clickElement = $('span.click');
clickElement.textContent = textContent;
clickElement.onclick = action;
clickElement.tabIndex = 0;
clickElement.onkeyup = (e) => {
const keyboardEvent = new StandardKeyboardEvent(e);
if (keyboardEvent.keyCode === KeyCode.Enter || (keyboardEvent.keyCode === KeyCode.Space)) {
action();
}
};
return clickElement;
}
export class StartView extends ViewPane {
static ID = 'workbench.debug.startView';
static LABEL = localize('start', "Start");
private debugButton!: Button;
private runButton!: Button;
private firstMessageContainer!: HTMLElement;
private secondMessageContainer!: HTMLElement;
private clickElement: HTMLElement | undefined;
@@ -48,9 +71,11 @@ export class StartView extends ViewPane {
@IDebugService private readonly debugService: IDebugService,
@IEditorService private readonly editorService: IEditorService,
@IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService,
@IFileDialogService private readonly dialogService: IFileDialogService
@IFileDialogService private readonly dialogService: IFileDialogService,
@IInstantiationService instantiationService: IInstantiationService,
@ITelemetryService private readonly telemetryService: ITelemetryService
) {
super({ ...(options as IViewPaneOptions), ariaHeaderLabel: localize('debugStart', "Debug Start Section") }, keybindingService, contextMenuService, configurationService, contextKeyService);
super({ ...(options as IViewPaneOptions), ariaHeaderLabel: localize('debugStart', "Debug Start Section") }, keybindingService, contextMenuService, configurationService, contextKeyService, instantiationService);
this._register(editorService.onDidActiveEditorChange(() => this.updateView()));
this._register(this.debugService.getConfigurationManager().onDidRegisterDebugger(() => this.updateView()));
}
@@ -63,21 +88,13 @@ export class StartView extends ViewPane {
const enabled = this.debuggerLabels.length > 0;
this.debugButton.enabled = enabled;
this.runButton.enabled = enabled;
const debugKeybinding = this.keybindingService.lookupKeybinding(StartAction.ID);
let debugLabel = this.debuggerLabels.length !== 1 ? localize('debug', "Debug") : localize('debugWith', "Debug with {0}", this.debuggerLabels[0]);
let debugLabel = this.debuggerLabels.length !== 1 ? localize('debug', "Run and Debug") : localize('debugWith', "Run and Debug {0}", this.debuggerLabels[0]);
if (debugKeybinding) {
debugLabel += ` (${debugKeybinding.getLabel()})`;
}
this.debugButton.label = debugLabel;
let runLabel = this.debuggerLabels.length !== 1 ? localize('run', "Run") : localize('runWith', "Run with {0}", this.debuggerLabels[0]);
const runKeybinding = this.keybindingService.lookupKeybinding(RunAction.ID);
if (runKeybinding) {
runLabel += ` (${runKeybinding.getLabel()})`;
}
this.runButton.label = runLabel;
const emptyWorkbench = this.workspaceContextService.getWorkbenchState() === WorkbenchState.EMPTY;
this.firstMessageContainer.innerHTML = '';
this.secondMessageContainer.innerHTML = '';
@@ -85,13 +102,19 @@ export class StartView extends ViewPane {
this.secondMessageContainer.appendChild(secondMessageElement);
const setSecondMessage = () => {
secondMessageElement.textContent = localize('specifyHowToRun', "To further configure Debug and Run");
this.clickElement = this.createClickElement(localize('configure', " create a launch.json file."), () => this.commandService.executeCommand(ConfigureAction.ID));
secondMessageElement.textContent = localize('specifyHowToRun', "To customize Run and Debug");
this.clickElement = createClickElement(localize('configure', " create a launch.json file."), () => {
this.telemetryService.publicLog2<DebugStartMetrics, DebugStartMetricsClassification>('debugStart.configure', { debuggers: this.debuggerLabels });
this.commandService.executeCommand(ConfigureAction.ID);
});
this.secondMessageContainer.appendChild(this.clickElement);
};
const setSecondMessageWithFolder = () => {
secondMessageElement.textContent = localize('noLaunchConfiguration', "To further configure Debug and Run, ");
this.clickElement = this.createClickElement(localize('openFolder', " open a folder"), () => this.dialogService.pickFolderAndOpen({ forceNewWindow: false }));
secondMessageElement.textContent = localize('noLaunchConfiguration', "To customize Run and Debug, ");
this.clickElement = createClickElement(localize('openFolder', " open a folder"), () => {
this.telemetryService.publicLog2<DebugStartMetrics, DebugStartMetricsClassification>('debugStart.openFolder', { debuggers: this.debuggerLabels });
this.dialogService.pickFolderAndOpen({ forceNewWindow: false });
});
this.secondMessageContainer.appendChild(this.clickElement);
const moreText = $('span.moreText');
@@ -116,7 +139,10 @@ export class StartView extends ViewPane {
}
if (!enabled && emptyWorkbench) {
this.clickElement = this.createClickElement(localize('openFile', "Open a file"), () => this.dialogService.pickFileAndOpen({ forceNewWindow: false }));
this.clickElement = createClickElement(localize('openFile', "Open a file"), () => {
this.telemetryService.publicLog2<DebugStartMetrics, DebugStartMetricsClassification>('debugStart.openFile');
this.dialogService.pickFileAndOpen({ forceNewWindow: false });
});
this.firstMessageContainer.appendChild(this.clickElement);
const firstMessageElement = $('span');
this.firstMessageContainer.appendChild(firstMessageElement);
@@ -127,21 +153,6 @@ export class StartView extends ViewPane {
}
}
private createClickElement(textContent: string, action: () => any): HTMLSpanElement {
const clickElement = $('span.click');
clickElement.textContent = textContent;
clickElement.onclick = action;
clickElement.tabIndex = 0;
clickElement.onkeyup = (e) => {
const keyboardEvent = new StandardKeyboardEvent(e);
if (keyboardEvent.keyCode === KeyCode.Enter || (keyboardEvent.keyCode === KeyCode.Space)) {
action();
}
};
return clickElement;
}
protected renderBody(container: HTMLElement): void {
this.firstMessageContainer = $('.top-section');
container.appendChild(this.firstMessageContainer);
@@ -149,17 +160,11 @@ export class StartView extends ViewPane {
this.debugButton = new Button(container);
this._register(this.debugButton.onDidClick(() => {
this.commandService.executeCommand(StartAction.ID);
this.telemetryService.publicLog2<DebugStartMetrics, DebugStartMetricsClassification>('debugStart.runAndDebug', { debuggers: this.debuggerLabels });
}));
attachButtonStyler(this.debugButton, this.themeService);
this.runButton = new Button(container);
this.runButton.label = localize('run', "Run");
dom.addClass(container, 'debug-start-view');
this._register(this.runButton.onDidClick(() => {
this.commandService.executeCommand(RunAction.ID);
}));
attachButtonStyler(this.runButton, this.themeService);
this.secondMessageContainer = $('.section');
container.appendChild(this.secondMessageContainer);

View File

@@ -67,7 +67,7 @@ export class StatusBarColorProvider extends Themable implements IWorkbenchContri
// Container Colors
const backgroundColor = this.getColor(this.getColorKey(STATUS_BAR_NO_FOLDER_BACKGROUND, STATUS_BAR_DEBUGGING_BACKGROUND, STATUS_BAR_BACKGROUND));
container.style.backgroundColor = backgroundColor || '';
container.style.color = this.getColor(this.getColorKey(STATUS_BAR_NO_FOLDER_FOREGROUND, STATUS_BAR_DEBUGGING_FOREGROUND, STATUS_BAR_FOREGROUND));
container.style.color = this.getColor(this.getColorKey(STATUS_BAR_NO_FOLDER_FOREGROUND, STATUS_BAR_DEBUGGING_FOREGROUND, STATUS_BAR_FOREGROUND)) || '';
// Border Color
const borderColor = this.getColor(this.getColorKey(STATUS_BAR_NO_FOLDER_BORDER, STATUS_BAR_DEBUGGING_BORDER, STATUS_BAR_BORDER)) || this.getColor(contrastBorder);
@@ -103,7 +103,7 @@ export class StatusBarColorProvider extends Themable implements IWorkbenchContri
}
}
export function isStatusbarInDebugMode(debugService: IDebugService): boolean {
function isStatusbarInDebugMode(debugService: IDebugService): boolean {
if (debugService.state === State.Inactive || debugService.state === State.Initializing) {
return false;
}

View File

@@ -50,11 +50,11 @@ export class VariablesView extends ViewPane {
@IDebugService private readonly debugService: IDebugService,
@IKeybindingService keybindingService: IKeybindingService,
@IConfigurationService configurationService: IConfigurationService,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IInstantiationService instantiationService: IInstantiationService,
@IClipboardService private readonly clipboardService: IClipboardService,
@IContextKeyService contextKeyService: IContextKeyService
) {
super({ ...(options as IViewPaneOptions), ariaHeaderLabel: nls.localize('variablesSection', "Variables Section") }, keybindingService, contextMenuService, configurationService, contextKeyService);
super({ ...(options as IViewPaneOptions), ariaHeaderLabel: nls.localize('variablesSection', "Variables Section") }, keybindingService, contextMenuService, configurationService, contextKeyService, instantiationService);
// Use scheduler to prevent unnecessary flashing
this.onFocusStackFrameScheduler = new RunOnceScheduler(async () => {

View File

@@ -33,6 +33,8 @@ import { dispose } from 'vs/base/common/lifecycle';
import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme';
const MAX_VALUE_RENDER_LENGTH_IN_VIEWLET = 1024;
let ignoreVariableSetEmitter = false;
let useCachedEvaluation = false;
export class WatchExpressionsView extends ViewPane {
@@ -45,11 +47,11 @@ export class WatchExpressionsView extends ViewPane {
@IContextMenuService contextMenuService: IContextMenuService,
@IDebugService private readonly debugService: IDebugService,
@IKeybindingService keybindingService: IKeybindingService,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IInstantiationService instantiationService: IInstantiationService,
@IConfigurationService configurationService: IConfigurationService,
@IContextKeyService contextKeyService: IContextKeyService,
) {
super({ ...(options as IViewPaneOptions), ariaHeaderLabel: nls.localize('watchExpressionsSection', "Watch Expressions Section") }, keybindingService, contextMenuService, configurationService, contextKeyService);
super({ ...(options as IViewPaneOptions), ariaHeaderLabel: nls.localize('watchExpressionsSection', "Watch Expressions Section") }, keybindingService, contextMenuService, configurationService, contextKeyService, instantiationService);
this.onWatchExpressionsUpdatedScheduler = new RunOnceScheduler(() => {
this.needsRefresh = false;
@@ -67,7 +69,16 @@ export class WatchExpressionsView extends ViewPane {
ariaLabel: nls.localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'watchAriaTreeLabel' }, "Debug Watch Expressions"),
accessibilityProvider: new WatchExpressionsAccessibilityProvider(),
identityProvider: { getId: (element: IExpression) => element.getId() },
keyboardNavigationLabelProvider: { getKeyboardNavigationLabel: (e: IExpression) => e },
keyboardNavigationLabelProvider: {
getKeyboardNavigationLabel: (e: IExpression) => {
if (e === this.debugService.getViewModel().getSelectedExpression()) {
// Don't filter input box
return undefined;
}
return e;
}
},
dnd: new WatchExpressionsDragAndDrop(this.debugService),
overrideStyles: {
listBackground: SIDE_BAR_BACKGROUND
@@ -90,7 +101,12 @@ export class WatchExpressionsView extends ViewPane {
if (!this.isBodyVisible()) {
this.needsRefresh = true;
} else {
if (we && !we.name) {
// We are adding a new input box, no need to re-evaluate watch expressions
useCachedEvaluation = true;
}
await this.tree.updateChildren();
useCachedEvaluation = false;
if (we instanceof Expression) {
this.tree.reveal(we);
}
@@ -106,7 +122,11 @@ export class WatchExpressionsView extends ViewPane {
this.onWatchExpressionsUpdatedScheduler.schedule();
}
}));
this._register(variableSetEmitter.event(() => this.tree.updateChildren()));
this._register(variableSetEmitter.event(() => {
if (!ignoreVariableSetEmitter) {
this.tree.updateChildren();
}
}));
this._register(this.onDidChangeBodyVisibility(visible => {
if (visible && this.needsRefresh) {
@@ -192,7 +212,7 @@ export class WatchExpressionsView extends ViewPane {
class WatchExpressionsDelegate implements IListVirtualDelegate<IExpression> {
getHeight(element: IExpression): number {
getHeight(_element: IExpression): number {
return 22;
}
@@ -221,7 +241,7 @@ class WatchExpressionsDataSource implements IAsyncDataSource<IDebugService, IExp
const debugService = element as IDebugService;
const watchExpressions = debugService.getModel().getWatchExpressions();
const viewModel = debugService.getViewModel();
return Promise.all(watchExpressions.map(we => !!we.name
return Promise.all(watchExpressions.map(we => !!we.name && !useCachedEvaluation
? we.evaluate(viewModel.focusedSession!, viewModel.focusedStackFrame!, 'watch').then(() => we)
: Promise.resolve(we)));
}
@@ -259,7 +279,9 @@ export class WatchExpressionsRenderer extends AbstractExpressionsRenderer {
onFinish: (value: string, success: boolean) => {
if (success && value) {
this.debugService.renameWatchExpression(expression.getId(), value);
ignoreVariableSetEmitter = true;
variableSetEmitter.fire();
ignoreVariableSetEmitter = false;
} else if (!expression.name) {
this.debugService.removeWatchExpressions(expression.getId());
}

View File

@@ -5,20 +5,19 @@
import { Emitter, Event } from 'vs/base/common/event';
import { IDebugAdapter } from 'vs/workbench/contrib/debug/common/debug';
import { timeout, Queue } from 'vs/base/common/async';
import { timeout } from 'vs/base/common/async';
/**
* Abstract implementation of the low level API for a debug adapter.
* Missing is how this API communicates with the debug adapter.
*/
export abstract class AbstractDebugAdapter implements IDebugAdapter {
private sequence: number;
private pendingRequests = new Map<number, (e: DebugProtocol.Response) => void>();
private requestCallback: ((request: DebugProtocol.Request) => void) | undefined;
private eventCallback: ((request: DebugProtocol.Event) => void) | undefined;
private messageCallback: ((message: DebugProtocol.ProtocolMessage) => void) | undefined;
private readonly queue = new Queue();
private queue: DebugProtocol.ProtocolMessage[] = [];
protected readonly _onError = new Emitter<Error>();
protected readonly _onExit = new Emitter<number | null>();
@@ -64,8 +63,7 @@ export abstract class AbstractDebugAdapter implements IDebugAdapter {
sendResponse(response: DebugProtocol.Response): void {
if (response.seq > 0) {
this._onError.fire(new Error(`attempt to send more than one response for command ${response.command}`));
}
else {
} else {
this.internalSend('response', response);
}
}
@@ -107,35 +105,73 @@ export abstract class AbstractDebugAdapter implements IDebugAdapter {
acceptMessage(message: DebugProtocol.ProtocolMessage): void {
if (this.messageCallback) {
this.messageCallback(message);
} else {
this.queue.push(message);
if (this.queue.length === 1) {
// first item = need to start processing loop
this.processQueue();
}
}
else {
this.queue.queue(() => {
switch (message.type) {
case 'event':
if (this.eventCallback) {
this.eventCallback(<DebugProtocol.Event>message);
}
break;
case 'request':
if (this.requestCallback) {
this.requestCallback(<DebugProtocol.Request>message);
}
break;
case 'response':
const response = <DebugProtocol.Response>message;
const clb = this.pendingRequests.get(response.request_seq);
if (clb) {
this.pendingRequests.delete(response.request_seq);
clb(response);
}
break;
}
}
// Artificially queueing protocol messages guarantees that any microtasks for
// previous message finish before next message is processed. This is essential
// to guarantee ordering when using promises anywhere along the call path.
return timeout(0);
});
/**
* Returns whether we should insert a timeout between processing messageA
* and messageB. Artificially queueing protocol messages guarantees that any
* microtasks for previous message finish before next message is processed.
* This is essential ordering when using promises anywhere along the call path.
*
* For example, take the following, where `chooseAndSendGreeting` returns
* a person name and then emits a greeting event:
*
* ```
* let person: string;
* adapter.onGreeting(() => console.log('hello', person));
* person = await adapter.chooseAndSendGreeting();
* ```
*
* Because the event is dispatched synchronously, it may fire before person
* is assigned if they're processed in the same task. Inserting a task
* boundary avoids this issue.
*/
protected needsTaskBoundaryBetween(messageA: DebugProtocol.ProtocolMessage, messageB: DebugProtocol.ProtocolMessage) {
return messageA.type !== 'event' || messageB.type !== 'event';
}
/**
* Reads and dispatches items from the queue until it is empty.
*/
private async processQueue() {
let message: DebugProtocol.ProtocolMessage | undefined;
while (this.queue.length) {
if (!message || this.needsTaskBoundaryBetween(this.queue[0], message)) {
await timeout(0);
}
message = this.queue.shift();
if (!message) {
return; // may have been disposed of
}
switch (message.type) {
case 'event':
if (this.eventCallback) {
this.eventCallback(<DebugProtocol.Event>message);
}
break;
case 'request':
if (this.requestCallback) {
this.requestCallback(<DebugProtocol.Request>message);
}
break;
case 'response':
const response = <DebugProtocol.Response>message;
const clb = this.pendingRequests.get(response.request_seq);
if (clb) {
this.pendingRequests.delete(response.request_seq);
clb(response);
}
break;
}
}
}
@@ -172,6 +208,6 @@ export abstract class AbstractDebugAdapter implements IDebugAdapter {
}
dispose(): void {
this.queue.dispose();
this.queue = [];
}
}

View File

@@ -13,7 +13,6 @@ import * as editorCommon from 'vs/editor/common/editorCommon';
import { ITextModel as EditorIModel } from 'vs/editor/common/model';
import { IEditor, ITextEditor } from 'vs/workbench/common/editor';
import { Position, IPosition } from 'vs/editor/common/core/position';
import { CompletionItem } from 'vs/editor/common/modes';
import { Source } from 'vs/workbench/contrib/debug/common/debugSource';
import { Range, IRange } from 'vs/editor/common/core/range';
import { RawContextKey } from 'vs/platform/contextkey/common/contextkey';
@@ -32,7 +31,8 @@ export const WATCH_VIEW_ID = 'workbench.debug.watchExpressionsView';
export const CALLSTACK_VIEW_ID = 'workbench.debug.callStackView';
export const LOADED_SCRIPTS_VIEW_ID = 'workbench.debug.loadedScriptsView';
export const BREAKPOINTS_VIEW_ID = 'workbench.debug.breakPointsView';
export const REPL_ID = 'workbench.panel.repl';
export const DEBUG_PANEL_ID = 'workbench.panel.repl';
export const REPL_VIEW_ID = 'workbench.panel.repl.view';
export const DEBUG_SERVICE_ID = 'debugService';
export const CONTEXT_DEBUG_TYPE = new RawContextKey<string>('debugType', undefined);
export const CONTEXT_DEBUG_CONFIGURATION_TYPE = new RawContextKey<string>('debugConfigurationType', undefined);
@@ -234,7 +234,7 @@ export interface IDebugSession extends ITreeElement {
pause(threadId: number): Promise<void>;
terminateThreads(threadIds: number[]): Promise<void>;
completions(frameId: number | undefined, text: string, position: Position, overwriteBefore: number, token: CancellationToken): Promise<CompletionItem[]>;
completions(frameId: number | undefined, text: string, position: Position, overwriteBefore: number, token: CancellationToken): Promise<DebugProtocol.CompletionsResponse>;
setVariable(variablesReference: number | undefined, name: string, value: string): Promise<DebugProtocol.SetVariableResponse>;
loadSource(resource: uri): Promise<DebugProtocol.SourceResponse>;
getLoadedSources(): Promise<Source[]>;
@@ -442,7 +442,7 @@ export interface IBreakpointsChangeEvent {
added?: Array<IBreakpoint | IFunctionBreakpoint | IDataBreakpoint>;
removed?: Array<IBreakpoint | IFunctionBreakpoint | IDataBreakpoint>;
changed?: Array<IBreakpoint | IFunctionBreakpoint | IDataBreakpoint>;
sessionOnly?: boolean;
sessionOnly: boolean;
}
// Debug configuration interfaces
@@ -463,6 +463,7 @@ export interface IDebugConfiguration {
fontFamily: string;
lineHeight: number;
wordWrap: boolean;
closeOnEnd: boolean;
};
focusWindowOnBreak: boolean;
onTaskErrors: 'debugAnyway' | 'showErrors' | 'prompt';
@@ -598,6 +599,7 @@ export interface IDebuggerContribution extends IPlatformSpecificAdapterContribut
export interface IDebugConfigurationProvider {
readonly type: string;
resolveDebugConfiguration?(folderUri: uri | undefined, debugConfiguration: IConfig, token: CancellationToken): Promise<IConfig | null | undefined>;
resolveDebugConfigurationWithSubstitutedVariables?(folderUri: uri | undefined, debugConfiguration: IConfig, token: CancellationToken): Promise<IConfig | null | undefined>;
provideDebugConfigurations?(folderUri: uri | undefined, token: CancellationToken): Promise<IConfig[]>;
debugAdapterExecutable?(folderUri: uri | undefined): Promise<IAdapterDescriptor>; // TODO@AW legacy
}

View File

@@ -160,10 +160,6 @@ export class ExpressionContainer implements IExpressionContainer {
this.session = session;
try {
const response = await session.evaluate(expression, stackFrame ? stackFrame.frameId : undefined, context);
if (response && response.success === false) {
this.value = response.message || '';
return false;
}
if (response && response.body) {
this.value = response.body.result || '';
@@ -1014,7 +1010,7 @@ export class DebugModel implements IDebugModel {
this.sortAndDeDup();
if (fireEvent) {
this._onDidChangeBreakpoints.fire({ added: newBreakpoints });
this._onDidChangeBreakpoints.fire({ added: newBreakpoints, sessionOnly: false });
}
return newBreakpoints;
@@ -1022,7 +1018,7 @@ export class DebugModel implements IDebugModel {
removeBreakpoints(toRemove: IBreakpoint[]): void {
this.breakpoints = this.breakpoints.filter(bp => !toRemove.some(toRemove => toRemove.getId() === bp.getId()));
this._onDidChangeBreakpoints.fire({ removed: toRemove });
this._onDidChangeBreakpoints.fire({ removed: toRemove, sessionOnly: false });
}
updateBreakpoints(data: Map<string, IBreakpointUpdateData>): void {
@@ -1035,7 +1031,7 @@ export class DebugModel implements IDebugModel {
}
});
this.sortAndDeDup();
this._onDidChangeBreakpoints.fire({ changed: updated });
this._onDidChangeBreakpoints.fire({ changed: updated, sessionOnly: false });
}
setBreakpointSessionData(sessionId: string, capabilites: DebugProtocol.Capabilities, data: Map<string, DebugProtocol.Breakpoint> | undefined): void {
@@ -1104,7 +1100,7 @@ export class DebugModel implements IDebugModel {
this.breakpointsActivated = true;
}
this._onDidChangeBreakpoints.fire({ changed: changed });
this._onDidChangeBreakpoints.fire({ changed: changed, sessionOnly: false });
}
}
@@ -1133,13 +1129,13 @@ export class DebugModel implements IDebugModel {
this.breakpointsActivated = true;
}
this._onDidChangeBreakpoints.fire({ changed: changed });
this._onDidChangeBreakpoints.fire({ changed: changed, sessionOnly: false });
}
addFunctionBreakpoint(functionName: string, id?: string): IFunctionBreakpoint {
const newFunctionBreakpoint = new FunctionBreakpoint(functionName, true, undefined, undefined, undefined, id);
this.functionBreakpoints.push(newFunctionBreakpoint);
this._onDidChangeBreakpoints.fire({ added: [newFunctionBreakpoint] });
this._onDidChangeBreakpoints.fire({ added: [newFunctionBreakpoint], sessionOnly: false });
return newFunctionBreakpoint;
}
@@ -1148,7 +1144,7 @@ export class DebugModel implements IDebugModel {
const functionBreakpoint = this.functionBreakpoints.filter(fbp => fbp.getId() === id).pop();
if (functionBreakpoint) {
functionBreakpoint.name = name;
this._onDidChangeBreakpoints.fire({ changed: [functionBreakpoint] });
this._onDidChangeBreakpoints.fire({ changed: [functionBreakpoint], sessionOnly: false });
}
}
@@ -1161,13 +1157,13 @@ export class DebugModel implements IDebugModel {
removed = this.functionBreakpoints;
this.functionBreakpoints = [];
}
this._onDidChangeBreakpoints.fire({ removed });
this._onDidChangeBreakpoints.fire({ removed, sessionOnly: false });
}
addDataBreakpoint(label: string, dataId: string, canPersist: boolean, accessTypes: DebugProtocol.DataBreakpointAccessType[] | undefined): void {
const newDataBreakpoint = new DataBreakpoint(label, dataId, canPersist, true, undefined, undefined, undefined, accessTypes);
this.dataBreakopints.push(newDataBreakpoint);
this._onDidChangeBreakpoints.fire({ added: [newDataBreakpoint] });
this._onDidChangeBreakpoints.fire({ added: [newDataBreakpoint], sessionOnly: false });
}
removeDataBreakpoints(id?: string): void {
@@ -1179,15 +1175,15 @@ export class DebugModel implements IDebugModel {
removed = this.dataBreakopints;
this.dataBreakopints = [];
}
this._onDidChangeBreakpoints.fire({ removed });
this._onDidChangeBreakpoints.fire({ removed, sessionOnly: false });
}
getWatchExpressions(): Expression[] {
return this.watchExpressions;
}
addWatchExpression(name: string): IExpression {
const we = new Expression(name);
addWatchExpression(name?: string): IExpression {
const we = new Expression(name || '');
this.watchExpressions.push(we);
this._onDidChangeWatchExpressions.fire(we);

View File

@@ -21,7 +21,7 @@ export const debuggersExtPoint = extensionsRegistry.ExtensionsRegistry.registerE
items: {
additionalProperties: false,
type: 'object',
defaultSnippets: [{ body: { type: '', program: '', runtime: '', enableBreakpointsFor: { languageIds: [''] } } }],
defaultSnippets: [{ body: { type: '', program: '', runtime: '' } }],
properties: {
type: {
description: nls.localize('vscode.extension.contributes.debuggers.type', "Unique identifier for this debug adapter."),
@@ -59,10 +59,6 @@ export const debuggersExtPoint = extensionsRegistry.ExtensionsRegistry.registerE
description: nls.localize('vscode.extension.contributes.debuggers.languages', "List of languages for which the debug extension could be considered the \"default debugger\"."),
type: 'array'
},
adapterExecutableCommand: {
description: nls.localize('vscode.extension.contributes.debuggers.adapterExecutableCommand', "If specified VS Code will call this command to determine the executable path of the debug adapter and the arguments to pass."),
type: 'string'
},
configurationSnippets: {
description: nls.localize('vscode.extension.contributes.debuggers.configurationSnippets', "Snippets for adding new configurations in \'launch.json\'."),
type: 'array'

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { equalsIgnoreCase } from 'vs/base/common/strings';
import { IDebuggerContribution, IDebugSession } from 'vs/workbench/contrib/debug/common/debug';
import { IDebuggerContribution, IDebugSession, IConfigPresentation } from 'vs/workbench/contrib/debug/common/debug';
import { URI as uri } from 'vs/base/common/uri';
import { isAbsolute } from 'vs/base/common/path';
import { deepClone } from 'vs/base/common/objects';
@@ -236,3 +236,31 @@ function convertPaths(msg: DebugProtocol.ProtocolMessage, fixSourcePath: (toDA:
break;
}
}
export function getVisibleAndSorted<T extends { presentation?: IConfigPresentation }>(array: T[]): T[] {
return array.filter(config => !config.presentation?.hidden).sort((first, second) => {
if (!first.presentation) {
return 1;
}
if (!second.presentation) {
return -1;
}
if (!first.presentation.group) {
return 1;
}
if (!second.presentation.group) {
return -1;
}
if (first.presentation.group !== second.presentation.group) {
return first.presentation.group.localeCompare(second.presentation.group);
}
if (typeof first.presentation.order !== 'number') {
return 1;
}
if (typeof second.presentation.order !== 'number') {
return -1;
}
return first.presentation.order - second.presentation.order;
});
}

View File

@@ -30,7 +30,10 @@ export class Debugger implements IDebugger {
private mergedExtensionDescriptions: IExtensionDescription[] = [];
private mainExtensionDescription: IExtensionDescription | undefined;
constructor(private configurationManager: IConfigurationManager, dbgContribution: IDebuggerContribution, extensionDescription: IExtensionDescription,
constructor(
private configurationManager: IConfigurationManager,
dbgContribution: IDebuggerContribution,
extensionDescription: IExtensionDescription,
@IConfigurationService private readonly configurationService: IConfigurationService,
@ITextResourcePropertiesService private readonly resourcePropertiesService: ITextResourcePropertiesService,
@IConfigurationResolverService private readonly configurationResolverService: IConfigurationResolverService,
@@ -41,7 +44,7 @@ export class Debugger implements IDebugger {
this.merge(dbgContribution, extensionDescription);
}
public merge(otherDebuggerContribution: IDebuggerContribution, extensionDescription: IExtensionDescription): void {
merge(otherDebuggerContribution: IDebuggerContribution, extensionDescription: IExtensionDescription): void {
/**
* Copies all properties of source into destination. The optional parameter "overwrite" allows to control
@@ -92,7 +95,7 @@ export class Debugger implements IDebugger {
}
}
public createDebugAdapter(session: IDebugSession): Promise<IDebugAdapter> {
createDebugAdapter(session: IDebugSession): Promise<IDebugAdapter> {
return this.configurationManager.activateDebuggers('onDebugAdapterProtocolTracker', this.type).then(_ => {
const da = this.configurationManager.createDebugAdapter(session);
if (da) {
@@ -172,7 +175,7 @@ export class Debugger implements IDebugger {
return Promise.resolve(content);
}
public getMainExtensionDescriptor(): IExtensionDescription {
getMainExtensionDescriptor(): IExtensionDescription {
return this.mainExtensionDescription || this.mergedExtensionDescriptions[0];
}

View File

@@ -5,7 +5,6 @@
import * as cp from 'child_process';
import * as env from 'vs/base/common/platform';
import { getSystemShell } from 'vs/workbench/contrib/terminal/node/terminal';
import { WindowsExternalTerminalService, MacExternalTerminalService, LinuxExternalTerminalService } from 'vs/workbench/contrib/externalTerminal/node/externalTerminalService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IExternalTerminalService } from 'vs/workbench/contrib/externalTerminal/common/externalTerminal';
@@ -76,35 +75,22 @@ export function hasChildProcesses(processId: number | undefined): Promise<boolea
const enum ShellType { cmd, powershell, bash }
export function prepareCommand(args: DebugProtocol.RunInTerminalRequestArguments, shell: string, configProvider: ExtHostConfigProvider): string {
export function prepareCommand(args: DebugProtocol.RunInTerminalRequestArguments, shell: string): string {
let shellType = env.isWindows ? ShellType.cmd : ShellType.bash; // pick a good default
if (shell) {
const config = configProvider.getConfiguration('terminal');
// get the shell configuration for the current platform
const shell_config = config.integrated.shell;
if (env.isWindows) {
shell = shell_config.windows || getSystemShell(env.Platform.Windows);
} else if (env.isLinux) {
shell = shell_config.linux || getSystemShell(env.Platform.Linux);
} else if (env.isMacintosh) {
shell = shell_config.osx || getSystemShell(env.Platform.Mac);
} else {
throw new Error('Unknown platform');
}
}
shell = shell.trim().toLowerCase();
// try to determine the shell type
shell = shell.trim().toLowerCase();
let shellType;
if (shell.indexOf('powershell') >= 0 || shell.indexOf('pwsh') >= 0) {
shellType = ShellType.powershell;
} else if (shell.indexOf('cmd.exe') >= 0) {
shellType = ShellType.cmd;
} else if (shell.indexOf('bash') >= 0) {
shellType = ShellType.bash;
} else if (env.isWindows) {
shellType = ShellType.cmd; // pick a good default for Windows
} else {
shellType = ShellType.bash; // pick a good default for anything else
}
let quote: (s: string) => string;

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { replaceWhitespace, renderExpressionValue, renderVariable } from 'vs/workbench/contrib/debug/browser/baseDebugView';
import { replaceWhitespace, renderExpressionValue, renderVariable, renderViewTree } from 'vs/workbench/contrib/debug/browser/baseDebugView';
import * as dom from 'vs/base/browser/dom';
import { Expression, Variable, Scope, StackFrame, Thread } from 'vs/workbench/contrib/debug/common/debugModel';
import { MockSession } from 'vs/workbench/contrib/debug/test/common/mockDebug';
@@ -32,6 +32,16 @@ suite('Debug - Base Debug View', () => {
assert.equal(replaceWhitespace('hey \r\t\n\t\t\n there'), 'hey \\r\\t\\n\\t\\t\\n there');
});
test('render view tree', () => {
const container = $('.container');
const treeContainer = renderViewTree(container);
assert.equal(treeContainer.className, 'debug-view-content');
assert.equal(container.childElementCount, 1);
assert.equal(container.firstChild, treeContainer);
assert.equal(treeContainer instanceof HTMLDivElement, true);
});
test.skip('render expression value', () => { // {{SQL CARBON EDIT}} skip test
let container = $('.container');
renderExpressionValue('render \n me', container, { showHover: true, preserveWhitespace: true });

View File

@@ -0,0 +1,349 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { URI as uri } from 'vs/base/common/uri';
import { DebugModel, Breakpoint } from 'vs/workbench/contrib/debug/common/debugModel';
import { DebugSession } from 'vs/workbench/contrib/debug/browser/debugSession';
import { NullOpenerService } from 'vs/platform/opener/common/opener';
import { getExpandedBodySize, getBreakpointMessageAndClassName } from 'vs/workbench/contrib/debug/browser/breakpointsView';
import { dispose } from 'vs/base/common/lifecycle';
import { Range } from 'vs/editor/common/core/range';
import { IBreakpointData, IDebugSessionOptions, IBreakpointUpdateData, State } from 'vs/workbench/contrib/debug/common/debug';
import { TextModel } from 'vs/editor/common/model/textModel';
import { LanguageIdentifier, LanguageId } from 'vs/editor/common/modes';
import { createBreakpointDecorations } from 'vs/workbench/contrib/debug/browser/breakpointEditorContribution';
import { OverviewRulerLane } from 'vs/editor/common/model';
import { MarkdownString } from 'vs/base/common/htmlContent';
function createMockSession(model: DebugModel, name = 'mockSession', options?: IDebugSessionOptions): DebugSession {
return new DebugSession({ resolved: { name, type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, options, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, NullOpenerService, undefined!);
}
function addBreakpointsAndCheckEvents(model: DebugModel, uri: uri, data: IBreakpointData[]): void {
let eventCount = 0;
const toDispose = model.onDidChangeBreakpoints(e => {
assert.equal(e?.sessionOnly, false);
assert.equal(e?.changed, undefined);
assert.equal(e?.removed, undefined);
const added = e?.added;
assert.notEqual(added, undefined);
assert.equal(added!.length, data.length);
eventCount++;
dispose(toDispose);
for (let i = 0; i < data.length; i++) {
assert.equal(e!.added![i] instanceof Breakpoint, true);
assert.equal((e!.added![i] as Breakpoint).lineNumber, data[i].lineNumber);
}
});
model.addBreakpoints(uri, data);
assert.equal(eventCount, 1);
}
suite('Debug - Breakpoints', () => {
let model: DebugModel;
setup(() => {
model = new DebugModel([], [], [], [], [], <any>{ isDirty: (e: any) => false });
});
// Breakpoints
test('simple', () => {
const modelUri = uri.file('/myfolder/myfile.js');
addBreakpointsAndCheckEvents(model, modelUri, [{ lineNumber: 5, enabled: true }, { lineNumber: 10, enabled: false }]);
assert.equal(model.areBreakpointsActivated(), true);
assert.equal(model.getBreakpoints().length, 2);
let eventCount = 0;
const toDispose = model.onDidChangeBreakpoints(e => {
eventCount++;
assert.equal(e?.added, undefined);
assert.equal(e?.sessionOnly, false);
assert.equal(e?.removed?.length, 2);
assert.equal(e?.changed, undefined);
dispose(toDispose);
});
model.removeBreakpoints(model.getBreakpoints());
assert.equal(eventCount, 1);
assert.equal(model.getBreakpoints().length, 0);
});
test('toggling', () => {
const modelUri = uri.file('/myfolder/myfile.js');
addBreakpointsAndCheckEvents(model, modelUri, [{ lineNumber: 5, enabled: true }, { lineNumber: 10, enabled: false }]);
addBreakpointsAndCheckEvents(model, modelUri, [{ lineNumber: 12, enabled: true, condition: 'fake condition' }]);
assert.equal(model.getBreakpoints().length, 3);
const bp = model.getBreakpoints().pop();
if (bp) {
model.removeBreakpoints([bp]);
}
assert.equal(model.getBreakpoints().length, 2);
model.setBreakpointsActivated(false);
assert.equal(model.areBreakpointsActivated(), false);
model.setBreakpointsActivated(true);
assert.equal(model.areBreakpointsActivated(), true);
});
test('two files', () => {
const modelUri1 = uri.file('/myfolder/my file first.js');
const modelUri2 = uri.file('/secondfolder/second/second file.js');
addBreakpointsAndCheckEvents(model, modelUri1, [{ lineNumber: 5, enabled: true }, { lineNumber: 10, enabled: false }]);
assert.equal(getExpandedBodySize(model), 44);
addBreakpointsAndCheckEvents(model, modelUri2, [{ lineNumber: 1, enabled: true }, { lineNumber: 2, enabled: true }, { lineNumber: 3, enabled: false }]);
assert.equal(getExpandedBodySize(model), 110);
assert.equal(model.getBreakpoints().length, 5);
assert.equal(model.getBreakpoints({ uri: modelUri1 }).length, 2);
assert.equal(model.getBreakpoints({ uri: modelUri2 }).length, 3);
assert.equal(model.getBreakpoints({ lineNumber: 5 }).length, 1);
assert.equal(model.getBreakpoints({ column: 5 }).length, 0);
const bp = model.getBreakpoints()[0];
const update = new Map<string, IBreakpointUpdateData>();
update.set(bp.getId(), { lineNumber: 100 });
let eventFired = false;
const toDispose = model.onDidChangeBreakpoints(e => {
eventFired = true;
assert.equal(e?.added, undefined);
assert.equal(e?.removed, undefined);
assert.equal(e?.changed?.length, 1);
dispose(toDispose);
});
model.updateBreakpoints(update);
assert.equal(eventFired, true);
assert.equal(bp.lineNumber, 100);
assert.equal(model.getBreakpoints({ enabledOnly: true }).length, 3);
model.enableOrDisableAllBreakpoints(false);
model.getBreakpoints().forEach(bp => {
assert.equal(bp.enabled, false);
});
assert.equal(model.getBreakpoints({ enabledOnly: true }).length, 0);
model.setEnablement(bp, true);
assert.equal(bp.enabled, true);
model.removeBreakpoints(model.getBreakpoints({ uri: modelUri1 }));
assert.equal(getExpandedBodySize(model), 66);
assert.equal(model.getBreakpoints().length, 3);
});
test('conditions', () => {
const modelUri1 = uri.file('/myfolder/my file first.js');
addBreakpointsAndCheckEvents(model, modelUri1, [{ lineNumber: 5, condition: 'i < 5', hitCondition: '17' }, { lineNumber: 10, condition: 'j < 3' }]);
const breakpoints = model.getBreakpoints();
assert.equal(breakpoints[0].condition, 'i < 5');
assert.equal(breakpoints[0].hitCondition, '17');
assert.equal(breakpoints[1].condition, 'j < 3');
assert.equal(!!breakpoints[1].hitCondition, false);
assert.equal(model.getBreakpoints().length, 2);
model.removeBreakpoints(model.getBreakpoints());
assert.equal(model.getBreakpoints().length, 0);
});
test('function breakpoints', () => {
model.addFunctionBreakpoint('foo', '1');
model.addFunctionBreakpoint('bar', '2');
model.renameFunctionBreakpoint('1', 'fooUpdated');
model.renameFunctionBreakpoint('2', 'barUpdated');
const functionBps = model.getFunctionBreakpoints();
assert.equal(functionBps[0].name, 'fooUpdated');
assert.equal(functionBps[1].name, 'barUpdated');
model.removeFunctionBreakpoints();
assert.equal(model.getFunctionBreakpoints().length, 0);
});
test('multiple sessions', () => {
const modelUri = uri.file('/myfolder/myfile.js');
addBreakpointsAndCheckEvents(model, modelUri, [{ lineNumber: 5, enabled: true, condition: 'x > 5' }, { lineNumber: 10, enabled: false }]);
const breakpoints = model.getBreakpoints();
const session = createMockSession(model);
const data = new Map<string, DebugProtocol.Breakpoint>();
assert.equal(breakpoints[0].lineNumber, 5);
assert.equal(breakpoints[1].lineNumber, 10);
data.set(breakpoints[0].getId(), { verified: false, line: 10 });
data.set(breakpoints[1].getId(), { verified: true, line: 50 });
model.setBreakpointSessionData(session.getId(), {}, data);
assert.equal(breakpoints[0].lineNumber, 5);
assert.equal(breakpoints[1].lineNumber, 50);
const session2 = createMockSession(model);
const data2 = new Map<string, DebugProtocol.Breakpoint>();
data2.set(breakpoints[0].getId(), { verified: true, line: 100 });
data2.set(breakpoints[1].getId(), { verified: true, line: 500 });
model.setBreakpointSessionData(session2.getId(), {}, data2);
// Breakpoint is verified only once, show that line
assert.equal(breakpoints[0].lineNumber, 100);
// Breakpoint is verified two times, show the original line
assert.equal(breakpoints[1].lineNumber, 10);
model.setBreakpointSessionData(session.getId(), {}, undefined);
// No more double session verification
assert.equal(breakpoints[0].lineNumber, 100);
assert.equal(breakpoints[1].lineNumber, 500);
assert.equal(breakpoints[0].supported, false);
const data3 = new Map<string, DebugProtocol.Breakpoint>();
data3.set(breakpoints[0].getId(), { verified: true, line: 500 });
model.setBreakpointSessionData(session2.getId(), { supportsConditionalBreakpoints: true }, data2);
assert.equal(breakpoints[0].supported, true);
});
test('exception breakpoints', () => {
let eventCount = 0;
model.onDidChangeBreakpoints(() => eventCount++);
model.setExceptionBreakpoints([{ filter: 'uncaught', label: 'UNCAUGHT', default: true }]);
assert.equal(eventCount, 1);
let exceptionBreakpoints = model.getExceptionBreakpoints();
assert.equal(exceptionBreakpoints.length, 1);
assert.equal(exceptionBreakpoints[0].filter, 'uncaught');
assert.equal(exceptionBreakpoints[0].enabled, true);
model.setExceptionBreakpoints([{ filter: 'uncaught', label: 'UNCAUGHT' }, { filter: 'caught', label: 'CAUGHT' }]);
assert.equal(eventCount, 2);
exceptionBreakpoints = model.getExceptionBreakpoints();
assert.equal(exceptionBreakpoints.length, 2);
assert.equal(exceptionBreakpoints[0].filter, 'uncaught');
assert.equal(exceptionBreakpoints[0].enabled, true);
assert.equal(exceptionBreakpoints[1].filter, 'caught');
assert.equal(exceptionBreakpoints[1].label, 'CAUGHT');
assert.equal(exceptionBreakpoints[1].enabled, false);
});
test('data breakpoints', () => {
let eventCount = 0;
model.onDidChangeBreakpoints(() => eventCount++);
model.addDataBreakpoint('label', 'id', true, ['read']);
model.addDataBreakpoint('second', 'secondId', false, ['readWrite']);
const dataBreakpoints = model.getDataBreakpoints();
assert.equal(dataBreakpoints[0].canPersist, true);
assert.equal(dataBreakpoints[0].dataId, 'id');
assert.equal(dataBreakpoints[1].canPersist, false);
assert.equal(dataBreakpoints[1].description, 'second');
assert.equal(eventCount, 2);
model.removeDataBreakpoints(dataBreakpoints[0].getId());
assert.equal(eventCount, 3);
assert.equal(model.getDataBreakpoints().length, 1);
model.removeDataBreakpoints();
assert.equal(model.getDataBreakpoints().length, 0);
assert.equal(eventCount, 4);
});
test('message and class name', () => {
const modelUri = uri.file('/myfolder/my file first.js');
addBreakpointsAndCheckEvents(model, modelUri, [
{ lineNumber: 5, enabled: true, condition: 'x > 5' },
{ lineNumber: 10, enabled: false },
{ lineNumber: 12, enabled: true, logMessage: 'hello' },
{ lineNumber: 15, enabled: true, hitCondition: '12' },
{ lineNumber: 500, enabled: true },
]);
const breakpoints = model.getBreakpoints();
let result = getBreakpointMessageAndClassName(State.Stopped, true, breakpoints[0]);
assert.equal(result.message, 'Expression: x > 5');
assert.equal(result.className, 'codicon-debug-breakpoint-conditional');
result = getBreakpointMessageAndClassName(State.Stopped, true, breakpoints[1]);
assert.equal(result.message, 'Disabled Breakpoint');
assert.equal(result.className, 'codicon-debug-breakpoint-disabled');
result = getBreakpointMessageAndClassName(State.Stopped, true, breakpoints[2]);
assert.equal(result.message, 'Log Message: hello');
assert.equal(result.className, 'codicon-debug-breakpoint-log');
result = getBreakpointMessageAndClassName(State.Stopped, true, breakpoints[3]);
assert.equal(result.message, 'Hit Count: 12');
assert.equal(result.className, 'codicon-debug-breakpoint-conditional');
result = getBreakpointMessageAndClassName(State.Stopped, true, breakpoints[4]);
assert.equal(result.message, 'Breakpoint');
assert.equal(result.className, 'codicon-debug-breakpoint');
result = getBreakpointMessageAndClassName(State.Stopped, false, breakpoints[2]);
assert.equal(result.message, 'Disabled Logpoint');
assert.equal(result.className, 'codicon-debug-breakpoint-log-disabled');
model.addDataBreakpoint('label', 'id', true, ['read']);
const dataBreakpoints = model.getDataBreakpoints();
result = getBreakpointMessageAndClassName(State.Stopped, true, dataBreakpoints[0]);
assert.equal(result.message, 'Data Breakpoint');
assert.equal(result.className, 'codicon-debug-breakpoint-data');
const functionBreakpoint = model.addFunctionBreakpoint('foo', '1');
result = getBreakpointMessageAndClassName(State.Stopped, true, functionBreakpoint);
assert.equal(result.message, 'Function Breakpoint');
assert.equal(result.className, 'codicon-debug-breakpoint-function');
const data = new Map<string, DebugProtocol.Breakpoint>();
data.set(breakpoints[0].getId(), { verified: false, line: 10 });
data.set(breakpoints[1].getId(), { verified: true, line: 50 });
data.set(breakpoints[2].getId(), { verified: true, line: 50, message: 'world' });
data.set(functionBreakpoint.getId(), { verified: true });
model.setBreakpointSessionData('mocksessionid', { supportsFunctionBreakpoints: false, supportsDataBreakpoints: true, supportsLogPoints: true }, data);
result = getBreakpointMessageAndClassName(State.Stopped, true, breakpoints[0]);
assert.equal(result.message, 'Unverified Breakpoint');
assert.equal(result.className, 'codicon-debug-breakpoint-unverified');
result = getBreakpointMessageAndClassName(State.Stopped, true, functionBreakpoint);
assert.equal(result.message, 'Function breakpoints not supported by this debug type');
assert.equal(result.className, 'codicon-debug-breakpoint-function-unverified');
result = getBreakpointMessageAndClassName(State.Stopped, true, breakpoints[2]);
assert.equal(result.message, 'Log Message: hello, world');
assert.equal(result.className, 'codicon-debug-breakpoint-log');
});
test('decorations', () => {
const modelUri = uri.file('/myfolder/my file first.js');
const languageIdentifier = new LanguageIdentifier('testMode', LanguageId.PlainText);
const textModel = new TextModel(
['this is line one', 'this is line two', ' this is line three it has whitespace at start', 'this is line four', 'this is line five'].join('\n'),
TextModel.DEFAULT_CREATION_OPTIONS,
languageIdentifier
);
addBreakpointsAndCheckEvents(model, modelUri, [
{ lineNumber: 1, enabled: true, condition: 'x > 5' },
{ lineNumber: 2, column: 4, enabled: false },
{ lineNumber: 3, enabled: true, logMessage: 'hello' },
{ lineNumber: 500, enabled: true },
]);
const breakpoints = model.getBreakpoints();
let decorations = createBreakpointDecorations(textModel, breakpoints, State.Running, true, true);
assert.equal(decorations.length, 3); // last breakpoint filtered out since it has a large line number
assert.deepEqual(decorations[0].range, new Range(1, 1, 1, 2));
assert.deepEqual(decorations[1].range, new Range(2, 4, 2, 5));
assert.deepEqual(decorations[2].range, new Range(3, 5, 3, 6));
assert.equal(decorations[0].options.beforeContentClassName, undefined);
assert.equal(decorations[1].options.beforeContentClassName, `debug-breakpoint-placeholder`);
assert.equal(decorations[0].options.overviewRuler?.position, OverviewRulerLane.Left);
const expected = new MarkdownString().appendCodeblock(languageIdentifier.language, 'Expression: x > 5');
assert.deepEqual(decorations[0].options.glyphMarginHoverMessage, expected);
decorations = createBreakpointDecorations(textModel, breakpoints, State.Running, true, false);
assert.equal(decorations[0].options.overviewRuler, null);
});
});

View File

@@ -0,0 +1,419 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { DebugModel, StackFrame, Thread } from 'vs/workbench/contrib/debug/common/debugModel';
import * as sinon from 'sinon';
import { MockRawSession } from 'vs/workbench/contrib/debug/test/common/mockDebug';
import { Source } from 'vs/workbench/contrib/debug/common/debugSource';
import { DebugSession } from 'vs/workbench/contrib/debug/browser/debugSession';
import { Range } from 'vs/editor/common/core/range';
import { IDebugSessionOptions, State } from 'vs/workbench/contrib/debug/common/debug';
import { NullOpenerService } from 'vs/platform/opener/common/opener';
import { createDecorationsForStackFrame } from 'vs/workbench/contrib/debug/browser/callStackEditorContribution';
import { Constants } from 'vs/base/common/uint';
import { getContext, getContextForContributedActions } from 'vs/workbench/contrib/debug/browser/callStackView';
import { getStackFrameThreadAndSessionToFocus } from 'vs/workbench/contrib/debug/browser/debugService';
export function createMockSession(model: DebugModel, name = 'mockSession', options?: IDebugSessionOptions): DebugSession {
return new DebugSession({ resolved: { name, type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, options, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, NullOpenerService, undefined!);
}
function createTwoStackFrames(session: DebugSession): { firstStackFrame: StackFrame, secondStackFrame: StackFrame } {
let firstStackFrame: StackFrame;
let secondStackFrame: StackFrame;
const thread = new class extends Thread {
public getCallStack(): StackFrame[] {
return [firstStackFrame, secondStackFrame];
}
}(session, 'mockthread', 1);
const firstSource = new Source({
name: 'internalModule.js',
path: 'a/b/c/d/internalModule.js',
sourceReference: 10,
}, 'aDebugSessionId');
const secondSource = new Source({
name: 'internalModule.js',
path: 'z/x/c/d/internalModule.js',
sourceReference: 11,
}, 'aDebugSessionId');
firstStackFrame = new StackFrame(thread, 1, firstSource, 'app.js', 'normal', { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 10 }, 1);
secondStackFrame = new StackFrame(thread, 1, secondSource, 'app.js', 'normal', { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 10 }, 1);
return { firstStackFrame, secondStackFrame };
}
suite('Debug - CallStack', () => {
let model: DebugModel;
let rawSession: MockRawSession;
setup(() => {
model = new DebugModel([], [], [], [], [], <any>{ isDirty: (e: any) => false });
rawSession = new MockRawSession();
});
// Threads
test('threads simple', () => {
const threadId = 1;
const threadName = 'firstThread';
const session = createMockSession(model);
model.addSession(session);
assert.equal(model.getSessions(true).length, 1);
model.rawUpdate({
sessionId: session.getId(),
threads: [{
id: threadId,
name: threadName
}]
});
assert.equal(session.getThread(threadId)!.name, threadName);
model.clearThreads(session.getId(), true);
assert.equal(session.getThread(threadId), undefined);
assert.equal(model.getSessions(true).length, 1);
});
test('threads multiple wtih allThreadsStopped', () => {
const threadId1 = 1;
const threadName1 = 'firstThread';
const threadId2 = 2;
const threadName2 = 'secondThread';
const stoppedReason = 'breakpoint';
// Add the threads
const session = createMockSession(model);
model.addSession(session);
session['raw'] = <any>rawSession;
model.rawUpdate({
sessionId: session.getId(),
threads: [{
id: threadId1,
name: threadName1
}]
});
// Stopped event with all threads stopped
model.rawUpdate({
sessionId: session.getId(),
threads: [{
id: threadId1,
name: threadName1
}, {
id: threadId2,
name: threadName2
}],
stoppedDetails: {
reason: stoppedReason,
threadId: 1,
allThreadsStopped: true
},
});
const thread1 = session.getThread(threadId1)!;
const thread2 = session.getThread(threadId2)!;
// at the beginning, callstacks are obtainable but not available
assert.equal(session.getAllThreads().length, 2);
assert.equal(thread1.name, threadName1);
assert.equal(thread1.stopped, true);
assert.equal(thread1.getCallStack().length, 0);
assert.equal(thread1.stoppedDetails!.reason, stoppedReason);
assert.equal(thread2.name, threadName2);
assert.equal(thread2.stopped, true);
assert.equal(thread2.getCallStack().length, 0);
assert.equal(thread2.stoppedDetails!.reason, undefined);
// after calling getCallStack, the callstack becomes available
// and results in a request for the callstack in the debug adapter
thread1.fetchCallStack().then(() => {
assert.notEqual(thread1.getCallStack().length, 0);
});
thread2.fetchCallStack().then(() => {
assert.notEqual(thread2.getCallStack().length, 0);
});
// calling multiple times getCallStack doesn't result in multiple calls
// to the debug adapter
thread1.fetchCallStack().then(() => {
return thread2.fetchCallStack();
});
// clearing the callstack results in the callstack not being available
thread1.clearCallStack();
assert.equal(thread1.stopped, true);
assert.equal(thread1.getCallStack().length, 0);
thread2.clearCallStack();
assert.equal(thread2.stopped, true);
assert.equal(thread2.getCallStack().length, 0);
model.clearThreads(session.getId(), true);
assert.equal(session.getThread(threadId1), undefined);
assert.equal(session.getThread(threadId2), undefined);
assert.equal(session.getAllThreads().length, 0);
});
test('threads mutltiple without allThreadsStopped', () => {
const sessionStub = sinon.spy(rawSession, 'stackTrace');
const stoppedThreadId = 1;
const stoppedThreadName = 'stoppedThread';
const runningThreadId = 2;
const runningThreadName = 'runningThread';
const stoppedReason = 'breakpoint';
const session = createMockSession(model);
model.addSession(session);
session['raw'] = <any>rawSession;
// Add the threads
model.rawUpdate({
sessionId: session.getId(),
threads: [{
id: stoppedThreadId,
name: stoppedThreadName
}]
});
// Stopped event with only one thread stopped
model.rawUpdate({
sessionId: session.getId(),
threads: [{
id: 1,
name: stoppedThreadName
}, {
id: runningThreadId,
name: runningThreadName
}],
stoppedDetails: {
reason: stoppedReason,
threadId: 1,
allThreadsStopped: false
}
});
const stoppedThread = session.getThread(stoppedThreadId)!;
const runningThread = session.getThread(runningThreadId)!;
// the callstack for the stopped thread is obtainable but not available
// the callstack for the running thread is not obtainable nor available
assert.equal(stoppedThread.name, stoppedThreadName);
assert.equal(stoppedThread.stopped, true);
assert.equal(session.getAllThreads().length, 2);
assert.equal(stoppedThread.getCallStack().length, 0);
assert.equal(stoppedThread.stoppedDetails!.reason, stoppedReason);
assert.equal(runningThread.name, runningThreadName);
assert.equal(runningThread.stopped, false);
assert.equal(runningThread.getCallStack().length, 0);
assert.equal(runningThread.stoppedDetails, undefined);
// after calling getCallStack, the callstack becomes available
// and results in a request for the callstack in the debug adapter
stoppedThread.fetchCallStack().then(() => {
assert.notEqual(stoppedThread.getCallStack().length, 0);
assert.equal(runningThread.getCallStack().length, 0);
assert.equal(sessionStub.callCount, 1);
});
// calling getCallStack on the running thread returns empty array
// and does not return in a request for the callstack in the debug
// adapter
runningThread.fetchCallStack().then(() => {
assert.equal(runningThread.getCallStack().length, 0);
assert.equal(sessionStub.callCount, 1);
});
// clearing the callstack results in the callstack not being available
stoppedThread.clearCallStack();
assert.equal(stoppedThread.stopped, true);
assert.equal(stoppedThread.getCallStack().length, 0);
model.clearThreads(session.getId(), true);
assert.equal(session.getThread(stoppedThreadId), undefined);
assert.equal(session.getThread(runningThreadId), undefined);
assert.equal(session.getAllThreads().length, 0);
});
test('stack frame get specific source name', () => {
const session = createMockSession(model);
model.addSession(session);
const { firstStackFrame, secondStackFrame } = createTwoStackFrames(session);
assert.equal(firstStackFrame.getSpecificSourceName(), '.../b/c/d/internalModule.js');
assert.equal(secondStackFrame.getSpecificSourceName(), '.../x/c/d/internalModule.js');
});
test('stack frame toString()', () => {
const session = createMockSession(model);
const thread = new Thread(session, 'mockthread', 1);
const firstSource = new Source({
name: 'internalModule.js',
path: 'a/b/c/d/internalModule.js',
sourceReference: 10,
}, 'aDebugSessionId');
const stackFrame = new StackFrame(thread, 1, firstSource, 'app', 'normal', { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 10 }, 1);
assert.equal(stackFrame.toString(), 'app (internalModule.js:1)');
const secondSource = new Source(undefined, 'aDebugSessionId');
const stackFrame2 = new StackFrame(thread, 2, secondSource, 'module', 'normal', { startLineNumber: undefined!, startColumn: undefined!, endLineNumber: undefined!, endColumn: undefined! }, 2);
assert.equal(stackFrame2.toString(), 'module');
});
test('debug child sessions are added in correct order', () => {
const session = createMockSession(model);
model.addSession(session);
const secondSession = createMockSession(model, 'mockSession2');
model.addSession(secondSession);
const firstChild = createMockSession(model, 'firstChild', { parentSession: session });
model.addSession(firstChild);
const secondChild = createMockSession(model, 'secondChild', { parentSession: session });
model.addSession(secondChild);
const thirdSession = createMockSession(model, 'mockSession3');
model.addSession(thirdSession);
const anotherChild = createMockSession(model, 'secondChild', { parentSession: secondSession });
model.addSession(anotherChild);
const sessions = model.getSessions();
assert.equal(sessions[0].getId(), session.getId());
assert.equal(sessions[1].getId(), firstChild.getId());
assert.equal(sessions[2].getId(), secondChild.getId());
assert.equal(sessions[3].getId(), secondSession.getId());
assert.equal(sessions[4].getId(), anotherChild.getId());
assert.equal(sessions[5].getId(), thirdSession.getId());
});
test('decorations', () => {
const session = createMockSession(model);
model.addSession(session);
const { firstStackFrame, secondStackFrame } = createTwoStackFrames(session);
let decorations = createDecorationsForStackFrame(firstStackFrame, firstStackFrame.range);
assert.equal(decorations.length, 2);
assert.deepEqual(decorations[0].range, new Range(1, 2, 1, 1));
assert.equal(decorations[0].options.glyphMarginClassName, 'codicon-debug-stackframe');
assert.deepEqual(decorations[1].range, new Range(1, Constants.MAX_SAFE_SMALL_INTEGER, 1, 1));
assert.equal(decorations[1].options.className, 'debug-top-stack-frame-line');
assert.equal(decorations[1].options.isWholeLine, true);
decorations = createDecorationsForStackFrame(secondStackFrame, firstStackFrame.range);
assert.equal(decorations.length, 2);
assert.deepEqual(decorations[0].range, new Range(1, 2, 1, 1));
assert.equal(decorations[0].options.glyphMarginClassName, 'codicon-debug-stackframe-focused');
assert.deepEqual(decorations[1].range, new Range(1, Constants.MAX_SAFE_SMALL_INTEGER, 1, 1));
assert.equal(decorations[1].options.className, 'debug-focused-stack-frame-line');
assert.equal(decorations[1].options.isWholeLine, true);
decorations = createDecorationsForStackFrame(firstStackFrame, new Range(1, 5, 1, 6));
assert.equal(decorations.length, 3);
assert.deepEqual(decorations[0].range, new Range(1, 2, 1, 1));
assert.equal(decorations[0].options.glyphMarginClassName, 'codicon-debug-stackframe');
assert.deepEqual(decorations[1].range, new Range(1, Constants.MAX_SAFE_SMALL_INTEGER, 1, 1));
assert.equal(decorations[1].options.className, 'debug-top-stack-frame-line');
assert.equal(decorations[1].options.isWholeLine, true);
// Inline decoration gets rendered in this case
assert.equal(decorations[2].options.beforeContentClassName, 'debug-top-stack-frame-column');
assert.deepEqual(decorations[2].range, new Range(1, Constants.MAX_SAFE_SMALL_INTEGER, 1, 1));
});
test('contexts', () => {
const session = createMockSession(model);
model.addSession(session);
const { firstStackFrame, secondStackFrame } = createTwoStackFrames(session);
let context = getContext(firstStackFrame);
assert.equal(context.sessionId, firstStackFrame.thread.session.getId());
assert.equal(context.threadId, firstStackFrame.thread.getId());
assert.equal(context.frameId, firstStackFrame.getId());
context = getContext(secondStackFrame.thread);
assert.equal(context.sessionId, secondStackFrame.thread.session.getId());
assert.equal(context.threadId, secondStackFrame.thread.getId());
assert.equal(context.frameId, undefined);
context = getContext(session);
assert.equal(context.sessionId, session.getId());
assert.equal(context.threadId, undefined);
assert.equal(context.frameId, undefined);
let contributedContext = getContextForContributedActions(firstStackFrame);
assert.equal(contributedContext, firstStackFrame.source.raw.path);
contributedContext = getContextForContributedActions(firstStackFrame.thread);
assert.equal(contributedContext, firstStackFrame.thread.threadId);
contributedContext = getContextForContributedActions(session);
assert.equal(contributedContext, session.getId());
});
test('focusStackFrameThreadAndSesion', () => {
const threadId1 = 1;
const threadName1 = 'firstThread';
const threadId2 = 2;
const threadName2 = 'secondThread';
const stoppedReason = 'breakpoint';
// Add the threads
const session = new class extends DebugSession {
get state(): State {
return State.Stopped;
}
}({ resolved: { name: 'stoppedSession', type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, undefined, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, NullOpenerService, undefined!);
const runningSession = createMockSession(model);
model.addSession(runningSession);
model.addSession(session);
session['raw'] = <any>rawSession;
model.rawUpdate({
sessionId: session.getId(),
threads: [{
id: threadId1,
name: threadName1
}]
});
// Stopped event with all threads stopped
model.rawUpdate({
sessionId: session.getId(),
threads: [{
id: threadId1,
name: threadName1
}, {
id: threadId2,
name: threadName2
}],
stoppedDetails: {
reason: stoppedReason,
threadId: 1,
allThreadsStopped: true
},
});
const thread = session.getThread(threadId1)!;
const runningThread = session.getThread(threadId2);
let toFocus = getStackFrameThreadAndSessionToFocus(model, undefined);
// Verify stopped session and stopped thread get focused
assert.deepEqual(toFocus, { stackFrame: undefined, thread: thread, session: session });
toFocus = getStackFrameThreadAndSessionToFocus(model, undefined, undefined, runningSession);
assert.deepEqual(toFocus, { stackFrame: undefined, thread: undefined, session: runningSession });
toFocus = getStackFrameThreadAndSessionToFocus(model, undefined, thread);
assert.deepEqual(toFocus, { stackFrame: undefined, thread: thread, session: session });
toFocus = getStackFrameThreadAndSessionToFocus(model, undefined, runningThread);
assert.deepEqual(toFocus, { stackFrame: undefined, thread: runningThread, session: session });
const stackFrame = new StackFrame(thread, 5, undefined!, 'stackframename2', undefined, undefined!, 1);
toFocus = getStackFrameThreadAndSessionToFocus(model, stackFrame);
assert.deepEqual(toFocus, { stackFrame: stackFrame, thread: thread, session: session });
});
});

View File

@@ -30,7 +30,7 @@ suite.skip('Debug - ANSI Handling', () => {
*/
setup(() => {
model = new DebugModel([], [], [], [], [], <any>{ isDirty: (e: any) => false });
session = new DebugSession({ resolved: { name, type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, undefined, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, NullOpenerService);
session = new DebugSession({ resolved: { name: 'test', type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, undefined, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, NullOpenerService, undefined!);
const instantiationService: TestInstantiationService = <TestInstantiationService>workbenchInstantiationService();
linkDetector = instantiationService.createInstance(LinkDetector);

View File

@@ -0,0 +1,66 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { findExpressionInStackFrame } from 'vs/workbench/contrib/debug/browser/debugHover';
import { createMockSession } from 'vs/workbench/contrib/debug/test/browser/callStack.test';
import { StackFrame, Thread, DebugModel, Scope, Variable } from 'vs/workbench/contrib/debug/common/debugModel';
import { Source } from 'vs/workbench/contrib/debug/common/debugSource';
import type { IScope, IExpression } from 'vs/workbench/contrib/debug/common/debug';
suite('Debug - Hover', () => {
test('find expression in stack frame', async () => {
const model = new DebugModel([], [], [], [], [], <any>{ isDirty: (e: any) => false });
const session = createMockSession(model);
let stackFrame: StackFrame;
const thread = new class extends Thread {
public getCallStack(): StackFrame[] {
return [stackFrame];
}
}(session, 'mockthread', 1);
const firstSource = new Source({
name: 'internalModule.js',
path: 'a/b/c/d/internalModule.js',
sourceReference: 10,
}, 'aDebugSessionId');
let scope: Scope;
stackFrame = new class extends StackFrame {
getScopes(): Promise<IScope[]> {
return Promise.resolve([scope]);
}
}(thread, 1, firstSource, 'app.js', 'normal', { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 10 }, 1);
let variableA: Variable;
let variableB: Variable;
scope = new class extends Scope {
getChildren(): Promise<IExpression[]> {
return Promise.resolve([variableA]);
}
}(stackFrame, 1, 'local', 1, false, 10, 10);
variableA = new class extends Variable {
getChildren(): Promise<IExpression[]> {
return Promise.resolve([variableB]);
}
}(session, 1, scope, 2, 'A', 'A', undefined!, 0, 0, {}, 'string');
variableB = new Variable(session, 1, scope, 2, 'B', 'A.B', undefined!, 0, 0, {}, 'string');
assert.equal(await findExpressionInStackFrame(stackFrame, []), undefined);
assert.equal(await findExpressionInStackFrame(stackFrame, ['A']), variableA);
assert.equal(await findExpressionInStackFrame(stackFrame, ['doesNotExist', 'no']), undefined);
assert.equal(await findExpressionInStackFrame(stackFrame, ['a']), undefined);
assert.equal(await findExpressionInStackFrame(stackFrame, ['B']), undefined);
assert.equal(await findExpressionInStackFrame(stackFrame, ['A', 'B']), variableB);
assert.equal(await findExpressionInStackFrame(stackFrame, ['A', 'C']), undefined);
// We do not search in expensive scopes
scope.expensive = true;
assert.equal(await findExpressionInStackFrame(stackFrame, ['A']), undefined);
});
});

View File

@@ -1,575 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { URI as uri } from 'vs/base/common/uri';
import severity from 'vs/base/common/severity';
import { DebugModel, Expression, StackFrame, Thread } from 'vs/workbench/contrib/debug/common/debugModel';
import * as sinon from 'sinon';
import { MockRawSession, MockDebugAdapter } from 'vs/workbench/contrib/debug/test/common/mockDebug';
import { Source } from 'vs/workbench/contrib/debug/common/debugSource';
import { DebugSession } from 'vs/workbench/contrib/debug/browser/debugSession';
import { SimpleReplElement, RawObjectReplElement, ReplEvaluationInput, ReplModel, ReplEvaluationResult } from 'vs/workbench/contrib/debug/common/replModel';
import { IBreakpointUpdateData, IDebugSessionOptions } from 'vs/workbench/contrib/debug/common/debug';
import { NullOpenerService } from 'vs/platform/opener/common/opener';
import { RawDebugSession } from 'vs/workbench/contrib/debug/browser/rawDebugSession';
import { timeout } from 'vs/base/common/async';
function createMockSession(model: DebugModel, name = 'mockSession', options?: IDebugSessionOptions): DebugSession {
return new DebugSession({ resolved: { name, type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, options, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, NullOpenerService);
}
suite('Debug - Model', () => {
let model: DebugModel;
let rawSession: MockRawSession;
setup(() => {
model = new DebugModel([], [], [], [], [], <any>{ isDirty: (e: any) => false });
rawSession = new MockRawSession();
});
// Breakpoints
test('breakpoints simple', () => {
const modelUri = uri.file('/myfolder/myfile.js');
model.addBreakpoints(modelUri, [{ lineNumber: 5, enabled: true }, { lineNumber: 10, enabled: false }]);
assert.equal(model.areBreakpointsActivated(), true);
assert.equal(model.getBreakpoints().length, 2);
model.removeBreakpoints(model.getBreakpoints());
assert.equal(model.getBreakpoints().length, 0);
});
test('breakpoints toggling', () => {
const modelUri = uri.file('/myfolder/myfile.js');
model.addBreakpoints(modelUri, [{ lineNumber: 5, enabled: true }, { lineNumber: 10, enabled: false }]);
model.addBreakpoints(modelUri, [{ lineNumber: 12, enabled: true, condition: 'fake condition' }]);
assert.equal(model.getBreakpoints().length, 3);
const bp = model.getBreakpoints().pop();
if (bp) {
model.removeBreakpoints([bp]);
}
assert.equal(model.getBreakpoints().length, 2);
model.setBreakpointsActivated(false);
assert.equal(model.areBreakpointsActivated(), false);
model.setBreakpointsActivated(true);
assert.equal(model.areBreakpointsActivated(), true);
});
test('breakpoints two files', () => {
const modelUri1 = uri.file('/myfolder/my file first.js');
const modelUri2 = uri.file('/secondfolder/second/second file.js');
model.addBreakpoints(modelUri1, [{ lineNumber: 5, enabled: true }, { lineNumber: 10, enabled: false }]);
model.addBreakpoints(modelUri2, [{ lineNumber: 1, enabled: true }, { lineNumber: 2, enabled: true }, { lineNumber: 3, enabled: false }]);
assert.equal(model.getBreakpoints().length, 5);
const bp = model.getBreakpoints()[0];
const update = new Map<string, IBreakpointUpdateData>();
update.set(bp.getId(), { lineNumber: 100 });
model.updateBreakpoints(update);
assert.equal(bp.lineNumber, 100);
model.enableOrDisableAllBreakpoints(false);
model.getBreakpoints().forEach(bp => {
assert.equal(bp.enabled, false);
});
model.setEnablement(bp, true);
assert.equal(bp.enabled, true);
model.removeBreakpoints(model.getBreakpoints({ uri: modelUri1 }));
assert.equal(model.getBreakpoints().length, 3);
});
test('breakpoints conditions', () => {
const modelUri1 = uri.file('/myfolder/my file first.js');
model.addBreakpoints(modelUri1, [{ lineNumber: 5, condition: 'i < 5', hitCondition: '17' }, { lineNumber: 10, condition: 'j < 3' }]);
const breakpoints = model.getBreakpoints();
assert.equal(breakpoints[0].condition, 'i < 5');
assert.equal(breakpoints[0].hitCondition, '17');
assert.equal(breakpoints[1].condition, 'j < 3');
assert.equal(!!breakpoints[1].hitCondition, false);
assert.equal(model.getBreakpoints().length, 2);
model.removeBreakpoints(model.getBreakpoints());
assert.equal(model.getBreakpoints().length, 0);
});
test('function breakpoints', () => {
model.addFunctionBreakpoint('foo', '1');
model.addFunctionBreakpoint('bar', '2');
model.renameFunctionBreakpoint('1', 'fooUpdated');
model.renameFunctionBreakpoint('2', 'barUpdated');
const functionBps = model.getFunctionBreakpoints();
assert.equal(functionBps[0].name, 'fooUpdated');
assert.equal(functionBps[1].name, 'barUpdated');
model.removeFunctionBreakpoints();
assert.equal(model.getFunctionBreakpoints().length, 0);
});
test('breakpoints multiple sessions', () => {
const modelUri = uri.file('/myfolder/myfile.js');
const breakpoints = model.addBreakpoints(modelUri, [{ lineNumber: 5, enabled: true, condition: 'x > 5' }, { lineNumber: 10, enabled: false }]);
const session = createMockSession(model);
const data = new Map<string, DebugProtocol.Breakpoint>();
assert.equal(breakpoints[0].lineNumber, 5);
assert.equal(breakpoints[1].lineNumber, 10);
data.set(breakpoints[0].getId(), { verified: false, line: 10 });
data.set(breakpoints[1].getId(), { verified: true, line: 50 });
model.setBreakpointSessionData(session.getId(), {}, data);
assert.equal(breakpoints[0].lineNumber, 5);
assert.equal(breakpoints[1].lineNumber, 50);
const session2 = createMockSession(model);
const data2 = new Map<string, DebugProtocol.Breakpoint>();
data2.set(breakpoints[0].getId(), { verified: true, line: 100 });
data2.set(breakpoints[1].getId(), { verified: true, line: 500 });
model.setBreakpointSessionData(session2.getId(), {}, data2);
// Breakpoint is verified only once, show that line
assert.equal(breakpoints[0].lineNumber, 100);
// Breakpoint is verified two times, show the original line
assert.equal(breakpoints[1].lineNumber, 10);
model.setBreakpointSessionData(session.getId(), {}, undefined);
// No more double session verification
assert.equal(breakpoints[0].lineNumber, 100);
assert.equal(breakpoints[1].lineNumber, 500);
assert.equal(breakpoints[0].supported, false);
const data3 = new Map<string, DebugProtocol.Breakpoint>();
data3.set(breakpoints[0].getId(), { verified: true, line: 500 });
model.setBreakpointSessionData(session2.getId(), { supportsConditionalBreakpoints: true }, data2);
assert.equal(breakpoints[0].supported, true);
});
// Threads
test('threads simple', () => {
const threadId = 1;
const threadName = 'firstThread';
const session = createMockSession(model);
model.addSession(session);
assert.equal(model.getSessions(true).length, 1);
model.rawUpdate({
sessionId: session.getId(),
threads: [{
id: threadId,
name: threadName
}]
});
assert.equal(session.getThread(threadId)!.name, threadName);
model.clearThreads(session.getId(), true);
assert.equal(session.getThread(threadId), undefined);
assert.equal(model.getSessions(true).length, 1);
});
test('threads multiple wtih allThreadsStopped', () => {
const threadId1 = 1;
const threadName1 = 'firstThread';
const threadId2 = 2;
const threadName2 = 'secondThread';
const stoppedReason = 'breakpoint';
// Add the threads
const session = createMockSession(model);
model.addSession(session);
session['raw'] = <any>rawSession;
model.rawUpdate({
sessionId: session.getId(),
threads: [{
id: threadId1,
name: threadName1
}]
});
// Stopped event with all threads stopped
model.rawUpdate({
sessionId: session.getId(),
threads: [{
id: threadId1,
name: threadName1
}, {
id: threadId2,
name: threadName2
}],
stoppedDetails: {
reason: stoppedReason,
threadId: 1,
allThreadsStopped: true
},
});
const thread1 = session.getThread(threadId1)!;
const thread2 = session.getThread(threadId2)!;
// at the beginning, callstacks are obtainable but not available
assert.equal(session.getAllThreads().length, 2);
assert.equal(thread1.name, threadName1);
assert.equal(thread1.stopped, true);
assert.equal(thread1.getCallStack().length, 0);
assert.equal(thread1.stoppedDetails!.reason, stoppedReason);
assert.equal(thread2.name, threadName2);
assert.equal(thread2.stopped, true);
assert.equal(thread2.getCallStack().length, 0);
assert.equal(thread2.stoppedDetails!.reason, undefined);
// after calling getCallStack, the callstack becomes available
// and results in a request for the callstack in the debug adapter
thread1.fetchCallStack().then(() => {
assert.notEqual(thread1.getCallStack().length, 0);
});
thread2.fetchCallStack().then(() => {
assert.notEqual(thread2.getCallStack().length, 0);
});
// calling multiple times getCallStack doesn't result in multiple calls
// to the debug adapter
thread1.fetchCallStack().then(() => {
return thread2.fetchCallStack();
});
// clearing the callstack results in the callstack not being available
thread1.clearCallStack();
assert.equal(thread1.stopped, true);
assert.equal(thread1.getCallStack().length, 0);
thread2.clearCallStack();
assert.equal(thread2.stopped, true);
assert.equal(thread2.getCallStack().length, 0);
model.clearThreads(session.getId(), true);
assert.equal(session.getThread(threadId1), undefined);
assert.equal(session.getThread(threadId2), undefined);
assert.equal(session.getAllThreads().length, 0);
});
test('threads mutltiple without allThreadsStopped', () => {
const sessionStub = sinon.spy(rawSession, 'stackTrace');
const stoppedThreadId = 1;
const stoppedThreadName = 'stoppedThread';
const runningThreadId = 2;
const runningThreadName = 'runningThread';
const stoppedReason = 'breakpoint';
const session = createMockSession(model);
model.addSession(session);
session['raw'] = <any>rawSession;
// Add the threads
model.rawUpdate({
sessionId: session.getId(),
threads: [{
id: stoppedThreadId,
name: stoppedThreadName
}]
});
// Stopped event with only one thread stopped
model.rawUpdate({
sessionId: session.getId(),
threads: [{
id: 1,
name: stoppedThreadName
}, {
id: runningThreadId,
name: runningThreadName
}],
stoppedDetails: {
reason: stoppedReason,
threadId: 1,
allThreadsStopped: false
}
});
const stoppedThread = session.getThread(stoppedThreadId)!;
const runningThread = session.getThread(runningThreadId)!;
// the callstack for the stopped thread is obtainable but not available
// the callstack for the running thread is not obtainable nor available
assert.equal(stoppedThread.name, stoppedThreadName);
assert.equal(stoppedThread.stopped, true);
assert.equal(session.getAllThreads().length, 2);
assert.equal(stoppedThread.getCallStack().length, 0);
assert.equal(stoppedThread.stoppedDetails!.reason, stoppedReason);
assert.equal(runningThread.name, runningThreadName);
assert.equal(runningThread.stopped, false);
assert.equal(runningThread.getCallStack().length, 0);
assert.equal(runningThread.stoppedDetails, undefined);
// after calling getCallStack, the callstack becomes available
// and results in a request for the callstack in the debug adapter
stoppedThread.fetchCallStack().then(() => {
assert.notEqual(stoppedThread.getCallStack().length, 0);
assert.equal(runningThread.getCallStack().length, 0);
assert.equal(sessionStub.callCount, 1);
});
// calling getCallStack on the running thread returns empty array
// and does not return in a request for the callstack in the debug
// adapter
runningThread.fetchCallStack().then(() => {
assert.equal(runningThread.getCallStack().length, 0);
assert.equal(sessionStub.callCount, 1);
});
// clearing the callstack results in the callstack not being available
stoppedThread.clearCallStack();
assert.equal(stoppedThread.stopped, true);
assert.equal(stoppedThread.getCallStack().length, 0);
model.clearThreads(session.getId(), true);
assert.equal(session.getThread(stoppedThreadId), undefined);
assert.equal(session.getThread(runningThreadId), undefined);
assert.equal(session.getAllThreads().length, 0);
});
// Expressions
function assertWatchExpressions(watchExpressions: Expression[], expectedName: string) {
assert.equal(watchExpressions.length, 2);
watchExpressions.forEach(we => {
assert.equal(we.available, false);
assert.equal(we.reference, 0);
assert.equal(we.name, expectedName);
});
}
test('watch expressions', () => {
assert.equal(model.getWatchExpressions().length, 0);
model.addWatchExpression('console');
model.addWatchExpression('console');
let watchExpressions = model.getWatchExpressions();
assertWatchExpressions(watchExpressions, 'console');
model.renameWatchExpression(watchExpressions[0].getId(), 'new_name');
model.renameWatchExpression(watchExpressions[1].getId(), 'new_name');
assertWatchExpressions(model.getWatchExpressions(), 'new_name');
assertWatchExpressions(model.getWatchExpressions(), 'new_name');
model.addWatchExpression('mockExpression');
model.moveWatchExpression(model.getWatchExpressions()[2].getId(), 1);
watchExpressions = model.getWatchExpressions();
assert.equal(watchExpressions[0].name, 'new_name');
assert.equal(watchExpressions[1].name, 'mockExpression');
assert.equal(watchExpressions[2].name, 'new_name');
model.removeWatchExpressions();
assert.equal(model.getWatchExpressions().length, 0);
});
test('repl expressions', () => {
const session = createMockSession(model);
assert.equal(session.getReplElements().length, 0);
model.addSession(session);
session['raw'] = <any>rawSession;
const thread = new Thread(session, 'mockthread', 1);
const stackFrame = new StackFrame(thread, 1, <any>undefined, 'app.js', 'normal', { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 10 }, 1);
const replModel = new ReplModel();
replModel.addReplExpression(session, stackFrame, 'myVariable').then();
replModel.addReplExpression(session, stackFrame, 'myVariable').then();
replModel.addReplExpression(session, stackFrame, 'myVariable').then();
assert.equal(replModel.getReplElements().length, 3);
replModel.getReplElements().forEach(re => {
assert.equal((<ReplEvaluationInput>re).value, 'myVariable');
});
replModel.removeReplExpressions();
assert.equal(replModel.getReplElements().length, 0);
});
test('stack frame get specific source name', () => {
const session = createMockSession(model);
model.addSession(session);
let firstStackFrame: StackFrame;
let secondStackFrame: StackFrame;
const thread = new class extends Thread {
public getCallStack(): StackFrame[] {
return [firstStackFrame, secondStackFrame];
}
}(session, 'mockthread', 1);
const firstSource = new Source({
name: 'internalModule.js',
path: 'a/b/c/d/internalModule.js',
sourceReference: 10,
}, 'aDebugSessionId');
const secondSource = new Source({
name: 'internalModule.js',
path: 'z/x/c/d/internalModule.js',
sourceReference: 11,
}, 'aDebugSessionId');
firstStackFrame = new StackFrame(thread, 1, firstSource, 'app.js', 'normal', { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 10 }, 1);
secondStackFrame = new StackFrame(thread, 1, secondSource, 'app.js', 'normal', { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 10 }, 1);
assert.equal(firstStackFrame.getSpecificSourceName(), '.../b/c/d/internalModule.js');
assert.equal(secondStackFrame.getSpecificSourceName(), '.../x/c/d/internalModule.js');
});
test('stack frame toString()', () => {
const session = createMockSession(model);
const thread = new Thread(session, 'mockthread', 1);
const firstSource = new Source({
name: 'internalModule.js',
path: 'a/b/c/d/internalModule.js',
sourceReference: 10,
}, 'aDebugSessionId');
const stackFrame = new StackFrame(thread, 1, firstSource, 'app', 'normal', { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 10 }, 1);
assert.equal(stackFrame.toString(), 'app (internalModule.js:1)');
const secondSource = new Source(undefined, 'aDebugSessionId');
const stackFrame2 = new StackFrame(thread, 2, secondSource, 'module', 'normal', { startLineNumber: undefined!, startColumn: undefined!, endLineNumber: undefined!, endColumn: undefined! }, 2);
assert.equal(stackFrame2.toString(), 'module');
});
test('debug child sessions are added in correct order', () => {
const session = createMockSession(model);
model.addSession(session);
const secondSession = createMockSession(model, 'mockSession2');
model.addSession(secondSession);
const firstChild = createMockSession(model, 'firstChild', { parentSession: session });
model.addSession(firstChild);
const secondChild = createMockSession(model, 'secondChild', { parentSession: session });
model.addSession(secondChild);
const thirdSession = createMockSession(model, 'mockSession3');
model.addSession(thirdSession);
const anotherChild = createMockSession(model, 'secondChild', { parentSession: secondSession });
model.addSession(anotherChild);
const sessions = model.getSessions();
assert.equal(sessions[0].getId(), session.getId());
assert.equal(sessions[1].getId(), firstChild.getId());
assert.equal(sessions[2].getId(), secondChild.getId());
assert.equal(sessions[3].getId(), secondSession.getId());
assert.equal(sessions[4].getId(), anotherChild.getId());
assert.equal(sessions[5].getId(), thirdSession.getId());
});
// Repl output
test('repl output', () => {
const session = createMockSession(model);
const repl = new ReplModel();
repl.appendToRepl(session, 'first line\n', severity.Error);
repl.appendToRepl(session, 'second line ', severity.Error);
repl.appendToRepl(session, 'third line ', severity.Error);
repl.appendToRepl(session, 'fourth line', severity.Error);
let elements = <SimpleReplElement[]>repl.getReplElements();
assert.equal(elements.length, 2);
assert.equal(elements[0].value, 'first line\n');
assert.equal(elements[0].severity, severity.Error);
assert.equal(elements[1].value, 'second line third line fourth line');
assert.equal(elements[1].severity, severity.Error);
repl.appendToRepl(session, '1', severity.Warning);
elements = <SimpleReplElement[]>repl.getReplElements();
assert.equal(elements.length, 3);
assert.equal(elements[2].value, '1');
assert.equal(elements[2].severity, severity.Warning);
const keyValueObject = { 'key1': 2, 'key2': 'value' };
repl.appendToRepl(session, new RawObjectReplElement('fakeid', 'fake', keyValueObject), severity.Info);
const element = <RawObjectReplElement>repl.getReplElements()[3];
assert.equal(element.value, 'Object');
assert.deepEqual(element.valueObj, keyValueObject);
repl.removeReplExpressions();
assert.equal(repl.getReplElements().length, 0);
repl.appendToRepl(session, '1\n', severity.Info);
repl.appendToRepl(session, '2', severity.Info);
repl.appendToRepl(session, '3\n4', severity.Info);
repl.appendToRepl(session, '5\n', severity.Info);
repl.appendToRepl(session, '6', severity.Info);
elements = <SimpleReplElement[]>repl.getReplElements();
assert.equal(elements.length, 3);
assert.equal(elements[0], '1\n');
assert.equal(elements[1], '23\n45\n');
assert.equal(elements[2], '6');
});
test('repl merging', () => {
// 'mergeWithParent' should be ignored when there is no parent.
const parent = createMockSession(model, 'parent', { repl: 'mergeWithParent' });
const child1 = createMockSession(model, 'child1', { parentSession: parent, repl: 'separate' });
const child2 = createMockSession(model, 'child2', { parentSession: parent, repl: 'mergeWithParent' });
const grandChild = createMockSession(model, 'grandChild', { parentSession: child2, repl: 'mergeWithParent' });
const child3 = createMockSession(model, 'child3', { parentSession: parent });
let parentChanges = 0;
parent.onDidChangeReplElements(() => ++parentChanges);
parent.appendToRepl('1\n', severity.Info);
assert.equal(parentChanges, 1);
assert.equal(parent.getReplElements().length, 1);
assert.equal(child1.getReplElements().length, 0);
assert.equal(child2.getReplElements().length, 1);
assert.equal(grandChild.getReplElements().length, 1);
assert.equal(child3.getReplElements().length, 0);
grandChild.appendToRepl('1\n', severity.Info);
assert.equal(parentChanges, 2);
assert.equal(parent.getReplElements().length, 2);
assert.equal(child1.getReplElements().length, 0);
assert.equal(child2.getReplElements().length, 2);
assert.equal(grandChild.getReplElements().length, 2);
assert.equal(child3.getReplElements().length, 0);
child3.appendToRepl('1\n', severity.Info);
assert.equal(parentChanges, 2);
assert.equal(parent.getReplElements().length, 2);
assert.equal(child1.getReplElements().length, 0);
assert.equal(child2.getReplElements().length, 2);
assert.equal(grandChild.getReplElements().length, 2);
assert.equal(child3.getReplElements().length, 1);
child1.appendToRepl('1\n', severity.Info);
assert.equal(parentChanges, 2);
assert.equal(parent.getReplElements().length, 2);
assert.equal(child1.getReplElements().length, 1);
assert.equal(child2.getReplElements().length, 2);
assert.equal(grandChild.getReplElements().length, 2);
assert.equal(child3.getReplElements().length, 1);
});
test('repl ordering', async () => {
const session = createMockSession(model);
model.addSession(session);
const adapter = new MockDebugAdapter();
const raw = new RawDebugSession(adapter, undefined!, undefined!, undefined!, undefined!, undefined!);
session.initializeForTest(raw);
await session.addReplExpression(undefined, 'before.1');
assert.equal(session.getReplElements().length, 3);
assert.equal((<ReplEvaluationInput>session.getReplElements()[0]).value, 'before.1');
assert.equal((<SimpleReplElement>session.getReplElements()[1]).value, 'before.1');
assert.equal((<ReplEvaluationResult>session.getReplElements()[2]).value, '=before.1');
await session.addReplExpression(undefined, 'after.2');
await timeout(0);
assert.equal(session.getReplElements().length, 6);
assert.equal((<ReplEvaluationInput>session.getReplElements()[3]).value, 'after.2');
assert.equal((<ReplEvaluationResult>session.getReplElements()[4]).value, '=after.2');
assert.equal((<SimpleReplElement>session.getReplElements()[5]).value, 'after.2');
});
});

View File

@@ -0,0 +1,154 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import severity from 'vs/base/common/severity';
import { DebugModel, StackFrame, Thread } from 'vs/workbench/contrib/debug/common/debugModel';
import { MockRawSession, MockDebugAdapter } from 'vs/workbench/contrib/debug/test/common/mockDebug';
import { SimpleReplElement, RawObjectReplElement, ReplEvaluationInput, ReplModel, ReplEvaluationResult } from 'vs/workbench/contrib/debug/common/replModel';
import { RawDebugSession } from 'vs/workbench/contrib/debug/browser/rawDebugSession';
import { timeout } from 'vs/base/common/async';
import { createMockSession } from 'vs/workbench/contrib/debug/test/browser/callStack.test';
suite('Debug - REPL', () => {
let model: DebugModel;
let rawSession: MockRawSession;
setup(() => {
model = new DebugModel([], [], [], [], [], <any>{ isDirty: (e: any) => false });
rawSession = new MockRawSession();
});
test('repl output', () => {
const session = createMockSession(model);
const repl = new ReplModel();
repl.appendToRepl(session, 'first line\n', severity.Error);
repl.appendToRepl(session, 'second line ', severity.Error);
repl.appendToRepl(session, 'third line ', severity.Error);
repl.appendToRepl(session, 'fourth line', severity.Error);
let elements = <SimpleReplElement[]>repl.getReplElements();
assert.equal(elements.length, 2);
assert.equal(elements[0].value, 'first line\n');
assert.equal(elements[0].severity, severity.Error);
assert.equal(elements[1].value, 'second line third line fourth line');
assert.equal(elements[1].severity, severity.Error);
repl.appendToRepl(session, '1', severity.Warning);
elements = <SimpleReplElement[]>repl.getReplElements();
assert.equal(elements.length, 3);
assert.equal(elements[2].value, '1');
assert.equal(elements[2].severity, severity.Warning);
const keyValueObject = { 'key1': 2, 'key2': 'value' };
repl.appendToRepl(session, new RawObjectReplElement('fakeid', 'fake', keyValueObject), severity.Info);
const element = <RawObjectReplElement>repl.getReplElements()[3];
assert.equal(element.value, 'Object');
assert.deepEqual(element.valueObj, keyValueObject);
repl.removeReplExpressions();
assert.equal(repl.getReplElements().length, 0);
repl.appendToRepl(session, '1\n', severity.Info);
repl.appendToRepl(session, '2', severity.Info);
repl.appendToRepl(session, '3\n4', severity.Info);
repl.appendToRepl(session, '5\n', severity.Info);
repl.appendToRepl(session, '6', severity.Info);
elements = <SimpleReplElement[]>repl.getReplElements();
assert.equal(elements.length, 3);
assert.equal(elements[0], '1\n');
assert.equal(elements[1], '23\n45\n');
assert.equal(elements[2], '6');
});
test('repl merging', () => {
// 'mergeWithParent' should be ignored when there is no parent.
const parent = createMockSession(model, 'parent', { repl: 'mergeWithParent' });
const child1 = createMockSession(model, 'child1', { parentSession: parent, repl: 'separate' });
const child2 = createMockSession(model, 'child2', { parentSession: parent, repl: 'mergeWithParent' });
const grandChild = createMockSession(model, 'grandChild', { parentSession: child2, repl: 'mergeWithParent' });
const child3 = createMockSession(model, 'child3', { parentSession: parent });
let parentChanges = 0;
parent.onDidChangeReplElements(() => ++parentChanges);
parent.appendToRepl('1\n', severity.Info);
assert.equal(parentChanges, 1);
assert.equal(parent.getReplElements().length, 1);
assert.equal(child1.getReplElements().length, 0);
assert.equal(child2.getReplElements().length, 1);
assert.equal(grandChild.getReplElements().length, 1);
assert.equal(child3.getReplElements().length, 0);
grandChild.appendToRepl('1\n', severity.Info);
assert.equal(parentChanges, 2);
assert.equal(parent.getReplElements().length, 2);
assert.equal(child1.getReplElements().length, 0);
assert.equal(child2.getReplElements().length, 2);
assert.equal(grandChild.getReplElements().length, 2);
assert.equal(child3.getReplElements().length, 0);
child3.appendToRepl('1\n', severity.Info);
assert.equal(parentChanges, 2);
assert.equal(parent.getReplElements().length, 2);
assert.equal(child1.getReplElements().length, 0);
assert.equal(child2.getReplElements().length, 2);
assert.equal(grandChild.getReplElements().length, 2);
assert.equal(child3.getReplElements().length, 1);
child1.appendToRepl('1\n', severity.Info);
assert.equal(parentChanges, 2);
assert.equal(parent.getReplElements().length, 2);
assert.equal(child1.getReplElements().length, 1);
assert.equal(child2.getReplElements().length, 2);
assert.equal(grandChild.getReplElements().length, 2);
assert.equal(child3.getReplElements().length, 1);
});
test('repl expressions', () => {
const session = createMockSession(model);
assert.equal(session.getReplElements().length, 0);
model.addSession(session);
session['raw'] = <any>rawSession;
const thread = new Thread(session, 'mockthread', 1);
const stackFrame = new StackFrame(thread, 1, <any>undefined, 'app.js', 'normal', { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 10 }, 1);
const replModel = new ReplModel();
replModel.addReplExpression(session, stackFrame, 'myVariable').then();
replModel.addReplExpression(session, stackFrame, 'myVariable').then();
replModel.addReplExpression(session, stackFrame, 'myVariable').then();
assert.equal(replModel.getReplElements().length, 3);
replModel.getReplElements().forEach(re => {
assert.equal((<ReplEvaluationInput>re).value, 'myVariable');
});
replModel.removeReplExpressions();
assert.equal(replModel.getReplElements().length, 0);
});
test('repl ordering', async () => {
const session = createMockSession(model);
model.addSession(session);
const adapter = new MockDebugAdapter();
const raw = new RawDebugSession(adapter, undefined!, undefined!, undefined!, undefined!, undefined!);
session.initializeForTest(raw);
await session.addReplExpression(undefined, 'before.1');
assert.equal(session.getReplElements().length, 3);
assert.equal((<ReplEvaluationInput>session.getReplElements()[0]).value, 'before.1');
assert.equal((<SimpleReplElement>session.getReplElements()[1]).value, 'before.1');
assert.equal((<ReplEvaluationResult>session.getReplElements()[2]).value, '=before.1');
await session.addReplExpression(undefined, 'after.2');
await timeout(0);
assert.equal(session.getReplElements().length, 6);
assert.equal((<ReplEvaluationInput>session.getReplElements()[3]).value, 'after.2');
assert.equal((<ReplEvaluationResult>session.getReplElements()[4]).value, '=after.2');
assert.equal((<SimpleReplElement>session.getReplElements()[5]).value, 'after.2');
});
});

View File

@@ -0,0 +1,51 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { Expression, DebugModel } from 'vs/workbench/contrib/debug/common/debugModel';
// Expressions
function assertWatchExpressions(watchExpressions: Expression[], expectedName: string) {
assert.equal(watchExpressions.length, 2);
watchExpressions.forEach(we => {
assert.equal(we.available, false);
assert.equal(we.reference, 0);
assert.equal(we.name, expectedName);
});
}
suite('Debug - Watch', () => {
let model: DebugModel;
setup(() => {
model = new DebugModel([], [], [], [], [], <any>{ isDirty: (e: any) => false });
});
test('watch expressions', () => {
assert.equal(model.getWatchExpressions().length, 0);
model.addWatchExpression('console');
model.addWatchExpression('console');
let watchExpressions = model.getWatchExpressions();
assertWatchExpressions(watchExpressions, 'console');
model.renameWatchExpression(watchExpressions[0].getId(), 'new_name');
model.renameWatchExpression(watchExpressions[1].getId(), 'new_name');
assertWatchExpressions(model.getWatchExpressions(), 'new_name');
assertWatchExpressions(model.getWatchExpressions(), 'new_name');
model.addWatchExpression('mockExpression');
model.moveWatchExpression(model.getWatchExpressions()[2].getId(), 1);
watchExpressions = model.getWatchExpressions();
assert.equal(watchExpressions[0].name, 'new_name');
assert.equal(watchExpressions[1].name, 'mockExpression');
assert.equal(watchExpressions[2].name, 'new_name');
model.removeWatchExpressions();
assert.equal(model.getWatchExpressions().length, 0);
});
});

View File

@@ -4,7 +4,8 @@
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { formatPII, getExactExpressionStartAndEnd } from 'vs/workbench/contrib/debug/common/debugUtils';
import { formatPII, getExactExpressionStartAndEnd, getVisibleAndSorted } from 'vs/workbench/contrib/debug/common/debugUtils';
import { IConfig } from 'vs/workbench/contrib/debug/common/debug';
suite('Debug - Utils', () => {
test('formatPII', () => {
@@ -37,4 +38,80 @@ suite('Debug - Utils', () => {
assert.deepEqual(getExactExpressionStartAndEnd('var t = a.b;c.d.name', 16, 20), { start: 13, end: 20 });
assert.deepEqual(getExactExpressionStartAndEnd('var t = a.b.c-d.name', 16, 20), { start: 15, end: 20 });
});
test('config presentation', () => {
const configs: IConfig[] = [];
configs.push({
type: 'node',
request: 'launch',
name: 'p'
});
configs.push({
type: 'node',
request: 'launch',
name: 'a'
});
configs.push({
type: 'node',
request: 'launch',
name: 'b',
presentation: {
hidden: false
}
});
configs.push({
type: 'node',
request: 'launch',
name: 'c',
presentation: {
hidden: true
}
});
configs.push({
type: 'node',
request: 'launch',
name: 'd',
presentation: {
group: '2_group',
order: 5
}
});
configs.push({
type: 'node',
request: 'launch',
name: 'e',
presentation: {
group: '2_group',
order: 52
}
});
configs.push({
type: 'node',
request: 'launch',
name: 'f',
presentation: {
group: '1_group',
order: 500
}
});
configs.push({
type: 'node',
request: 'launch',
name: 'g',
presentation: {
group: '5_group',
order: 500
}
});
const sorted = getVisibleAndSorted(configs);
assert.equal(sorted.length, 7);
assert.equal(sorted[0].name, 'f');
assert.equal(sorted[1].name, 'd');
assert.equal(sorted[2].name, 'e');
assert.equal(sorted[3].name, 'g');
assert.equal(sorted[4].name, 'b');
assert.equal(sorted[5].name, 'p');
assert.equal(sorted[6].name, 'a');
});
});

View File

@@ -9,7 +9,6 @@ import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { Position, IPosition } from 'vs/editor/common/core/position';
import { ILaunch, IDebugService, State, IDebugSession, IConfigurationManager, IStackFrame, IBreakpointData, IBreakpointUpdateData, IConfig, IDebugModel, IViewModel, IBreakpoint, LoadedSourceEvent, IThread, IRawModelUpdate, IFunctionBreakpoint, IExceptionBreakpoint, IDebugger, IExceptionInfo, AdapterEndEvent, IReplElement, IExpression, IReplElementSource, IDataBreakpoint, IDebugSessionOptions } from 'vs/workbench/contrib/debug/common/debug';
import { Source } from 'vs/workbench/contrib/debug/common/debugSource';
import { CompletionItem } from 'vs/editor/common/modes';
import Severity from 'vs/base/common/severity';
import { AbstractDebugAdapter } from 'vs/workbench/contrib/debug/common/abstractDebugAdapter';
@@ -237,8 +236,8 @@ export class MockSession implements IDebugSession {
return Promise.resolve([]);
}
completions(frameId: number, text: string, position: Position, overwriteBefore: number): Promise<CompletionItem[]> {
return Promise.resolve([]);
completions(frameId: number, text: string, position: Position, overwriteBefore: number): Promise<DebugProtocol.CompletionsResponse> {
throw new Error('not implemented');
}
clearThreads(removeThreads: boolean, reference?: number): void { }

View File

@@ -0,0 +1,52 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { MockDebugAdapter } from 'vs/workbench/contrib/debug/test/common/mockDebug';
import { timeout } from 'vs/base/common/async';
suite('Debug - AbstractDebugAdapter', () => {
suite('event ordering', () => {
let adapter: MockDebugAdapter;
let output: string[];
setup(() => {
adapter = new MockDebugAdapter();
output = [];
adapter.onEvent(ev => {
output.push((ev as DebugProtocol.OutputEvent).body.output);
Promise.resolve().then(() => output.push('--end microtask--'));
});
});
const evaluate = async (expression: string) => {
await new Promise(resolve => adapter.sendRequest('evaluate', { expression }, resolve));
output.push(`=${expression}`);
Promise.resolve().then(() => output.push('--end microtask--'));
};
test('inserts task boundary before response', async () => {
await evaluate('before.foo');
await timeout(0);
assert.deepStrictEqual(output, ['before.foo', '--end microtask--', '=before.foo', '--end microtask--']);
});
test('inserts task boundary after response', async () => {
await evaluate('after.foo');
await timeout(0);
assert.deepStrictEqual(output, ['=after.foo', '--end microtask--', 'after.foo', '--end microtask--']);
});
test('does not insert boundaries between events', async () => {
adapter.sendEventBody('output', { output: 'a' });
adapter.sendEventBody('output', { output: 'b' });
adapter.sendEventBody('output', { output: 'c' });
await timeout(0);
assert.deepStrictEqual(output, ['a', 'b', 'c', '--end microtask--', '--end microtask--', '--end microtask--']);
});
});
});