Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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