diff --git a/packages/debug/src/browser/breakpoint/breakpoint-manager.ts b/packages/debug/src/browser/breakpoint/breakpoint-manager.ts index d6a4a81d6bc46..67549eccdc0e6 100644 --- a/packages/debug/src/browser/breakpoint/breakpoint-manager.ts +++ b/packages/debug/src/browser/breakpoint/breakpoint-manager.ts @@ -77,10 +77,17 @@ export class BreakpointManager extends MarkerManager { return result; } - getBreakpoint(uri: URI, line: number): SourceBreakpoint | undefined { - const marker = this.findMarkers({ + getLineBreakpoints(uri: URI, line: number): SourceBreakpoint[] { + return this.findMarkers({ uri, dataFilter: breakpoint => breakpoint.raw.line === line + }).map(({ data }) => data); + } + + getInlineBreakpoint(uri: URI, line: number, column: number): SourceBreakpoint | undefined { + const marker = this.findMarkers({ + uri, + dataFilter: breakpoint => breakpoint.raw.line === line && breakpoint.raw.column === column })[0]; return marker && marker.data; } @@ -90,13 +97,13 @@ export class BreakpointManager extends MarkerManager { } setBreakpoints(uri: URI, breakpoints: SourceBreakpoint[]): void { - this.setMarkers(uri, this.owner, breakpoints.sort((a, b) => a.raw.line - b.raw.line)); + this.setMarkers(uri, this.owner, breakpoints.sort((a, b) => (a.raw.line - b.raw.line) || ((a.raw.column || 0) - (b.raw.column || 0)))); } addBreakpoint(breakpoint: SourceBreakpoint): boolean { const uri = new URI(breakpoint.uri); const breakpoints = this.getBreakpoints(uri); - const newBreakpoints = breakpoints.filter(({ raw }) => raw.line !== breakpoint.raw.line); + const newBreakpoints = breakpoints.filter(({ raw }) => !(raw.line === breakpoint.raw.line && raw.column === breakpoint.raw.column)); if (breakpoints.length === newBreakpoints.length) { newBreakpoints.push(breakpoint); this.setBreakpoints(uri, newBreakpoints); diff --git a/packages/debug/src/browser/debug-frontend-application-contribution.ts b/packages/debug/src/browser/debug-frontend-application-contribution.ts index b1f7e30688400..762e03b1da2ff 100644 --- a/packages/debug/src/browser/debug-frontend-application-contribution.ts +++ b/packages/debug/src/browser/debug-frontend-application-contribution.ts @@ -47,6 +47,7 @@ import { DebugWatchWidget } from './view/debug-watch-widget'; import { DebugWatchExpression } from './view/debug-watch-expression'; import { DebugWatchManager } from './debug-watch-manager'; import { DebugFunctionBreakpoint } from './model/debug-function-breakpoint'; +import { DebugBreakpoint } from './model/debug-breakpoint'; export namespace DebugMenus { export const DEBUG = [...MAIN_MENU_BAR, '6_debug']; @@ -54,7 +55,9 @@ export namespace DebugMenus { export const DEBUG_CONFIGURATION = [...DEBUG, 'b_configuration']; export const DEBUG_THREADS = [...DEBUG, 'c_threads']; export const DEBUG_SESSIONS = [...DEBUG, 'd_sessions']; - export const DEBUG_BREAKPOINTS = [...DEBUG, 'e_breakpoints']; + export const DEBUG_BREAKPOINT = [...DEBUG, 'e_breakpoint']; + export const DEBUG_NEW_BREAKPOINT = [...DEBUG_BREAKPOINT, 'a_new_breakpoint']; + export const DEBUG_BREAKPOINTS = [...DEBUG, 'f_breakpoints']; } export namespace DebugCommands { @@ -140,6 +143,11 @@ export namespace DebugCommands { category: DEBUG_CATEGORY, label: 'Toggle Breakpoint', }; + export const INLINE_BREAKPOINT: Command = { + id: 'editor.debug.action.inlineBreakpoint', + category: DEBUG_CATEGORY, + label: 'Inline Breakpoint', + }; export const ADD_CONDITIONAL_BREAKPOINT: Command = { id: 'debug.breakpoint.add.conditional', category: DEBUG_CATEGORY, @@ -503,8 +511,17 @@ export class DebugFrontendApplicationContribution extends AbstractViewContributi DebugCommands.CONTINUE_ALL, DebugCommands.PAUSE_ALL ); + registerMenuActions(DebugMenus.DEBUG_BREAKPOINT, + DebugCommands.TOGGLE_BREAKPOINT + ); + menus.registerSubmenu(DebugMenus.DEBUG_NEW_BREAKPOINT, 'New Breakpoint'); + registerMenuActions(DebugMenus.DEBUG_NEW_BREAKPOINT, + DebugCommands.ADD_CONDITIONAL_BREAKPOINT, + DebugCommands.INLINE_BREAKPOINT, + DebugCommands.ADD_FUNCTION_BREAKPOINT, + DebugCommands.ADD_LOGPOINT, + ); registerMenuActions(DebugMenus.DEBUG_BREAKPOINTS, - DebugCommands.TOGGLE_BREAKPOINT, DebugCommands.ENABLE_ALL_BREAKPOINTS, DebugCommands.DISABLE_ALL_BREAKPOINTS, DebugCommands.REMOVE_ALL_BREAKPOINTS @@ -718,6 +735,10 @@ export class DebugFrontendApplicationContribution extends AbstractViewContributi execute: () => this.editors.toggleBreakpoint(), isEnabled: () => !!this.editors.model }); + registry.registerCommand(DebugCommands.INLINE_BREAKPOINT, { + execute: () => this.editors.addInlineBreakpoint(), + isEnabled: () => !!this.editors.model && !this.editors.inlineBreakpoint + }); registry.registerCommand(DebugCommands.ADD_CONDITIONAL_BREAKPOINT, { execute: () => this.editors.addBreakpoint('condition'), isEnabled: () => !!this.editors.model && !this.editors.anyBreakpoint @@ -745,13 +766,15 @@ export class DebugFrontendApplicationContribution extends AbstractViewContributi }); registry.registerCommand(DebugCommands.EDIT_BREAKPOINT, { execute: async () => { - const { selectedBreakpoint } = this; + const { selectedBreakpoint, selectedFunctionBreakpoint } = this; if (selectedBreakpoint) { await this.editors.editBreakpoint(selectedBreakpoint); + } else if (selectedFunctionBreakpoint) { + await selectedFunctionBreakpoint.open(); } }, - isEnabled: () => !!this.selectedBreakpoint, - isVisible: () => !!this.selectedBreakpoint + isEnabled: () => !!this.selectedBreakpoint || !!this.selectedFunctionBreakpoint, + isVisible: () => !!this.selectedBreakpoint || !!this.selectedFunctionBreakpoint }); registry.registerCommand(DebugCommands.EDIT_LOGPOINT, { execute: async () => { @@ -765,13 +788,13 @@ export class DebugFrontendApplicationContribution extends AbstractViewContributi }); registry.registerCommand(DebugCommands.REMOVE_BREAKPOINT, { execute: () => { - const { selectedBreakpoint } = this; + const selectedBreakpoint = this.selectedBreakpoint || this.selectedFunctionBreakpoint; if (selectedBreakpoint) { selectedBreakpoint.remove(); } }, - isEnabled: () => !!this.selectedBreakpoint, - isVisible: () => !!this.selectedBreakpoint + isEnabled: () => !!this.selectedBreakpoint || !!this.selectedFunctionBreakpoint, + isVisible: () => !!this.selectedBreakpoint || !!this.selectedFunctionBreakpoint, }); registry.registerCommand(DebugCommands.REMOVE_LOGPOINT, { execute: () => { @@ -856,7 +879,7 @@ export class DebugFrontendApplicationContribution extends AbstractViewContributi isVisible: () => !this.editors.anyBreakpoint }); registry.registerCommand(DebugEditorContextCommands.REMOVE_BREAKPOINT, { - execute: () => this.editors.toggleBreakpoint(), + execute: () => this.editors.removeBreakpoint(), isEnabled: () => !!this.editors.breakpoint, isVisible: () => !!this.editors.breakpoint }); @@ -876,7 +899,7 @@ export class DebugFrontendApplicationContribution extends AbstractViewContributi isVisible: () => !!this.editors.breakpointEnabled }); registry.registerCommand(DebugEditorContextCommands.REMOVE_LOGPOINT, { - execute: () => this.editors.toggleBreakpoint(), + execute: () => this.editors.removeBreakpoint(), isEnabled: () => !!this.editors.logpoint, isVisible: () => !!this.editors.logpoint }); @@ -1018,6 +1041,11 @@ export class DebugFrontendApplicationContribution extends AbstractViewContributi keybinding: 'f9', context: EditorKeybindingContexts.editorTextFocus }); + keybindings.registerKeybinding({ + command: DebugCommands.INLINE_BREAKPOINT.id, + keybinding: 'shift+f9', + context: EditorKeybindingContexts.editorTextFocus + }); keybindings.registerKeybinding({ command: DebugBreakpointWidgetCommands.ACCEPT.id, @@ -1170,17 +1198,22 @@ export class DebugFrontendApplicationContribution extends AbstractViewContributi const { currentWidget } = this.shell; return currentWidget instanceof DebugBreakpointsWidget && currentWidget || undefined; } - get selectedAnyBreakpoint(): DebugSourceBreakpoint | undefined { + get selectedAnyBreakpoint(): DebugBreakpoint | undefined { const { breakpoints } = this; - return breakpoints && breakpoints.selectedElement instanceof DebugSourceBreakpoint && breakpoints.selectedElement || undefined; + const selectedElement = breakpoints && breakpoints.selectedElement; + return selectedElement instanceof DebugBreakpoint ? selectedElement : undefined; } get selectedBreakpoint(): DebugSourceBreakpoint | undefined { const breakpoint = this.selectedAnyBreakpoint; - return breakpoint && !breakpoint.logMessage ? breakpoint : undefined; + return breakpoint && breakpoint instanceof DebugSourceBreakpoint && !breakpoint.logMessage ? breakpoint : undefined; } get selectedLogpoint(): DebugSourceBreakpoint | undefined { const breakpoint = this.selectedAnyBreakpoint; - return breakpoint && !!breakpoint.logMessage ? breakpoint : undefined; + return breakpoint && breakpoint instanceof DebugSourceBreakpoint && !!breakpoint.logMessage ? breakpoint : undefined; + } + get selectedFunctionBreakpoint(): DebugFunctionBreakpoint | undefined { + const breakpoint = this.selectedAnyBreakpoint; + return breakpoint && breakpoint instanceof DebugFunctionBreakpoint ? breakpoint : undefined; } get variables(): DebugVariablesWidget | undefined { diff --git a/packages/debug/src/browser/debug-frontend-module.ts b/packages/debug/src/browser/debug-frontend-module.ts index 794426c694180..9245bf51be113 100644 --- a/packages/debug/src/browser/debug-frontend-module.ts +++ b/packages/debug/src/browser/debug-frontend-module.ts @@ -52,6 +52,8 @@ import { DebugPrefixConfiguration } from './debug-prefix-configuration'; import { CommandContribution } from '@theia/core/lib/common/command'; import { TabBarToolbarContribution } from '@theia/core/lib/browser/shell/tab-bar-toolbar'; import { DebugWatchManager } from './debug-watch-manager'; +import { MonacoEditorService } from '@theia/monaco/lib/browser/monaco-editor-service'; +import { DebugBreakpointWidget } from './editor/debug-breakpoint-widget'; export default new ContainerModule((bind: interfaces.Bind) => { bind(DebugCallStackItemTypeKey).toDynamicValue(({ container }) => @@ -66,7 +68,10 @@ export default new ContainerModule((bind: interfaces.Bind) => { bind(DebugEditorModelFactory).toDynamicValue(({ container }) => (editor => DebugEditorModel.createModel(container, editor) )).inSingletonScope(); - bind(DebugEditorService).toSelf().inSingletonScope(); + bind(DebugEditorService).toSelf().inSingletonScope().onActivation((context, service) => { + context.container.get(MonacoEditorService).registerDecorationType(DebugBreakpointWidget.PLACEHOLDER_DECORATION, {}); + return service; + }); bind(DebugSessionWidgetFactory).toDynamicValue(({ container }) => (options: DebugViewOptions) => DebugSessionWidget.createWidget(container, options) diff --git a/packages/debug/src/browser/debug-session-manager.ts b/packages/debug/src/browser/debug-session-manager.ts index ceaebf97c401e..899eb1aff94df 100644 --- a/packages/debug/src/browser/debug-session-manager.ts +++ b/packages/debug/src/browser/debug-session-manager.ts @@ -371,12 +371,23 @@ export class DebugSessionManager { return this.breakpoints.findMarkers({ uri }).map(({ data }) => new DebugSourceBreakpoint(data, { labelProvider, breakpoints, editorManager })); } - getBreakpoint(uri: URI, line: number): DebugSourceBreakpoint | undefined { + getLineBreakpoints(uri: URI, line: number): DebugSourceBreakpoint[] { const session = this.currentSession; if (session && session.state > DebugState.Initializing) { - return session.getSourceBreakpoints(uri).filter(breakpoint => breakpoint.line === line)[0]; + return session.getSourceBreakpoints(uri).filter(breakpoint => breakpoint.line === line); } - const origin = this.breakpoints.getBreakpoint(uri, line); + const { labelProvider, breakpoints, editorManager } = this; + return this.breakpoints.getLineBreakpoints(uri, line).map(origin => + new DebugSourceBreakpoint(origin, { labelProvider, breakpoints, editorManager }) + ); + } + + getInlineBreakpoint(uri: URI, line: number, column: number): DebugSourceBreakpoint | undefined { + const session = this.currentSession; + if (session && session.state > DebugState.Initializing) { + return session.getSourceBreakpoints(uri).filter(breakpoint => breakpoint.line === line && breakpoint.column === column)[0]; + } + const origin = this.breakpoints.getInlineBreakpoint(uri, line, column); const { labelProvider, breakpoints, editorManager } = this; return origin && new DebugSourceBreakpoint(origin, { labelProvider, breakpoints, editorManager }); } diff --git a/packages/debug/src/browser/debug-session.tsx b/packages/debug/src/browser/debug-session.tsx index b8f982aadc435..e9b48ef3d9af9 100644 --- a/packages/debug/src/browser/debug-session.tsx +++ b/packages/debug/src/browser/debug-session.tsx @@ -510,7 +510,7 @@ export class DebugSession implements CompositeTreeElement { if (body.reason === 'new') { if (raw.source && typeof raw.line === 'number') { const uri = DebugSource.toUri(raw.source); - const origin = SourceBreakpoint.create(uri, { line: raw.line, column: 1 }); + const origin = SourceBreakpoint.create(uri, { line: raw.line, column: raw.column }); if (this.breakpoints.addBreakpoint(origin)) { const breakpoints = this.getSourceBreakpoints(uri); const breakpoint = new DebugSourceBreakpoint(origin, this.asDebugBreakpointOptions()); @@ -536,7 +536,14 @@ export class DebugSession implements CompositeTreeElement { const toUpdate = this.findBreakpoint(b => b.idFromAdapter === raw.id); if (toUpdate) { toUpdate.update({ raw }); - this.fireDidChangeBreakpoints(toUpdate.uri); + if (toUpdate instanceof DebugSourceBreakpoint) { + const sourceBreakpoints = this.getSourceBreakpoints(toUpdate.uri); + // in order to dedup again if a debugger converted line breakpoint to inline breakpoint + // i.e. assigned a column to a line breakpoint + this.setSourceBreakpoints(toUpdate.uri, sourceBreakpoints); + } else { + this.fireDidChangeBreakpoints(toUpdate.uri); + } } } } finally { @@ -658,19 +665,19 @@ export class DebugSession implements CompositeTreeElement { this.setBreakpoints(uri, distinct); } protected dedupSourceBreakpoints(all: DebugSourceBreakpoint[]): DebugSourceBreakpoint[] { - const lines = new Map(); + const positions = new Map(); for (const breakpoint of all) { - let primary = lines.get(breakpoint.line) || breakpoint; + let primary = positions.get(breakpoint.renderPosition()) || breakpoint; if (primary !== breakpoint) { let secondary = breakpoint; - if (secondary.raw && secondary.raw.line === secondary.origin.raw.line) { + if (secondary.raw && secondary.raw.line === secondary.origin.raw.line && secondary.raw.column === secondary.origin.raw.column) { [primary, secondary] = [breakpoint, primary]; } primary.origins.push(...secondary.origins); } - lines.set(primary.line, primary); + positions.set(primary.renderPosition(), primary); } - return [...lines.values()]; + return [...positions.values()]; } protected *getAffectedUris(uri?: URI): IterableIterator { if (uri) { diff --git a/packages/debug/src/browser/editor/debug-breakpoint-widget.tsx b/packages/debug/src/browser/editor/debug-breakpoint-widget.tsx index ecd296d425739..cbb4a7c52568c 100644 --- a/packages/debug/src/browser/editor/debug-breakpoint-widget.tsx +++ b/packages/debug/src/browser/editor/debug-breakpoint-widget.tsx @@ -233,9 +233,6 @@ export class DebugBreakpointWidget implements Disposable { } } - -monaco.services.StaticServices.codeEditorService.get().registerDecorationType(DebugBreakpointWidget.PLACEHOLDER_DECORATION, {}); - export namespace DebugBreakpointWidget { export type Context = keyof Pick; } diff --git a/packages/debug/src/browser/editor/debug-editor-model.ts b/packages/debug/src/browser/editor/debug-editor-model.ts index b3754fce33f78..dfe996e1f7807 100644 --- a/packages/debug/src/browser/editor/debug-editor-model.ts +++ b/packages/debug/src/browser/editor/debug-editor-model.ts @@ -27,6 +27,7 @@ import { DebugEditor } from './debug-editor'; import { DebugHoverWidget, createDebugHoverWidgetContainer } from './debug-hover-widget'; import { DebugBreakpointWidget } from './debug-breakpoint-widget'; import { DebugExceptionWidget } from './debug-exception-widget'; +import { DebugProtocol } from 'vscode-debugprotocol'; export const DebugEditorModelFactory = Symbol('DebugEditorModelFactory'); export type DebugEditorModelFactory = (editor: DebugEditor) => DebugEditorModel; @@ -124,26 +125,18 @@ export class DebugEditorModel implements Disposable { options: DebugEditorModel.TOP_STACK_FRAME_MARGIN, range }); - - if (currentFrame.thread.stoppedDetails && currentFrame.thread.stoppedDetails.reason === 'exception') { - decorations.push({ - options: DebugEditorModel.TOP_STACK_FRAME_EXCEPTION_DECORATION, - range: columnUntilEOLRange - }); - } else { + decorations.push({ + options: DebugEditorModel.TOP_STACK_FRAME_DECORATION, + range: columnUntilEOLRange + }); + const { topFrameRange } = this; + if (topFrameRange && topFrameRange.startLineNumber === currentFrame.raw.line && topFrameRange.startColumn !== currentFrame.raw.column) { decorations.push({ - options: DebugEditorModel.TOP_STACK_FRAME_DECORATION, + options: DebugEditorModel.TOP_STACK_FRAME_INLINE_DECORATION, range: columnUntilEOLRange }); - const { topFrameRange } = this; - if (topFrameRange && topFrameRange.startLineNumber === currentFrame.raw.line && topFrameRange.startColumn !== currentFrame.raw.column) { - decorations.push({ - options: DebugEditorModel.TOP_STACK_FRAME_INLINE_DECORATION, - range: columnUntilEOLRange - }); - } - this.topFrameRange = columnUntilEOLRange; } + this.topFrameRange = columnUntilEOLRange; } else { decorations.push({ options: DebugEditorModel.FOCUSED_STACK_FRAME_MARGIN, @@ -192,7 +185,8 @@ export class DebugEditorModel implements Disposable { } protected createBreakpointDecoration(breakpoint: SourceBreakpoint): monaco.editor.IModelDeltaDecoration { const lineNumber = breakpoint.raw.line; - const range = new monaco.Range(lineNumber, 1, lineNumber, 2); + const column = breakpoint.raw.column; + const range = typeof column === 'number' ? new monaco.Range(lineNumber, column, lineNumber, column + 1) : new monaco.Range(lineNumber, 1, lineNumber, 2); return { range, options: { @@ -218,14 +212,16 @@ export class DebugEditorModel implements Disposable { } protected createCurrentBreakpointDecoration(breakpoint: DebugSourceBreakpoint): monaco.editor.IModelDeltaDecoration { const lineNumber = breakpoint.line; - const range = new monaco.Range(lineNumber, 1, lineNumber, 1); + const column = breakpoint.column; + const range = typeof column === 'number' ? new monaco.Range(lineNumber, column, lineNumber, column + 1) : new monaco.Range(lineNumber, 1, lineNumber, 1); const { className, message } = breakpoint.getDecoration(); return { range, options: { glyphMarginClassName: className, glyphMarginHoverMessage: message.map(value => ({ value })), - stickiness: DebugEditorModel.STICKINESS + stickiness: DebugEditorModel.STICKINESS, + beforeContentClassName: typeof column === 'number' ? `theia-debug-breakpoint-column ${className}-column` : undefined } }; } @@ -257,9 +253,10 @@ export class DebugEditorModel implements Disposable { const range = this.editor.getControl().getModel().getDecorationRange(decoration); if (range && !lines.has(range.startLineNumber)) { const line = range.startLineNumber; + const column = range.startColumn; const oldRange = this.breakpointRanges.get(decoration); - const oldBreakpoint = oldRange && this.breakpoints.getBreakpoint(uri, oldRange.startLineNumber); - const breakpoint = SourceBreakpoint.create(uri, { line, column: 1 }, oldBreakpoint); + const oldBreakpoint = oldRange && this.breakpoints.getInlineBreakpoint(uri, oldRange.startLineNumber, oldRange.startColumn); + const breakpoint = SourceBreakpoint.create(uri, { line, column }, oldBreakpoint); breakpoints.push(breakpoint); lines.add(line); } @@ -271,39 +268,62 @@ export class DebugEditorModel implements Disposable { get position(): monaco.Position { return this._position || this.editor.getControl().getPosition(); } + get breakpoint(): DebugSourceBreakpoint | undefined { - return this.getBreakpoint(); + return this.inlineBreakpoint || this.getLineBreakpoints()[0]; + } + + get inlineBreakpoint(): DebugSourceBreakpoint | undefined { + return this.getInlineBreakpoint(); + } + + protected getInlineBreakpoint(position: monaco.Position = this.position): DebugSourceBreakpoint | undefined { + return this.sessions.getInlineBreakpoint(this.uri, position.lineNumber, position.column); } - protected getBreakpoint(position: monaco.Position = this.position): DebugBreakpoint | undefined { - return this.sessions.getBreakpoint(this.uri, position.lineNumber); + + protected getLineBreakpoints(position: monaco.Position = this.position): DebugSourceBreakpoint[] { + return this.sessions.getLineBreakpoints(this.uri, position.lineNumber); + } + + protected addBreakpoint(raw: DebugProtocol.SourceBreakpoint): void { + this.breakpoints.addBreakpoint(SourceBreakpoint.create(this.uri, raw)); } + toggleBreakpoint(): void { this.doToggleBreakpoint(); } protected doToggleBreakpoint(position: monaco.Position = this.position): void { - const breakpoint = this.getBreakpoint(position); - if (breakpoint) { - breakpoint.remove(); + const { lineNumber } = position; + const breakpoints = this.getLineBreakpoints(position); + if (breakpoints.length) { + for (const breakpoint of breakpoints) { + breakpoint.remove(); + } } else { - this.breakpoints.addBreakpoint(SourceBreakpoint.create(this.uri, { - line: position.lineNumber, - column: 1 - })); + this.addBreakpoint({ line: lineNumber }); + } + } + + addInlineBreakpoint(): void { + const { position } = this; + const { lineNumber, column } = position; + const breakpoint = this.getInlineBreakpoint(position); + if (breakpoint) { + return; } + this.addBreakpoint({ line: lineNumber, column }); } acceptBreakpoint(): void { const { position, values } = this.breakpointWidget; if (position && values) { - const breakpoint = this.getBreakpoint(position); + const breakpoint = position.column > 0 ? this.getInlineBreakpoint(position) : this.getLineBreakpoints(position)[0]; if (breakpoint) { breakpoint.updateOrigins(values); } else { - this.breakpoints.addBreakpoint(SourceBreakpoint.create(this.uri, { - line: position.lineNumber, - column: 1, - ...values - })); + const { lineNumber } = position; + const column = position.column > 0 ? position.column : undefined; + this.addBreakpoint({ line: lineNumber, column, ...values }); } this.breakpointWidget.hide(); } @@ -342,7 +362,7 @@ export class DebugEditorModel implements Disposable { protected createHintDecorations(event: monaco.editor.IEditorMouseEvent): monaco.editor.IModelDeltaDecoration[] { if (event.target && event.target.type === monaco.editor.MouseTargetType.GUTTER_GLYPH_MARGIN) { const lineNumber = event.target.position.lineNumber; - if (!!this.sessions.getBreakpoint(this.uri, lineNumber)) { + if (this.getLineBreakpoints(event.target.position).length) { return []; } return [{ @@ -407,11 +427,6 @@ export class DebugEditorModel implements Disposable { className: 'theia-debug-top-stack-frame-line', stickiness: DebugEditorModel.STICKINESS }; - static TOP_STACK_FRAME_EXCEPTION_DECORATION: monaco.editor.IModelDecorationOptions = { - isWholeLine: true, - className: 'theia-debug-top-stack-frame-exception-line', - stickiness: DebugEditorModel.STICKINESS - }; static TOP_STACK_FRAME_INLINE_DECORATION: monaco.editor.IModelDecorationOptions = { beforeContentClassName: 'theia-debug-top-stack-frame-column' }; diff --git a/packages/debug/src/browser/editor/debug-editor-service.ts b/packages/debug/src/browser/editor/debug-editor-service.ts index 390748b89caef..ff49ac755a062 100644 --- a/packages/debug/src/browser/editor/debug-editor-service.ts +++ b/packages/debug/src/browser/editor/debug-editor-service.ts @@ -102,6 +102,10 @@ export class DebugEditorService { return breakpoint && breakpoint.enabled; } + get inlineBreakpoint(): DebugSourceBreakpoint | undefined { + return this.model && this.model.inlineBreakpoint; + } + get anyBreakpoint(): DebugSourceBreakpoint | undefined { return this.model && this.model.breakpoint; } @@ -112,6 +116,14 @@ export class DebugEditorService { model.toggleBreakpoint(); } } + + removeBreakpoint(): void { + const { anyBreakpoint } = this; + if (anyBreakpoint) { + anyBreakpoint.remove(); + } + } + setBreakpointEnabled(enabled: boolean): void { const { anyBreakpoint } = this; if (anyBreakpoint) { @@ -119,6 +131,13 @@ export class DebugEditorService { } } + addInlineBreakpoint(): void { + const { model } = this; + if (model) { + model.addInlineBreakpoint(); + } + } + showHover(): void { const { model } = this; if (model) { diff --git a/packages/debug/src/browser/model/debug-function-breakpoint.tsx b/packages/debug/src/browser/model/debug-function-breakpoint.tsx index 4867e3d6d8ce7..85306fc81d955 100644 --- a/packages/debug/src/browser/model/debug-function-breakpoint.tsx +++ b/packages/debug/src/browser/model/debug-function-breakpoint.tsx @@ -45,7 +45,6 @@ export class DebugFunctionBreakpoint extends DebugBreakpoint return !session || !!session.capabilities.supportsFunctionBreakpoints; } - remove(): void { const breakpoints = this.breakpoints.getFunctionBreakpoints(); const newBreakpoints = breakpoints.filter(b => b.id !== this.id); diff --git a/packages/debug/src/browser/model/debug-source-breakpoint.tsx b/packages/debug/src/browser/model/debug-source-breakpoint.tsx index 7ee0c9c576e7d..f7766145af2d7 100644 --- a/packages/debug/src/browser/model/debug-source-breakpoint.tsx +++ b/packages/debug/src/browser/model/debug-source-breakpoint.tsx @@ -49,14 +49,14 @@ export class DebugSourceBreakpoint extends DebugBreakpoint imp setEnabled(enabled: boolean): void { const { uri, raw } = this; let shouldUpdate = false; - let breakpoints = raw && this.doRemove(this.origins.filter(origin => origin.raw.line !== raw.line)); + let breakpoints = raw && this.doRemove(this.origins.filter(origin => !(origin.raw.line === raw.line && origin.raw.column === raw.column))); if (breakpoints) { shouldUpdate = true; } else { breakpoints = this.breakpoints.getBreakpoints(uri); } for (const breakpoint of breakpoints) { - if (breakpoint.raw.line === this.origin.raw.line && breakpoint.enabled !== enabled) { + if (breakpoint.raw.line === this.origin.raw.line && breakpoint.raw.column === this.origin.raw.column && breakpoint.enabled !== enabled) { breakpoint.enabled = enabled; shouldUpdate = true; } @@ -69,10 +69,10 @@ export class DebugSourceBreakpoint extends DebugBreakpoint imp updateOrigins(data: Partial): void { const breakpoints = this.breakpoints.getBreakpoints(this.uri); let shouldUpdate = false; - const originLines = new Set(); - this.origins.forEach(origin => originLines.add(origin.raw.line)); + const originPositions = new Set(); + this.origins.forEach(origin => originPositions.add(origin.raw.line + ':' + origin.raw.column)); for (const breakpoint of breakpoints) { - if (originLines.has(breakpoint.raw.line)) { + if (originPositions.has(breakpoint.raw.line + ':' + breakpoint.raw.column)) { Object.assign(breakpoint.raw, data); shouldUpdate = true; } @@ -149,10 +149,14 @@ export class DebugSourceBreakpoint extends DebugBreakpoint imp {this.labelProvider.getName(this.uri)} {this.labelProvider.getLongName(this.uri.parent)} - {this.line} + {this.renderPosition()} ; } + renderPosition(): string { + return this.line + (typeof this.column === 'number' ? ':' + this.column : ''); + } + doGetDecoration(messages: string[] = []): DebugBreakpointDecoration { if (this.logMessage || this.condition || this.hitCondition) { const { session } = this; @@ -216,12 +220,12 @@ export class DebugSourceBreakpoint extends DebugBreakpoint imp } const { uri } = this; const toRemove = new Set(); - origins.forEach(origin => toRemove.add(origin.raw.line)); + origins.forEach(origin => toRemove.add(origin.raw.line + ':' + origin.raw.column)); let shouldUpdate = false; const breakpoints = this.breakpoints.findMarkers({ uri, dataFilter: data => { - const result = !toRemove.has(data.raw.line); + const result = !toRemove.has(data.raw.line + ':' + data.raw.column); shouldUpdate = shouldUpdate || !result; return result; } diff --git a/packages/debug/src/browser/style/index.css b/packages/debug/src/browser/style/index.css index 1b88a479dc1e6..2e7b1d6b3de96 100644 --- a/packages/debug/src/browser/style/index.css +++ b/packages/debug/src/browser/style/index.css @@ -214,36 +214,59 @@ margin-left: 0px !important; } -.theia-debug-breakpoint { +.monaco-editor .theia-debug-breakpoint-column::before, +.monaco-editor .theia-debug-top-stack-frame-column::before { + content: ' '; + width: 1.3em; + height: 1.3em; + display: inline-block; + vertical-align: text-bottom; +} + +.monaco-editor .theia-debug-top-stack-frame-column { + background: url('current-arrow.svg') center center no-repeat; +} + +.theia-debug-breakpoint, +.monaco-editor .theia-debug-breakpoint-column.theia-debug-breakpoint-column { background: url('breakpoint.svg') center center no-repeat; } -.theia-debug-breakpoint-disabled { +.theia-debug-breakpoint-disabled, +.monaco-editor .theia-debug-breakpoint-column.theia-debug-breakpoint-disabled-column { background: url('breakpoint-disabled.svg') center center no-repeat; } -.theia-debug-breakpoint-unverified { +.theia-debug-breakpoint-unverified, +.monaco-editor .theia-debug-breakpoint-column.theia-debug-breakpoint-unverified-column { background: url('breakpoint-unverified.svg') center center no-repeat; } -.theia-debug-breakpoint-unsupported { +.theia-debug-breakpoint-unsupported, +.monaco-editor .theia-debug-breakpoint-column.theia-debug-breakpoint-unsupported-column { background: url('breakpoint-unsupported.svg') center center no-repeat; } -.theia-debug-conditional-breakpoint { +.theia-debug-conditional-breakpoint, +.monaco-editor .theia-debug-breakpoint-column.theia-debug-conditional-breakpoint-column { background: url('breakpoint-conditional.svg') center center no-repeat; } -.theia-debug-conditional-breakpoint-disabled { +.theia-debug-conditional-breakpoint-disabled, +.monaco-editor .theia-debug-breakpoint-column.theia-debug-conditional-breakpoint-disabled-column { background: url('breakpoint-conditional-disabled.svg') center center no-repeat; } -.theia-debug-conditional-breakpoint-unverified { +.theia-debug-conditional-breakpoint-unverified, +.monaco-editor .theia-debug-breakpoint-column.theia-debug-conditional-breakpoint-unverified-column { background: url('breakpoint-conditional-unverified.svg') center center no-repeat; } -.theia-debug-logpoint { +.theia-debug-logpoint, +.monaco-editor .theia-debug-breakpoint-column.theia-debug-logpoint-column { background: url('breakpoint-log.svg') center center no-repeat; } -.theia-debug-logpoint-disabled { +.theia-debug-logpoint-disabled, +.monaco-editor .theia-debug-breakpoint-column.theia-debug-logpoint-disabled-columne { background: url('breakpoint-log-disabled.svg') center center no-repeat; } -.theia-debug-logpoint-unverified { +.theia-debug-logpoint-unverified, +.monaco-editor .theia-debug-breakpoint-column.theia-debug-logpoint-unverified-column { background: url('breakpoint-log-unverified.svg') center center no-repeat; } @@ -260,7 +283,8 @@ .monaco-editor .theia-debug-top-stack-frame { background: url('current-arrow.svg') center center no-repeat; } -.monaco-editor .theia-debug-top-stack-frame.theia-debug-breakpoint { +.monaco-editor .theia-debug-top-stack-frame.theia-debug-breakpoint, +.monaco-editor .theia-debug-breakpoint-column.theia-debug-breakpoint-column.debug-top-stack-frame-column { background: url('current-and-breakpoint.svg') center center no-repeat; } .monaco-editor .view-overlays .theia-debug-top-stack-frame-line { diff --git a/packages/plugin-ext/src/main/browser/debug/debug-main.ts b/packages/plugin-ext/src/main/browser/debug/debug-main.ts index e5d0b33835c79..5ead68fc8fc63 100644 --- a/packages/plugin-ext/src/main/browser/debug/debug-main.ts +++ b/packages/plugin-ext/src/main/browser/debug/debug-main.ts @@ -183,13 +183,14 @@ export class DebugMainImpl implements DebugMain { for (const breakpoint of newBreakpoints.values()) { if (breakpoint.location) { const location = breakpoint.location; + const column = breakpoint.location.range.startColumn; this.breakpointsManager.addBreakpoint({ id: breakpoint.id, uri: Uri.revive(location.uri).toString(), enabled: breakpoint.enabled, raw: { line: breakpoint.location.range.startLineNumber + 1, - column: 1, + column: column > 0 ? column + 1 : undefined, condition: breakpoint.condition, hitCondition: breakpoint.hitCondition, logMessage: breakpoint.logMessage