Skip to content

Commit

Permalink
Merge pull request #764 from Microsoft/isidorn/function-breakpoints
Browse files Browse the repository at this point in the history
Isidorn/function breakpoints
  • Loading branch information
isidorn committed Nov 27, 2015
2 parents b68a923 + b04ab81 commit 9c4c52b
Show file tree
Hide file tree
Showing 8 changed files with 224 additions and 89 deletions.
19 changes: 17 additions & 2 deletions src/vs/workbench/parts/debug/browser/debugActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,8 @@ export class RemoveBreakpointAction extends AbstractDebugAction {
}

public run(breakpoint: debug.IBreakpoint): Promise {
return this.debugService.toggleBreakpoint(breakpoint.source.uri, breakpoint.lineNumber);
return breakpoint instanceof model.Breakpoint ? this.debugService.toggleBreakpoint(breakpoint.source.uri, breakpoint.lineNumber)
: this.debugService.removeFunctionBreakpoints(breakpoint.getId());
}
}

Expand All @@ -280,7 +281,7 @@ export class RemoveAllBreakpointsAction extends AbstractDebugAction {
}

public run(): Promise {
return this.debugService.clearBreakpoints();
return Promise.join([this.debugService.removeBreakpoints(), this.debugService.removeFunctionBreakpoints()]);
}

protected isEnabled(): boolean {
Expand Down Expand Up @@ -379,6 +380,20 @@ export class ReapplyBreakpointsAction extends AbstractDebugAction {
}
}

export class AddFunctionBreakpointAction extends AbstractDebugAction {
static ID = 'workbench.debug.viewlet.action.addFunctionBreakpointAction';
static LABEL = nls.localize('addFunctionBreakpoint', "Add Function Breakpoint");

constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) {
super(id, label, 'debug-action add-function-breakpoint', debugService, keybindingService);
}

public run(): Promise {
this.debugService.addFunctionBreakpoint();
return Promise.as(null);
}
}

export class ToggleBreakpointAction extends EditorAction {
static ID = 'editor.debug.action.toggleBreakpoint';

Expand Down
166 changes: 100 additions & 66 deletions src/vs/workbench/parts/debug/browser/debugViewer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import nls = require('vs/nls');
import { Promise, TPromise } from 'vs/base/common/winjs.base';
import lifecycle = require('vs/base/common/lifecycle');
import paths = require('vs/base/common/paths');
import async = require('vs/base/common/async');
import errors = require('vs/base/common/errors');
import severity from 'vs/base/common/severity';
import strings = require('vs/base/common/strings');
import { isMacintosh } from 'vs/base/common/platform';
Expand Down Expand Up @@ -65,6 +67,55 @@ export function renderVariable(tree: tree.ITree, variable: model.Variable, data:
}
}

function renderRenameBox(debugService: debug.IDebugService, contextViewService: IContextViewService, tree: tree.ITree, element: any, container: HTMLElement, placeholder: string): void {
let inputBoxContainer = dom.append(container, $('.inputBoxContainer'));
let inputBox = new inputbox.InputBox(inputBoxContainer, contextViewService, {
validationOptions: {
validation: null,
showMessage: false
},
placeholder: placeholder
});

inputBox.value = element.name ? element.name : '';
inputBox.focus();

var disposed = false;
var toDispose: [lifecycle.IDisposable] = [inputBox];

var wrapUp = async.once<any, void>((renamed: boolean) => {
if (!disposed) {
disposed = true;
if (element instanceof model.Expression && renamed && inputBox.value) {
debugService.renameWatchExpression(element.getId(), inputBox.value).done(null, errors.onUnexpectedError);
} else if (element instanceof model.Expression && !element.name) {
debugService.clearWatchExpressions(element.getId());
} else if (element instanceof model.FunctionBreakpoint && renamed && inputBox.value) {
debugService.renameFunctionBreakpoint(element.getId(), inputBox.value).done(null, errors.onUnexpectedError);
} else if (element instanceof model.FunctionBreakpoint && !element.name) {
debugService.removeFunctionBreakpoints(element.getId()).done(null, errors.onUnexpectedError);
}

tree.clearHighlight();
tree.DOMFocus();
// Need to remove the input box since this template will be reused.
container.removeChild(inputBoxContainer);
lifecycle.disposeAll(toDispose);
}
});

toDispose.push(dom.addStandardDisposableListener(inputBox.inputElement, 'keydown', (e: dom.IKeyboardEvent) => {
let isEscape = e.equals(CommonKeybindings.ESCAPE);
let isEnter = e.equals(CommonKeybindings.ENTER);
if (isEscape || isEnter) {
wrapUp(isEnter);
}
}));
toDispose.push(dom.addDisposableListener(inputBox.inputElement, 'blur', () => {
wrapUp(true);
}));
}

