Skip to content

Commit

Permalink
WIP fix #3312: complete support of source breakpoints
Browse files Browse the repository at this point in the history
Signed-off-by: Anton Kosyakov <[email protected]>
  • Loading branch information
akosyakov committed Dec 10, 2018
1 parent 558d4b5 commit a9e97b4
Show file tree
Hide file tree
Showing 8 changed files with 467 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,16 @@ export namespace DebugCommands {
category: DEBUG_CATEGORY,
label: 'Toggle Breakpoint',
};
export const ADD_CONDITIONAL_BREAKPOINT: Command = {
id: 'debug.breakpoint.add.conditional',
category: DEBUG_CATEGORY,
label: 'Add Conditional Breakpoint...',
};
export const ADD_LOGPOINT: Command = {
id: 'debug.breakpoint.add.logpoint',
category: DEBUG_CATEGORY,
label: 'Add Logpoint...',
};
export const ENABLE_ALL_BREAKPOINTS: Command = {
id: 'debug.breakpoint.enableAll',
category: DEBUG_CATEGORY,
Expand Down Expand Up @@ -237,15 +247,36 @@ export namespace DebugEditorContextCommands {
export const ADD_BREAKPOINT = {
id: 'debug.editor.context.addBreakpoint'
};
export const ADD_CONDITIONAL_BREAKPOINT = {
id: 'debug.editor.context.addBreakpoint.conditional'
};
export const ADD_LOGPOINT = {
id: 'debug.editor.context.add.logpoint'
};
export const REMOVE_BREAKPOINT = {
id: 'debug.editor.context.removeBreakpoint'
};
export const EDIT_BREAKPOINT = {
id: 'debug.editor.context.edit.breakpoint'
};
export const ENABLE_BREAKPOINT = {
id: 'debug.editor.context.enableBreakpoint'
};
export const DISABLE_BREAKPOINT = {
id: 'debug.editor.context.disableBreakpoint'
};
export const REMOVE_LOGPOINT = {
id: 'debug.editor.context.logpoint.remove'
};
export const EDIT_LOGPOINT = {
id: 'debug.editor.context.logpoint.edit'
};
export const ENABLE_LOGPOINT = {
id: 'debug.editor.context.logpoint.enable'
};
export const DISABLE_LOGPOINT = {
id: 'debug.editor.context.logpoint.disable'
};
}

const darkCss = require('../../src/browser/style/debug-dark.useable.css');
Expand Down Expand Up @@ -467,9 +498,16 @@ export class DebugFrontendApplicationContribution extends AbstractViewContributi

registerMenuActions(DebugEditorModel.CONTEXT_MENU,
{ ...DebugEditorContextCommands.ADD_BREAKPOINT, label: 'Add Breakpoint' },
{ ...DebugEditorContextCommands.ADD_CONDITIONAL_BREAKPOINT, label: DebugCommands.ADD_CONDITIONAL_BREAKPOINT.label },
{ ...DebugEditorContextCommands.ADD_LOGPOINT, label: DebugCommands.ADD_LOGPOINT.label },
{ ...DebugEditorContextCommands.REMOVE_BREAKPOINT, label: 'Remove Breakpoint' },
{ ...DebugEditorContextCommands.EDIT_BREAKPOINT, label: 'Edit Breakpoint...' },
{ ...DebugEditorContextCommands.ENABLE_BREAKPOINT, label: 'Enable Breakpoint' },
{ ...DebugEditorContextCommands.DISABLE_BREAKPOINT, label: 'Disable Breakpoint' }
{ ...DebugEditorContextCommands.DISABLE_BREAKPOINT, label: 'Disable Breakpoint' },
{ ...DebugEditorContextCommands.REMOVE_LOGPOINT, label: 'Remove Logpoint' },
{ ...DebugEditorContextCommands.EDIT_LOGPOINT, label: 'Edit Logpoint...' },
{ ...DebugEditorContextCommands.ENABLE_LOGPOINT, label: 'Enable Logpoint' },
{ ...DebugEditorContextCommands.DISABLE_LOGPOINT, label: 'Disable Logpoint' }
);
}

