debug: add log point support

#45128
This commit is contained in:
isidor 2018-03-15 18:27:19 +01:00
parent ab1669f898
commit 1d026bbff9
6 changed files with 118 additions and 45 deletions

View file

@ -22,18 +22,21 @@ import { attachInputBoxStyler, attachSelectBoxStyler } from 'vs/platform/theme/c
import { IThemeService } from 'vs/platform/theme/common/themeService';
const $ = dom.$;
const EXPRESSION_PLACEHOLDER = nls.localize('breakpointWidgetExpressionPlaceholder', "Break when expression evaluates to true. 'Enter' to accept, 'esc' to cancel.");
const EXPRESSION_ARIA_LABEL = nls.localize('breakpointWidgetAriaLabel', "The program will only stop here if this condition is true. Press Enter to accept or Escape to cancel.");
const HIT_COUNT_PLACEHOLDER = nls.localize('breakpointWidgetHitCountPlaceholder', "Break when hit count condition is met. 'Enter' to accept, 'esc' to cancel.");
const HIT_COUNT_ARIA_LABEL = nls.localize('breakpointWidgetHitCountAriaLabel', "The program will only stop here if the hit count is met. Press Enter to accept or Escape to cancel.");
enum Context {
CONDITION = 0,
HIT_COUNT = 1,
LOG_MESSAGE = 2
}
export class BreakpointWidget extends ZoneWidget {
private inputBox: InputBox;
private toDispose: lifecycle.IDisposable[];
private hitCountContext: boolean;
private hitCountInput: string;
private conditionInput: string;
private context: Context;
private conditionInput = '';
private hitCountInput = '';
private logMessageInput = '';
private breakpoint: IBreakpoint;
constructor(editor: ICodeEditor, private lineNumber: number, private column: number,
@ -44,8 +47,6 @@ export class BreakpointWidget extends ZoneWidget {
super(editor, { showFrame: true, showArrow: false, frameWidth: 1 });
this.toDispose = [];
this.hitCountInput = '';
this.conditionInput = '';
const uri = this.editor.getModel().uri;
this.breakpoint = this.debugService.getModel().getBreakpoints().filter(bp => bp.lineNumber === this.lineNumber && bp.column === this.column && bp.uri.toString() === uri.toString()).pop();
@ -58,35 +59,70 @@ export class BreakpointWidget extends ZoneWidget {
}
private get placeholder(): string {
return this.hitCountContext ? HIT_COUNT_PLACEHOLDER : EXPRESSION_PLACEHOLDER;
switch (this.context) {
case Context.LOG_MESSAGE:
return nls.localize('breakpointWidgetLogMessagePlaceholder', "Message to log when breakpoint is hit. 'Enter' to accept, 'esc' to cancel.");
case Context.HIT_COUNT:
return nls.localize('breakpointWidgetHitCountPlaceholder', "Break when hit count condition is met. 'Enter' to accept, 'esc' to cancel.");
default:
return nls.localize('breakpointWidgetExpressionPlaceholder', "Break when expression evaluates to true. 'Enter' to accept, 'esc' to cancel.");
}
}
private get ariaLabel(): string {
return this.hitCountContext ? HIT_COUNT_ARIA_LABEL : EXPRESSION_ARIA_LABEL;
switch (this.context) {
case Context.LOG_MESSAGE:
return nls.localize('breakpointWidgetLogMessageAriaLabel', "The program will log this message everytime this breakpoint is hit. Press Enter to accept or Escape to cancel.");
case Context.HIT_COUNT:
return nls.localize('breakpointWidgetHitCountAriaLabel', "The program will only stop here if the hit count is met. Press Enter to accept or Escape to cancel.");
default:
return nls.localize('breakpointWidgetAriaLabel', "The program will only stop here if this condition is true. Press Enter to accept or Escape to cancel.");
}
}
private getInputBoxValue(breakpoint: IBreakpoint): string {
if (this.hitCountContext) {
return breakpoint && breakpoint.hitCondition ? breakpoint.hitCondition : this.hitCountInput;
switch (this.context) {
case Context.LOG_MESSAGE:
return breakpoint && breakpoint.logMessage ? breakpoint.logMessage : this.logMessageInput;
case Context.HIT_COUNT:
return breakpoint && breakpoint.hitCondition ? breakpoint.hitCondition : this.hitCountInput;
default:
return breakpoint && breakpoint.condition ? breakpoint.condition : this.conditionInput;
}
}
return breakpoint && breakpoint.condition ? breakpoint.condition : this.conditionInput;
private rememberInput(): void {
switch (this.context) {
case Context.LOG_MESSAGE:
this.logMessageInput = this.inputBox.value;
break;
case Context.HIT_COUNT:
this.hitCountInput = this.inputBox.value;
break;
default:
this.conditionInput = this.inputBox.value;
}
}
protected _fillContainer(container: HTMLElement): void {
this.setCssClass('breakpoint-widget');
this.hitCountContext = this.breakpoint && this.breakpoint.hitCondition && !this.breakpoint.condition;
const selected = this.hitCountContext ? 1 : 0;
const selectBox = new SelectBox([nls.localize('expression', "Expression"), nls.localize('hitCount', "Hit Count")], selected, this.contextViewService);
let selected: number;
if (this.breakpoint && !this.breakpoint.condition && !this.breakpoint.hitCondition && this.breakpoint.logMessage) {
this.context = Context.LOG_MESSAGE;
selected = 2;
} else if (this.breakpoint && !this.breakpoint.condition && this.breakpoint.hitCondition) {
this.context = Context.HIT_COUNT;
selected = 1;
} else {
this.context = Context.CONDITION;
selected = 0;
}
const selectBox = new SelectBox([nls.localize('expression', "Expression"), nls.localize('hitCount', "Hit Count"), nls.localize('logMessage', "Log Message")], selected, this.contextViewService);
this.toDispose.push(attachSelectBoxStyler(selectBox, this.themeService));
selectBox.render(dom.append(container, $('.breakpoint-select-container')));
selectBox.onDidSelect(e => {
this.hitCountContext = e.selected === 'Hit Count';
if (this.hitCountContext) {
this.conditionInput = this.inputBox.value;
} else {
this.hitCountInput = this.inputBox.value;
}
this.rememberInput();
this.context = e.index;
this.inputBox.setAriaLabel(this.ariaLabel);
this.inputBox.setPlaceHolder(this.placeholder);
@ -115,17 +151,17 @@ export class BreakpointWidget extends ZoneWidget {
let condition = this.breakpoint && this.breakpoint.condition;
let hitCondition = this.breakpoint && this.breakpoint.hitCondition;
let logMessage = this.breakpoint && this.breakpoint.logMessage;
this.rememberInput();
if (this.hitCountContext) {
hitCondition = this.inputBox.value;
if (this.conditionInput) {
condition = this.conditionInput;
}
} else {
condition = this.inputBox.value;
if (this.hitCountInput) {
hitCondition = this.hitCountInput;
}
if (this.conditionInput) {
condition = this.conditionInput;
}
if (this.hitCountInput) {
hitCondition = this.hitCountInput;
}
if (this.logMessageInput) {
logMessage = this.logMessageInput;
}
if (this.breakpoint) {
@ -133,7 +169,8 @@ export class BreakpointWidget extends ZoneWidget {
[this.breakpoint.getId()]: {
condition,
hitCondition,
verified: this.breakpoint.verified
verified: this.breakpoint.verified,
logMessage
}
}, false);
} else {
@ -142,7 +179,8 @@ export class BreakpointWidget extends ZoneWidget {
column: this.breakpoint ? this.breakpoint.column : undefined,
enabled: true,
condition,
hitCondition
hitCondition,
logMessage
}]).done(null, errors.onUnexpectedError);
}
}

View file

@ -547,7 +547,7 @@ export function getBreakpointMessageAndClassName(debugService: IDebugService, te
if (!breakpoint.enabled || !debugService.getModel().areBreakpointsActivated()) {
return {
className: breakpoint instanceof FunctionBreakpoint ? 'debug-function-breakpoint-disabled' : 'debug-breakpoint-disabled',
className: breakpoint instanceof FunctionBreakpoint ? 'debug-function-breakpoint-disabled' : breakpoint.logMessage ? 'debug-breakpoint-log-disabled' : 'debug-breakpoint-disabled',
message: nls.localize('breakpointDisabledHover', "Disabled breakpoint"),
};
}
@ -557,7 +557,7 @@ export function getBreakpointMessageAndClassName(debugService: IDebugService, te
};
if (debugActive && !breakpoint.verified) {
return {
className: breakpoint instanceof FunctionBreakpoint ? 'debug-function-breakpoint-unverified' : 'debug-breakpoint-unverified',
className: breakpoint instanceof FunctionBreakpoint ? 'debug-function-breakpoint-unverified' : breakpoint.logMessage ? 'debug-breakpoint-log-unverified' : 'debug-breakpoint-unverified',
message: appendMessage(nls.localize('breakpointUnverifieddHover', "Unverified breakpoint")),
};
}
@ -583,6 +583,20 @@ export function getBreakpointMessageAndClassName(debugService: IDebugService, te
};
}
if (breakpoint.logMessage) {
if (process && breakpoint.condition && !process.session.capabilities.supportsLogPoints) {
return {
className: 'debug-breakpoint-unsupported',
message: nls.localize('logBreakpointUnsupported', "Log points not supported by this debug type"),
};
}
return {
className: 'debug-breakpoint-log',
message: appendMessage(breakpoint.logMessage)
};
}
if (breakpoint.condition || breakpoint.hitCondition) {
if (process && breakpoint.condition && !process.session.capabilities.supportsConditionalBreakpoints) {
return {

View file

@ -74,7 +74,6 @@
.debug-function-breakpoint-unverified {
background: url('breakpoint-function-unverified.svg') center center no-repeat;
}
.debug-function-breakpoint-disabled {
@ -86,6 +85,19 @@
background: url('breakpoint-conditional.svg') center center no-repeat;
}
.debug-breakpoint-log,
.monaco-editor .debug-breakpoint-column.debug-breakpoint-log-column::before {
background: url('breakpoint-log.svg') center center no-repeat;
}
.debug-breakpoint-log-disabled {
background: url('breakpoint-log-disabled.svg') center center no-repeat;
}
.debug-breakpoint-log-unverified {
background: url('breakpoint-log-unverified.svg') center center no-repeat;
}
.debug-breakpoint-unsupported,
.monaco-editor .debug-breakpoint-column.debug-breakpoint-unsupported-column::before {
background: url('breakpoint-unsupported.svg') center center no-repeat;
@ -93,12 +105,14 @@
.monaco-editor .debug-top-stack-frame.debug-breakpoint,
.monaco-editor .debug-top-stack-frame.debug-breakpoint-conditional,
.monaco-editor .debug-top-stack-frame.debug-breakpoint-log,
.monaco-editor .debug-breakpoint-column.debug-breakpoint-column.debug-top-stack-frame-column::before {
background: url('current-and-breakpoint.svg') center center no-repeat;
}
.monaco-editor .debug-focused-stack-frame.debug-breakpoint,
.monaco-editor .debug-focused-stack-frame.debug-breakpoint-conditional {
.monaco-editor .debug-focused-stack-frame.debug-breakpoint-conditional,
.monaco-editor .debug-focused-stack-frame.debug-breakpoint-log {
background: url('stackframe-and-breakpoint.svg') center center no-repeat;
}

View file

@ -230,17 +230,20 @@ export interface IBreakpointData {
column?: number;
enabled?: boolean;
condition?: string;
logMessage?: string;
hitCondition?: string;
}
export interface IBreakpointUpdateData extends DebugProtocol.Breakpoint {
condition?: string;
hitCondition?: string;
logMessage?: string;
}
export interface IBaseBreakpoint extends IEnablement {
condition: string;
hitCondition: string;
logMessage: string;
}
export interface IBreakpoint extends IBaseBreakpoint {

View file

@ -678,6 +678,7 @@ export class Breakpoint implements IBreakpoint {
public enabled: boolean,
public condition: string,
public hitCondition: string,
public logMessage: string,
public adapterData: any,
private id = generateUuid()
) {
@ -697,7 +698,7 @@ export class FunctionBreakpoint implements IFunctionBreakpoint {
public verified: boolean;
public idFromAdapter: number;
constructor(public name: string, public enabled: boolean, public hitCondition: string, public condition: string, private id = generateUuid()) {
constructor(public name: string, public enabled: boolean, public hitCondition: string, public condition: string, public logMessage: string, private id = generateUuid()) {
this.verified = false;
}
@ -859,7 +860,7 @@ export class Model implements IModel {
}
public addBreakpoints(uri: uri, rawData: IBreakpointData[], fireEvent = true): Breakpoint[] {
const newBreakpoints = rawData.map(rawBp => new Breakpoint(uri, rawBp.lineNumber, rawBp.column, rawBp.enabled, rawBp.condition, rawBp.hitCondition, undefined, rawBp.id));
const newBreakpoints = rawData.map(rawBp => new Breakpoint(uri, rawBp.lineNumber, rawBp.column, rawBp.enabled, rawBp.condition, rawBp.hitCondition, rawBp.logMessage, rawBp.id));
this.breakpoints = this.breakpoints.concat(newBreakpoints);
this.breakpointsActivated = true;
this.sortAndDeDup();
@ -900,6 +901,9 @@ export class Model implements IModel {
if (!isUndefinedOrNull(bpData.hitCondition)) {
bp.hitCondition = bpData.hitCondition;
}
if (!isUndefinedOrNull(bpData.logMessage)) {
bp.logMessage = bpData.logMessage;
}
updated.push(bp);
}
});
@ -961,7 +965,7 @@ export class Model implements IModel {
}
public addFunctionBreakpoint(functionName: string, id: string): FunctionBreakpoint {
const newFunctionBreakpoint = new FunctionBreakpoint(functionName, true, null, id);
const newFunctionBreakpoint = new FunctionBreakpoint(functionName, true, undefined, undefined, undefined, id);
this.functionBreakpoints.push(newFunctionBreakpoint);
this._onDidChangeBreakpoints.fire({ added: [newFunctionBreakpoint] });

View file

@ -461,7 +461,7 @@ export class DebugService implements debug.IDebugService {
let result: Breakpoint[];
try {
result = JSON.parse(this.storageService.get(DEBUG_BREAKPOINTS_KEY, StorageScope.WORKSPACE, '[]')).map((breakpoint: any) => {
return new Breakpoint(uri.parse(breakpoint.uri.external || breakpoint.source.uri.external), breakpoint.lineNumber, breakpoint.column, breakpoint.enabled, breakpoint.condition, breakpoint.hitCondition, breakpoint.adapterData);
return new Breakpoint(uri.parse(breakpoint.uri.external || breakpoint.source.uri.external), breakpoint.lineNumber, breakpoint.column, breakpoint.enabled, breakpoint.condition, breakpoint.hitCondition, breakpoint.logMessage, breakpoint.adapterData);
});
} catch (e) { }
@ -472,7 +472,7 @@ export class DebugService implements debug.IDebugService {
let result: FunctionBreakpoint[];
try {
result = JSON.parse(this.storageService.get(DEBUG_FUNCTION_BREAKPOINTS_KEY, StorageScope.WORKSPACE, '[]')).map((fb: any) => {
return new FunctionBreakpoint(fb.name, fb.enabled, fb.hitCondition, fb.condition);
return new FunctionBreakpoint(fb.name, fb.enabled, fb.hitCondition, fb.condition, fb.logMessage);
});
} catch (e) { }
@ -1225,7 +1225,7 @@ export class DebugService implements debug.IDebugService {
return session.setBreakpoints({
source: rawSource,
lines: breakpointsToSend.map(bp => bp.lineNumber),
breakpoints: breakpointsToSend.map(bp => ({ line: bp.lineNumber, column: bp.column, condition: bp.condition, hitCondition: bp.hitCondition })),
breakpoints: breakpointsToSend.map(bp => ({ line: bp.lineNumber, column: bp.column, condition: bp.condition, hitCondition: bp.hitCondition, logMessage: bp.logMessage })),
sourceModified
}).then(response => {
if (!response || !response.body) {