export class SimpleActionProvider implements renderer.IActionProvider {

constructor() {
Expand Down Expand Up @@ -543,7 +594,7 @@ export class WatchExpressionsRenderer implements tree.IRenderer {
private renderWatchExpression(tree: tree.ITree, watchExpression: debug.IExpression, data: IWatchExpressionTemplateData): void {
let selectedExpression = this.debugService.getViewModel().getSelectedExpression();
if ((selectedExpression instanceof model.Expression && selectedExpression.getId() === watchExpression.getId()) || (watchExpression instanceof model.Expression && !watchExpression.name)) {
this.renderRenameBox(tree, watchExpression, data);
renderRenameBox(this.debugService, this.contextViewService, tree, watchExpression, data.expression, nls.localize('watchExpressionPlaceholder', "Expression to watch"));
}
data.actionBar.context = watchExpression;

Expand All @@ -557,49 +608,6 @@ export class WatchExpressionsRenderer implements tree.IRenderer {
}
}

private renderRenameBox(tree: tree.ITree, expression: debug.IExpression, data: IWatchExpressionTemplateData): void {
let inputBoxContainer = dom.append(data.expression, $('.inputBoxContainer'));
let inputBox = new inputbox.InputBox(inputBoxContainer, this.contextViewService, {
validationOptions: {
validation: null,
showMessage: false
}
});

inputBox.value = expression.name ? expression.name : '';
inputBox.focus();

var disposed = false;
var toDispose: [lifecycle.IDisposable] = [inputBox];

var wrapUp = async.once<any, void>((renamed: boolean) => {
if (!disposed) {
disposed = true;
if (renamed && inputBox.value) {
this.debugService.renameWatchExpression(expression.getId(), inputBox.value);
} else if (!expression.name) {
this.debugService.clearWatchExpressions(expression.getId());
}
tree.clearHighlight();
tree.DOMFocus();
// Need to remove the input box since this template will be reused.
data.expression.removeChild(inputBoxContainer);
lifecycle.disposeAll(toDispose);
}
});

toDispose.push(dom.addStandardDisposableListener(inputBox.inputElement, 'keydown', (e: dom.IKeyboardEvent) => {
let isEscape = e.equals(CommonKeybindings.ESCAPE);
let isEnter = e.equals(CommonKeybindings.ENTER);
if (isEscape || isEnter) {
wrapUp(isEnter);
}
}));
toDispose.push(dom.addDisposableListener(inputBox.inputElement, 'blur', () => {
wrapUp(true);
}));
}

public disposeTemplate(tree: tree.ITree, templateId: string, templateData: any): void {
// noop
}
Expand Down Expand Up @@ -673,7 +681,7 @@ export class BreakpointsActionProvider extends SimpleActionProvider {
}

public hasSecondaryActions(tree: tree.ITree, element: any): boolean {
return element instanceof model.Breakpoint || element instanceof model.ExceptionBreakpoint;
return element instanceof model.Breakpoint || element instanceof model.ExceptionBreakpoint || element instanceof model.FunctionBreakpoint;
}

public getActions(tree: tree.ITree, element: any): TPromise<actions.IAction[]> {
Expand Down Expand Up @@ -703,6 +711,9 @@ export class BreakpointsActionProvider extends SimpleActionProvider {
actions.push(this.instantiationService.createInstance(debugactions.DisableAllBreakpointsAction, debugactions.DisableAllBreakpointsAction.ID, debugactions.DisableAllBreakpointsAction.LABEL));
actions.push(new actionbar.Separator());

actions.push(this.instantiationService.createInstance(debugactions.AddFunctionBreakpointAction, debugactions.AddFunctionBreakpointAction.ID, debugactions.AddFunctionBreakpointAction.LABEL));
actions.push(new actionbar.Separator());

actions.push(this.instantiationService.createInstance(debugactions.ReapplyBreakpointsAction, debugactions.ReapplyBreakpointsAction.ID, debugactions.ReapplyBreakpointsAction.LABEL));

return Promise.as(actions);
Expand All @@ -723,7 +734,7 @@ export class BreakpointsDataSource implements tree.IDataSource {
var model = <model.Model> element;
var exBreakpoints = <debug.IEnablement[]> model.getExceptionBreakpoints();

return Promise.as(exBreakpoints.concat(model.getBreakpoints()));
return Promise.as(exBreakpoints.concat(model.getFunctionBreakpoints()).concat(model.getBreakpoints()));
}

public getParent(tree: tree.ITree, element: any): Promise {
Expand All @@ -732,6 +743,7 @@ export class BreakpointsDataSource implements tree.IDataSource {
}

interface IExceptionBreakpointTemplateData {
breakpoint: HTMLElement;
name: HTMLElement;
checkbox: HTMLInputElement;
toDisposeBeforeRender: lifecycle.IDisposable[];
Expand All @@ -743,17 +755,23 @@ interface IBreakpointTemplateData extends IExceptionBreakpointTemplateData {
filePath: HTMLElement;
}

interface IFunctionBreakpointTemplateData extends IExceptionBreakpointTemplateData {
actionBar: actionbar.ActionBar;
}

export class BreakpointsRenderer implements tree.IRenderer {

private static EXCEPTION_BREAKPOINT_TEMPLATE_ID = 'exceptionBreakpoint';
private static FUNCTION_BREAKPOINT_TEMPLATE_ID = 'functionBreakpoint';
private static BREAKPOINT_TEMPLATE_ID = 'breakpoint';

constructor(
private actionProvider: BreakpointsActionProvider,
private actionRunner: actions.IActionRunner,
@IMessageService private messageService: IMessageService,
@IWorkspaceContextService private contextService: IWorkspaceContextService,
@debug.IDebugService private debugService: debug.IDebugService
@debug.IDebugService private debugService: debug.IDebugService,
@IContextViewService private contextViewService: IContextViewService
) {
// noop
}
Expand All @@ -766,6 +784,9 @@ export class BreakpointsRenderer implements tree.IRenderer {
if (element instanceof model.Breakpoint) {
return BreakpointsRenderer.BREAKPOINT_TEMPLATE_ID;
}
if (element instanceof model.FunctionBreakpoint) {
return BreakpointsRenderer.FUNCTION_BREAKPOINT_TEMPLATE_ID;
}
if (element instanceof model.ExceptionBreakpoint) {
return BreakpointsRenderer.EXCEPTION_BREAKPOINT_TEMPLATE_ID;
}
Expand All @@ -775,65 +796,73 @@ export class BreakpointsRenderer implements tree.IRenderer {

public renderTemplate(tree: tree.ITree, templateId: string, container: HTMLElement): any {
var data: IBreakpointTemplateData = Object.create(null);
if (templateId === BreakpointsRenderer.BREAKPOINT_TEMPLATE_ID) {
if (templateId === BreakpointsRenderer.BREAKPOINT_TEMPLATE_ID || templateId === BreakpointsRenderer.FUNCTION_BREAKPOINT_TEMPLATE_ID) {
data.actionBar = new actionbar.ActionBar(container, { actionRunner: this.actionRunner });
data.actionBar.push(this.actionProvider.getBreakpointActions(), { icon: true, label: false });
}

var el = dom.append(container, $('.breakpoint'));
data.breakpoint = dom.append(container, $('.breakpoint'));
data.toDisposeBeforeRender = [];

data.checkbox = <HTMLInputElement> $('input');
data.checkbox.type = 'checkbox';
data.checkbox.className = 'checkbox';
dom.append(el, data.checkbox);
dom.append(data.breakpoint, data.checkbox);

data.name = dom.append(el, $('span.name'));
data.name = dom.append(data.breakpoint, $('span.name'));

if (templateId === BreakpointsRenderer.BREAKPOINT_TEMPLATE_ID) {
data.lineNumber = dom.append(el, $('span.line-number'));
data.filePath = dom.append(el, $('span.file-path'));
data.lineNumber = dom.append(data.breakpoint, $('span.line-number'));
data.filePath = dom.append(data.breakpoint, $('span.file-path'));
}

return data;
}

public renderElement(tree: tree.ITree, element: any, templateId: string, templateData: any): void {
templateData.toDisposeBeforeRender = lifecycle.disposeAll(templateData.toDisposeBeforeRender);
templateData.toDisposeBeforeRender.push(dom.addStandardDisposableListener(templateData.checkbox, 'change', (e) => {
this.debugService.toggleEnablement(element);
}));

if (templateId === BreakpointsRenderer.EXCEPTION_BREAKPOINT_TEMPLATE_ID) {
this.renderExceptionBreakpoint(element, templateData);
} else if (templateId === BreakpointsRenderer.FUNCTION_BREAKPOINT_TEMPLATE_ID) {
this.renderFunctionBreakpoint(tree, element, templateData);
} else {
this.renderBreakpoint(tree, element, templateData);
}
}

private renderExceptionBreakpoint(exceptionBreakpoint: debug.IExceptionBreakpoint, data: IExceptionBreakpointTemplateData): void {
data.toDisposeBeforeRender = lifecycle.disposeAll(data.toDisposeBeforeRender);
var namePascalCase = exceptionBreakpoint.name.charAt(0).toUpperCase() + exceptionBreakpoint.name.slice(1);
data.name.textContent = `${ namePascalCase} exceptions`;
data.checkbox.checked = exceptionBreakpoint.enabled;
}

data.toDisposeBeforeRender.push(dom.addStandardDisposableListener(data.checkbox, 'change', (e) => {
this.debugService.toggleEnablement(exceptionBreakpoint);
}));
private renderFunctionBreakpoint(tree: tree.ITree, functionBreakpoint: debug.IFunctionBreakpoint, data: IFunctionBreakpointTemplateData): void {
if (!functionBreakpoint.name) {
renderRenameBox(this.debugService, this.contextViewService, tree, functionBreakpoint, data.breakpoint, nls.localize('functionBreakpointPlaceholder', "Function to break on"));
} else {
this.debugService.getModel().areBreakpointsActivated() ? tree.removeTraits('disabled', [functionBreakpoint]) : tree.addTraits('disabled', [functionBreakpoint]);
data.name.textContent = functionBreakpoint.name;
data.checkbox.checked = functionBreakpoint.enabled;
}
data.actionBar.context = functionBreakpoint;
}

private renderBreakpoint(tree: tree.ITree, breakpoint: debug.IBreakpoint, data: IBreakpointTemplateData): void {
this.debugService.getModel().areBreakpointsActivated() ? tree.removeTraits('disabled', [breakpoint]) : tree.addTraits('disabled', [breakpoint]);

data.toDisposeBeforeRender = lifecycle.disposeAll(data.toDisposeBeforeRender);
data.name.textContent = labels.getPathLabel(paths.basename(breakpoint.source.uri.fsPath), this.contextService);
data.lineNumber.textContent = breakpoint.desiredLineNumber !== breakpoint.lineNumber ? breakpoint.desiredLineNumber + ' \u2192 ' + breakpoint.lineNumber : '' + breakpoint.lineNumber;
data.filePath.textContent = labels.getPathLabel(paths.dirname(breakpoint.source.uri.fsPath), this.contextService);
data.checkbox.checked = breakpoint.enabled;
data.actionBar.context = breakpoint;

data.toDisposeBeforeRender.push(dom.addStandardDisposableListener(data.checkbox, 'change', (e) => {
this.debugService.toggleEnablement(breakpoint);
}));
}

public disposeTemplate(tree: tree.ITree, templateId: string, templateData: any): void {
if (templateId === BreakpointsRenderer.BREAKPOINT_TEMPLATE_ID) {
if (templateId === BreakpointsRenderer.BREAKPOINT_TEMPLATE_ID || templateId === BreakpointsRenderer.FUNCTION_BREAKPOINT_TEMPLATE_ID) {
templateData.actionBar.dispose();
}
}
Expand Down Expand Up @@ -869,10 +898,15 @@ export class BreakpointsController extends BaseDebugController {
}

protected onDelete(tree: tree.ITree, event: keyboard.StandardKeyboardEvent): boolean {
var element = tree.getFocus();
const element = tree.getFocus();
if (element instanceof model.Breakpoint) {
var bp = <model.Breakpoint> element;
this.debugService.toggleBreakpoint(bp.source.uri, bp.lineNumber);
const bp = <model.Breakpoint> element;
this.debugService.toggleBreakpoint(bp.source.uri, bp.lineNumber).done(null, errors.onUnexpectedError);

return true;
} else if (element instanceof model.FunctionBreakpoint) {
const fbp = <model.FunctionBreakpoint> element;
this.debugService.removeFunctionBreakpoints(fbp.getId()).done(null, errors.onUnexpectedError);

return true;
}
Expand Down
14 changes: 12 additions & 2 deletions src/vs/workbench/parts/debug/browser/debugViewlet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,8 @@ class BreakpointsView extends viewlet.AdaptiveCollapsibleViewletView {
@IInstantiationService private instantiationService: IInstantiationService
) {
super(actionRunner, BreakpointsView.getExpandedBodySize(
debugService.getModel().getBreakpoints().length + debugService.getModel().getExceptionBreakpoints().length), !!settings[BreakpointsView.MEMENTO], 'breakpointsView', messageService, contextMenuService);
debugService.getModel().getBreakpoints().length + debugService.getModel().getFunctionBreakpoints().length + debugService.getModel().getExceptionBreakpoints().length),
!!settings[BreakpointsView.MEMENTO], 'breakpointsView', messageService, contextMenuService);

this.toDispose.push(this.debugService.getModel().addListener2(debug.ModelEvents.BREAKPOINTS_UPDATED,() => this.onBreakpointsChange()));
}
Expand Down Expand Up @@ -327,6 +328,12 @@ class BreakpointsView extends viewlet.AdaptiveCollapsibleViewletView {
if (second instanceof model.ExceptionBreakpoint) {
return 1;
}
if (first instanceof model.FunctionBreakpoint) {
return -1;
}
if(second instanceof model.FunctionBreakpoint) {
return 1;
}

if (first.source.uri.toString() !== second.source.uri.toString()) {
return first.source.uri.toString().localeCompare(second.source.uri.toString());
Expand Down Expand Up @@ -369,14 +376,17 @@ class BreakpointsView extends viewlet.AdaptiveCollapsibleViewletView {

public getActions(): actions.IAction[] {
return [
this.instantiationService.createInstance(dbgactions.AddFunctionBreakpointAction, dbgactions.AddFunctionBreakpointAction.ID, dbgactions.AddFunctionBreakpointAction.LABEL),
this.instantiationService.createInstance(dbgactions.ReapplyBreakpointsAction, dbgactions.ReapplyBreakpointsAction.ID, dbgactions.ReapplyBreakpointsAction.LABEL),
this.instantiationService.createInstance(dbgactions.ToggleBreakpointsActivatedAction, dbgactions.ToggleBreakpointsActivatedAction.ID, dbgactions.ToggleBreakpointsActivatedAction.LABEL),
this.instantiationService.createInstance(dbgactions.RemoveAllBreakpointsAction, dbgactions.RemoveAllBreakpointsAction.ID, dbgactions.RemoveAllBreakpointsAction.LABEL)
];
}

private onBreakpointsChange(): void {
this.expandedBodySize = BreakpointsView.getExpandedBodySize(this.debugService.getModel().getBreakpoints().length + this.debugService.getModel().getExceptionBreakpoints().length);
const model = this.debugService.getModel();
this.expandedBodySize = BreakpointsView.getExpandedBodySize(
model.getBreakpoints().length + model.getExceptionBreakpoints().length + model.getFunctionBreakpoints().length);

if (this.tree) {
this.tree.refresh();
Expand Down
Loading

0 comments on commit 9c4c52b

Please sign in to comment.