Expand Down Expand Up @@ -608,6 +646,14 @@ export class DebugFrontendApplicationContribution extends AbstractViewContributi
execute: () => this.editors.toggleBreakpoint(),
isEnabled: () => !!this.editors.model
});
registry.registerCommand(DebugCommands.ADD_CONDITIONAL_BREAKPOINT, {
execute: () => this.editors.addBreakpoint('condition'),
isEnabled: () => !!this.editors.model
});
registry.registerCommand(DebugCommands.ADD_LOGPOINT, {
execute: () => this.editors.addBreakpoint('logMessage'),
isEnabled: () => !!this.editors.model
});
registry.registerCommand(DebugCommands.ENABLE_ALL_BREAKPOINTS, {
execute: () => this.breakpointManager.enableAllBreakpoints(true),
isEnabled: () => !!this.breakpointManager.getUris().next().value
Expand Down Expand Up @@ -672,11 +718,26 @@ export class DebugFrontendApplicationContribution extends AbstractViewContributi
isEnabled: () => !this.editors.breakpoint,
isVisible: () => !this.editors.breakpoint
});
registry.registerCommand(DebugEditorContextCommands.ADD_CONDITIONAL_BREAKPOINT, {
execute: () => this.editors.addBreakpoint('condition'),
isEnabled: () => !this.editors.breakpoint,
isVisible: () => !this.editors.breakpoint
});
registry.registerCommand(DebugEditorContextCommands.ADD_LOGPOINT, {
execute: () => this.editors.addBreakpoint('logMessage'),
isEnabled: () => !this.editors.breakpoint,
isVisible: () => !this.editors.breakpoint
});
registry.registerCommand(DebugEditorContextCommands.REMOVE_BREAKPOINT, {
execute: () => this.editors.toggleBreakpoint(),
isEnabled: () => !!this.editors.breakpoint,
isVisible: () => !!this.editors.breakpoint
});
registry.registerCommand(DebugEditorContextCommands.EDIT_BREAKPOINT, {
execute: () => this.editors.showBreakpoint(),
isEnabled: () => !!this.editors.breakpoint,
isVisible: () => !!this.editors.breakpoint
});
registry.registerCommand(DebugEditorContextCommands.ENABLE_BREAKPOINT, {
execute: () => this.editors.setBreakpointEnabled(true),
isEnabled: () => this.editors.breakpointEnabled === false,
Expand All @@ -687,6 +748,26 @@ export class DebugFrontendApplicationContribution extends AbstractViewContributi
isEnabled: () => !!this.editors.breakpointEnabled,
isVisible: () => !!this.editors.breakpointEnabled
});
registry.registerCommand(DebugEditorContextCommands.REMOVE_LOGPOINT, {
execute: () => this.editors.toggleBreakpoint(),
isEnabled: () => !!this.editors.logpoint,
isVisible: () => !!this.editors.logpoint
});
registry.registerCommand(DebugEditorContextCommands.EDIT_LOGPOINT, {
execute: () => this.editors.showBreakpoint(),
isEnabled: () => !!this.editors.logpoint,
isVisible: () => !!this.editors.logpoint
});
registry.registerCommand(DebugEditorContextCommands.ENABLE_LOGPOINT, {
execute: () => this.editors.setBreakpointEnabled(true),
isEnabled: () => this.editors.logpointEnabled === false,
isVisible: () => this.editors.logpointEnabled === false
});
registry.registerCommand(DebugEditorContextCommands.DISABLE_LOGPOINT, {
execute: () => this.editors.setBreakpointEnabled(false),
isEnabled: () => !!this.editors.logpointEnabled,
isVisible: () => !!this.editors.logpointEnabled
});
}

