diff --git a/src/vs/workbench/parts/debug/browser/breakpointWidget.ts b/src/vs/workbench/parts/debug/browser/breakpointWidget.ts index 5e5cfb6954a17..40e1317efeb17 100644 --- a/src/vs/workbench/parts/debug/browser/breakpointWidget.ts +++ b/src/vs/workbench/parts/debug/browser/breakpointWidget.ts @@ -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); } } diff --git a/src/vs/workbench/parts/debug/browser/breakpointsView.ts b/src/vs/workbench/parts/debug/browser/breakpointsView.ts index 5c1da2204e151..eeea9059d8b1f 100644 --- a/src/vs/workbench/parts/debug/browser/breakpointsView.ts +++ b/src/vs/workbench/parts/debug/browser/breakpointsView.ts @@ -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 { diff --git a/src/vs/workbench/parts/debug/browser/media/debug.contribution.css b/src/vs/workbench/parts/debug/browser/media/debug.contribution.css index 8a0a09253ffe1..b70a115a9d31b 100644 --- a/src/vs/workbench/parts/debug/browser/media/debug.contribution.css +++ b/src/vs/workbench/parts/debug/browser/media/debug.contribution.css @@ -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; } diff --git a/src/vs/workbench/parts/debug/common/debug.ts b/src/vs/workbench/parts/debug/common/debug.ts index 705d9ff73c1d9..ad000004ef0eb 100644 --- a/src/vs/workbench/parts/debug/common/debug.ts +++ b/src/vs/workbench/parts/debug/common/debug.ts @@ -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 { diff --git a/src/vs/workbench/parts/debug/common/debugModel.ts b/src/vs/workbench/parts/debug/common/debugModel.ts index b7922fcabc779..0fb3dc1c164ad 100644 --- a/src/vs/workbench/parts/debug/common/debugModel.ts +++ b/src/vs/workbench/parts/debug/common/debugModel.ts @@ -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] }); diff --git a/src/vs/workbench/parts/debug/electron-browser/debugService.ts b/src/vs/workbench/parts/debug/electron-browser/debugService.ts index 3c229b958afd3..e3bee83adc37e 100644 --- a/src/vs/workbench/parts/debug/electron-browser/debugService.ts +++ b/src/vs/workbench/parts/debug/electron-browser/debugService.ts @@ -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) {