registerKeybindings(keybindings: KeybindingRegistry): void {
Expand Down
153 changes: 153 additions & 0 deletions packages/debug/src/browser/editor/debug-breakpoint-widget.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
/********************************************************************************
* Copyright (C) 2018 TypeFox and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the Eclipse
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
* with the GNU Classpath Exception which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/

import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { DebugProtocol } from 'vscode-debugprotocol';
import { injectable, postConstruct, inject } from 'inversify';
import { Disposable, DisposableCollection } from '@theia/core';
import URI from '@theia/core/lib/common/uri';
import { MonacoEditorProvider } from '@theia/monaco/lib/browser/monaco-editor-provider';
import { MonacoEditorZoneWidget } from '@theia/monaco/lib/browser/monaco-editor-zone-widget';
import { MonacoEditor } from '@theia/monaco/lib/browser/monaco-editor';
import { DebugEditor } from './debug-editor';
import { DebugBreakpoint } from '../model/debug-breakpoint';

export type ShowDebugBreakpointOptions = DebugBreakpoint | {
position: monaco.Position,
context: DebugBreakpointWidget.Context
};

@injectable()
export class DebugBreakpointWidget implements Disposable {

@inject(DebugEditor)
readonly editor: monaco.editor.IStandaloneCodeEditor;

@inject(MonacoEditorProvider)
protected readonly editorProvider: MonacoEditorProvider;

protected selectNode: HTMLDivElement;

protected zone: MonacoEditorZoneWidget;

protected readonly toDispose = new DisposableCollection();

protected context: DebugBreakpointWidget.Context = 'condition';
protected values: {
[context in DebugBreakpointWidget.Context]?: string
} = {};

protected input: MonacoEditor;

@postConstruct()
protected async init(): Promise<void> {
this.toDispose.push(this.zone = new MonacoEditorZoneWidget(this.editor));
this.zone.containerNode.classList.add('theia-debug-breakpoint-widget');

const selectNode = this.selectNode = document.createElement('div');
selectNode.classList.add('theia-debug-breakpoint-select');
this.zone.containerNode.appendChild(selectNode);

const inputNode = document.createElement('div');
inputNode.classList.add('theia-debug-breakpoint-input');
this.zone.containerNode.appendChild(inputNode);

// TODO: move input together with breakpoint decorations
// TODO: placeholder
// TODO: completions
this.toDispose.push(this.input = await this.createInput(inputNode));
this.toDispose.push(this.zone.onDidLayoutChange(dimension => this.layout(dimension)));
this.toDispose.push(this.input.getControl().onDidChangeModelContent(() => {
const heightInLines = this.input.getControl().getModel().getLineCount() + 1;
this.zone.layout(heightInLines);
}));
this.renderSelect();
this.toDispose.push(Disposable.create(() => ReactDOM.unmountComponentAtNode(selectNode)));
}

dispose(): void {
this.toDispose.dispose();
}

show(options: ShowDebugBreakpointOptions): void {
if (options instanceof DebugBreakpoint) {
if (options.logMessage) {
this.context = 'logMessage';
} else if (options.hitCondition && !options.condition) {
this.context = 'hitCondition';
} else {
this.context = 'condition';
}
this.values = {
condition: options.condition,
hitCondition: options.hitCondition,
logMessage: options.logMessage
};
} else {
this.context = options.context;
this.values = {};
}
const editor = this.input.getControl();
const afterLineNumber = options instanceof DebugBreakpoint ? options.line : options.position.lineNumber;
const afterColumn = options instanceof DebugBreakpoint ? options.column : options.position.column;
const heightInLines = editor.getModel().getLineCount() + 1;
this.zone.show({ afterLineNumber, afterColumn, heightInLines });
editor.setPosition(editor.getModel().getPositionAt(editor.getModel().getValueLength()));
this.input.focus();
// TODO ENTER -> apply + focus editor
// TODO SHIFT + ENTER -> new line
// TODO ESC -> abort + focus editor
}

hide(): void {
this.zone.hide();
}

protected layout(dimension: monaco.editor.IDimension): void {
this.input.getControl().layout(dimension);
}

protected createInput(node: HTMLElement): Promise<MonacoEditor> {
return this.editorProvider.createInline(new URI().withScheme('breakpointinput').withPath(this.editor.getId()), node, {
autoSizing: false
});
}

protected renderSelect(): void {
if (this.toDispose.disposed) {
return;
}
ReactDOM.render(<select value={this.context} onChange={this.updateInput}>
{this.renderOption('condition', 'Expression')}
{this.renderOption('hitCondition', 'Hit Count')}
{this.renderOption('logMessage', 'Log Message')}
</select>, this.selectNode);
}
protected renderOption(context: DebugBreakpointWidget.Context, label: string): JSX.Element {
return <option value={context}>{label}</option>;
}
protected readonly updateInput = (e: React.ChangeEvent<HTMLSelectElement>) => {
this.values[this.context] = this.input.getControl().getValue();
this.context = e.currentTarget.value as DebugBreakpointWidget.Context;
this.input.getControl().setValue(this.values[this.context] || '');
this.renderSelect();
}

}
export namespace DebugBreakpointWidget {
export type Context = keyof Pick<DebugProtocol.SourceBreakpoint, 'condition' | 'hitCondition' | 'logMessage'>;
}
6 changes: 6 additions & 0 deletions packages/debug/src/browser/editor/debug-editor-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { DebugSessionManager } from '../debug-session-manager';
import { SourceBreakpoint } from '../breakpoint/breakpoint-marker';
import { DebugEditor } from './debug-editor';
import { DebugHoverWidget, createDebugHoverWidgetContainer } from './debug-hover-widget';
import { DebugBreakpointWidget } from './debug-breakpoint-widget';

export const DebugEditorModelFactory = Symbol('DebugEditorModelFactory');
export type DebugEditorModelFactory = (editor: monaco.editor.IStandaloneCodeEditor) => DebugEditorModel;
Expand All @@ -35,6 +36,7 @@ export class DebugEditorModel implements Disposable {
static createContainer(parent: interfaces.Container, editor: monaco.editor.IStandaloneCodeEditor): Container {
const child = createDebugHoverWidgetContainer(parent, editor);
child.bind(DebugEditorModel).toSelf();
child.bind(DebugBreakpointWidget).toSelf();
return child;
}
static createModel(parent: interfaces.Container, editor: monaco.editor.IStandaloneCodeEditor): DebugEditorModel {
Expand Down Expand Up @@ -72,11 +74,15 @@ export class DebugEditorModel implements Disposable {
@inject(ContextMenuRenderer)
readonly contextMenu: ContextMenuRenderer;

@inject(DebugBreakpointWidget)
readonly breakpointWidget: DebugBreakpointWidget;

@postConstruct()
protected init(): void {
this.uri = new URI(this.editor.getModel().uri.toString());
this.toDispose.pushAll([
this.hover,
this.breakpointWidget,
this.editor.onMouseDown(event => this.handleMouseDown(event)),
this.editor.onMouseMove(event => this.handleMouseMove(event)),
this.editor.onMouseLeave(event => this.handleMouseLeave(event)),
Expand Down
40 changes: 34 additions & 6 deletions packages/debug/src/browser/editor/debug-editor-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { DebugSessionManager } from '../debug-session-manager';
import { DebugEditorModel, DebugEditorModelFactory } from './debug-editor-model';
import { BreakpointManager } from '../breakpoint/breakpoint-manager';
import { DebugBreakpoint } from '../model/debug-breakpoint';
import { DebugBreakpointWidget } from './debug-breakpoint-widget';

@injectable()
export class DebugEditorService {
Expand Down Expand Up @@ -81,20 +82,31 @@ export class DebugEditorService {
const uri = currentEditor && currentEditor.getResourceUri();
return uri && this.models.get(uri.toString());
}

get logpoint(): DebugBreakpoint | undefined {
const breakpoint = this.model && this.model.breakpoint;
return breakpoint && breakpoint.logMessage ? breakpoint : undefined;
}
get logpointEnabled(): boolean | undefined {
const { logpoint } = this;
return logpoint && logpoint.enabled;
}

get breakpoint(): DebugBreakpoint | undefined {
const { model } = this;
return model && model.breakpoint;
const breakpoint = this.model && this.model.breakpoint;
return breakpoint && breakpoint.logMessage ? undefined : breakpoint;
}
get breakpointEnabled(): boolean | undefined {
const { breakpoint } = this;
return breakpoint && breakpoint.enabled;
}

toggleBreakpoint(): void {
const { model } = this;
if (model) {
model.toggleBreakpoint();
}
}
get breakpointEnabled(): boolean | undefined {
const { breakpoint } = this;
return breakpoint && breakpoint.enabled;
}
setBreakpointEnabled(enabled: boolean): void {
const { breakpoint } = this;
if (breakpoint) {
Expand All @@ -118,4 +130,20 @@ export class DebugEditorService {
return false;
}

addBreakpoint(context: DebugBreakpointWidget.Context): void {
const { model } = this;
if (model) {
model.breakpointWidget.show({
position: model.position,
context
});
}
}
showBreakpoint(): void {
const { model, breakpoint } = this;
if (model && breakpoint) {
model.breakpointWidget.show(breakpoint);
}
}

}
Loading

0 comments on commit a9e97b4

Please sign in to comment.