mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-03-26 23:00:29 -04:00
1308 lines
48 KiB
TypeScript
1308 lines
48 KiB
TypeScript
/*---------------------------------------------------------------------------------------------
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
|
*--------------------------------------------------------------------------------------------*/
|
|
|
|
import * as nls from 'vs/nls';
|
|
import { TPromise } from 'vs/base/common/winjs.base';
|
|
import * as lifecycle from 'vs/base/common/lifecycle';
|
|
import { KeyCode } from 'vs/base/common/keyCodes';
|
|
import * as paths from 'vs/base/common/paths';
|
|
import * as errors from 'vs/base/common/errors';
|
|
import { equalsIgnoreCase } from 'vs/base/common/strings';
|
|
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
|
import * as dom from 'vs/base/browser/dom';
|
|
import { IMouseEvent, DragMouseEvent } from 'vs/base/browser/mouseEvent';
|
|
import { getPathLabel } from 'vs/base/common/labels';
|
|
import { IAction, IActionRunner } from 'vs/base/common/actions';
|
|
import { IActionItem, Separator } from 'vs/base/browser/ui/actionbar/actionbar';
|
|
import { ITree, IAccessibilityProvider, ContextMenuEvent, IDataSource, IRenderer, DRAG_OVER_REJECT, IDragAndDropData, IDragOverReaction, IActionProvider } from 'vs/base/parts/tree/browser/tree';
|
|
import { InputBox, IInputValidationOptions } from 'vs/base/browser/ui/inputbox/inputBox';
|
|
import { DefaultController, DefaultDragAndDrop, ClickBehavior } from 'vs/base/parts/tree/browser/treeDefaults';
|
|
import { Constants } from 'vs/editor/common/core/uint';
|
|
import { IContextViewService, IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
|
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
|
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
|
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
|
import { IMenuService, IMenu, MenuId } from 'vs/platform/actions/common/actions';
|
|
import { fillInActions } from 'vs/platform/actions/browser/menuItemActionItem';
|
|
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
|
|
import * as debug from 'vs/workbench/parts/debug/common/debug';
|
|
import { Expression, Variable, FunctionBreakpoint, StackFrame, Thread, Process, Breakpoint, ExceptionBreakpoint, Model, Scope, ThreadAndProcessIds } from 'vs/workbench/parts/debug/common/debugModel';
|
|
import { ViewModel } from 'vs/workbench/parts/debug/common/debugViewModel';
|
|
import { ContinueAction, StepOverAction, PauseAction, ReapplyBreakpointsAction, DisableAllBreakpointsAction, RemoveBreakpointAction, RemoveWatchExpressionAction, AddWatchExpressionAction, EditWatchExpressionAction, RemoveAllBreakpointsAction, EnableAllBreakpointsAction, StepOutAction, StepIntoAction, SetValueAction, RemoveAllWatchExpressionsAction, RestartFrameAction, AddToWatchExpressionsAction, StopAction, RestartAction } from 'vs/workbench/parts/debug/browser/debugActions';
|
|
import { CopyValueAction, CopyStackTraceAction } from 'vs/workbench/parts/debug/electron-browser/electronDebugActions';
|
|
import { Source } from 'vs/workbench/parts/debug/common/debugSource';
|
|
import { once } from 'vs/base/common/functional';
|
|
import { attachInputBoxStyler } from 'vs/platform/theme/common/styler';
|
|
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
|
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
|
|
|
const $ = dom.$;
|
|
const booleanRegex = /^true|false$/i;
|
|
const stringRegex = /^(['"]).*\1$/;
|
|
const MAX_VALUE_RENDER_LENGTH_IN_VIEWLET = 1024;
|
|
|
|
export interface IRenderValueOptions {
|
|
preserveWhitespace?: boolean;
|
|
showChanged?: boolean;
|
|
maxValueLength?: number;
|
|
showHover?: boolean;
|
|
}
|
|
|
|
function replaceWhitespace(value: string): string {
|
|
const map = { '\n': '\\n', '\r': '\\r', '\t': '\\t' };
|
|
return value.replace(/[\n\r\t]/g, char => map[char]);
|
|
}
|
|
|
|
export function renderExpressionValue(expressionOrValue: debug.IExpression | string, container: HTMLElement, options: IRenderValueOptions): void {
|
|
let value = typeof expressionOrValue === 'string' ? expressionOrValue : expressionOrValue.value;
|
|
|
|
// remove stale classes
|
|
container.className = 'value';
|
|
// when resolving expressions we represent errors from the server as a variable with name === null.
|
|
if (value === null || ((expressionOrValue instanceof Expression || expressionOrValue instanceof Variable) && !expressionOrValue.available)) {
|
|
dom.addClass(container, 'unavailable');
|
|
if (value !== Expression.DEFAULT_VALUE) {
|
|
dom.addClass(container, 'error');
|
|
}
|
|
} else if (!isNaN(+value)) {
|
|
dom.addClass(container, 'number');
|
|
} else if (booleanRegex.test(value)) {
|
|
dom.addClass(container, 'boolean');
|
|
} else if (stringRegex.test(value)) {
|
|
dom.addClass(container, 'string');
|
|
}
|
|
|
|
if (options.showChanged && (<any>expressionOrValue).valueChanged && value !== Expression.DEFAULT_VALUE) {
|
|
// value changed color has priority over other colors.
|
|
container.className = 'value changed';
|
|
}
|
|
|
|
if (options.maxValueLength && value.length > options.maxValueLength) {
|
|
value = value.substr(0, options.maxValueLength) + '...';
|
|
}
|
|
if (value && !options.preserveWhitespace) {
|
|
container.textContent = replaceWhitespace(value);
|
|
} else {
|
|
container.textContent = value;
|
|
}
|
|
if (options.showHover) {
|
|
container.title = value;
|
|
}
|
|
}
|
|
|
|
export function renderVariable(tree: ITree, variable: Variable, data: IVariableTemplateData, showChanged: boolean): void {
|
|
if (variable.available) {
|
|
data.name.textContent = replaceWhitespace(variable.name);
|
|
data.name.title = variable.type ? variable.type : variable.name;
|
|
}
|
|
|
|
if (variable.value) {
|
|
data.name.textContent += variable.name ? ':' : '';
|
|
renderExpressionValue(variable, data.value, {
|
|
showChanged,
|
|
maxValueLength: MAX_VALUE_RENDER_LENGTH_IN_VIEWLET,
|
|
preserveWhitespace: false,
|
|
showHover: true
|
|
});
|
|
} else {
|
|
data.value.textContent = '';
|
|
data.value.title = '';
|
|
}
|
|
}
|
|
|
|
interface IRenameBoxOptions {
|
|
initialValue: string;
|
|
ariaLabel: string;
|
|
placeholder?: string;
|
|
validationOptions?: IInputValidationOptions;
|
|
}
|
|
|
|
function renderRenameBox(debugService: debug.IDebugService, contextViewService: IContextViewService, themeService: IThemeService, tree: ITree, element: any, container: HTMLElement, options: IRenameBoxOptions): void {
|
|
let inputBoxContainer = dom.append(container, $('.inputBoxContainer'));
|
|
let inputBox = new InputBox(inputBoxContainer, contextViewService, {
|
|
validationOptions: options.validationOptions,
|
|
placeholder: options.placeholder,
|
|
ariaLabel: options.ariaLabel
|
|
});
|
|
const styler = attachInputBoxStyler(inputBox, themeService);
|
|
|
|
tree.setHighlight();
|
|
inputBox.value = options.initialValue ? options.initialValue : '';
|
|
inputBox.focus();
|
|
inputBox.select();
|
|
|
|
let disposed = false;
|
|
const toDispose: [lifecycle.IDisposable] = [inputBox, styler];
|
|
|
|
const wrapUp = once((renamed: boolean) => {
|
|
if (!disposed) {
|
|
disposed = true;
|
|
if (element instanceof Expression && renamed && inputBox.value) {
|
|
debugService.renameWatchExpression(element.getId(), inputBox.value).done(null, errors.onUnexpectedError);
|
|
} else if (element instanceof Expression && !element.name) {
|
|
debugService.removeWatchExpressions(element.getId());
|
|
} else if (element instanceof FunctionBreakpoint && inputBox.value) {
|
|
debugService.renameFunctionBreakpoint(element.getId(), renamed ? inputBox.value : element.name).done(null, errors.onUnexpectedError);
|
|
} else if (element instanceof FunctionBreakpoint && !element.name) {
|
|
debugService.removeFunctionBreakpoints(element.getId()).done(null, errors.onUnexpectedError);
|
|
} else if (element instanceof Variable) {
|
|
element.errorMessage = null;
|
|
if (renamed && element.value !== inputBox.value) {
|
|
element.setVariable(inputBox.value)
|
|
// if everything went fine we need to refresh ui elements since the variable update can change watch and variables view
|
|
.done(() => tree.refresh(element, false), errors.onUnexpectedError);
|
|
}
|
|
}
|
|
|
|
tree.clearHighlight();
|
|
tree.DOMFocus();
|
|
tree.setFocus(element);
|
|
|
|
// need to remove the input box since this template will be reused.
|
|
container.removeChild(inputBoxContainer);
|
|
lifecycle.dispose(toDispose);
|
|
}
|
|
});
|
|
|
|
toDispose.push(dom.addStandardDisposableListener(inputBox.inputElement, 'keydown', (e: IKeyboardEvent) => {
|
|
const isEscape = e.equals(KeyCode.Escape);
|
|
const isEnter = e.equals(KeyCode.Enter);
|
|
if (isEscape || isEnter) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
wrapUp(isEnter);
|
|
}
|
|
}));
|
|
toDispose.push(dom.addDisposableListener(inputBox.inputElement, 'blur', () => {
|
|
wrapUp(true);
|
|
}));
|
|
}
|
|
|
|
function getSourceName(source: Source, contextService: IWorkspaceContextService, environmentService?: IEnvironmentService): string {
|
|
if (source.name) {
|
|
return source.name;
|
|
}
|
|
|
|
return paths.basename(source.uri.fsPath);
|
|
}
|
|
|
|
export class BaseDebugController extends DefaultController {
|
|
|
|
private contributedContextMenu: IMenu;
|
|
|
|
constructor(
|
|
private actionProvider: IActionProvider,
|
|
menuId: MenuId,
|
|
@debug.IDebugService protected debugService: debug.IDebugService,
|
|
@IWorkbenchEditorService protected editorService: IWorkbenchEditorService,
|
|
@IContextMenuService private contextMenuService: IContextMenuService,
|
|
@IContextKeyService contextKeyService: IContextKeyService,
|
|
@IMenuService menuService: IMenuService
|
|
) {
|
|
super({ clickBehavior: ClickBehavior.ON_MOUSE_UP, keyboardSupport: false });
|
|
|
|
this.contributedContextMenu = menuService.createMenu(menuId, contextKeyService);
|
|
}
|
|
|
|
public onContextMenu(tree: ITree, element: debug.IEnablement, event: ContextMenuEvent): boolean {
|
|
if (event.target && event.target.tagName && event.target.tagName.toLowerCase() === 'input') {
|
|
return false;
|
|
}
|
|
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
|
|
tree.setFocus(element);
|
|
|
|
if (this.actionProvider.hasSecondaryActions(tree, element)) {
|
|
const anchor = { x: event.posx + 1, y: event.posy };
|
|
this.contextMenuService.showContextMenu({
|
|
getAnchor: () => anchor,
|
|
getActions: () => this.actionProvider.getSecondaryActions(tree, element).then(actions => {
|
|
fillInActions(this.contributedContextMenu, { arg: this.getContext(element) }, actions);
|
|
return actions;
|
|
}),
|
|
onHide: (wasCancelled?: boolean) => {
|
|
if (wasCancelled) {
|
|
tree.DOMFocus();
|
|
}
|
|
},
|
|
getActionsContext: () => element
|
|
});
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
protected getContext(element: any): any {
|
|
return undefined;
|
|
}
|
|
}
|
|
|
|
// call stack
|
|
|
|
export class CallStackController extends BaseDebugController {
|
|
|
|
protected onLeftClick(tree: ITree, element: any, event: IMouseEvent): boolean {
|
|
if (element instanceof ThreadAndProcessIds) {
|
|
return this.showMoreStackFrames(tree, element);
|
|
}
|
|
if (element instanceof StackFrame) {
|
|
super.onLeftClick(tree, element, event);
|
|
this.focusStackFrame(element, event, event.detail !== 2);
|
|
return true;
|
|
}
|
|
|
|
return super.onLeftClick(tree, element, event);
|
|
}
|
|
|
|
protected getContext(element: any): any {
|
|
if (element instanceof StackFrame) {
|
|
if (element.source.inMemory) {
|
|
return element.source.raw.path || element.source.reference;
|
|
}
|
|
|
|
return element.source.uri.toString();
|
|
}
|
|
}
|
|
|
|
// user clicked / pressed on 'Load More Stack Frames', get those stack frames and refresh the tree.
|
|
public showMoreStackFrames(tree: ITree, threadAndProcessIds: ThreadAndProcessIds): boolean {
|
|
const process = this.debugService.getModel().getProcesses().filter(p => p.getId() === threadAndProcessIds.processId).pop();
|
|
const thread = process && process.getThread(threadAndProcessIds.threadId);
|
|
if (thread) {
|
|
(<Thread>thread).fetchCallStack()
|
|
.done(() => tree.refresh(), errors.onUnexpectedError);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public focusStackFrame(stackFrame: debug.IStackFrame, event: IKeyboardEvent | IMouseEvent, preserveFocus: boolean): void {
|
|
this.debugService.focusStackFrameAndEvaluate(stackFrame, undefined, true).then(() => {
|
|
const sideBySide = (event && (event.ctrlKey || event.metaKey));
|
|
return stackFrame.openInEditor(this.editorService, preserveFocus, sideBySide);
|
|
}, errors.onUnexpectedError);
|
|
}
|
|
}
|
|
|
|
|
|
export class CallStackActionProvider implements IActionProvider {
|
|
|
|
constructor( @IInstantiationService private instantiationService: IInstantiationService, @debug.IDebugService private debugService: debug.IDebugService) {
|
|
// noop
|
|
}
|
|
|
|
public hasActions(tree: ITree, element: any): boolean {
|
|
return false;
|
|
}
|
|
|
|
public getActions(tree: ITree, element: any): TPromise<IAction[]> {
|
|
return TPromise.as([]);
|
|
}
|
|
|
|
public hasSecondaryActions(tree: ITree, element: any): boolean {
|
|
return element !== tree.getInput();
|
|
}
|
|
|
|
public getSecondaryActions(tree: ITree, element: any): TPromise<IAction[]> {
|
|
const actions: IAction[] = [];
|
|
if (element instanceof Process) {
|
|
actions.push(this.instantiationService.createInstance(RestartAction, RestartAction.ID, RestartAction.LABEL));
|
|
actions.push(this.instantiationService.createInstance(StopAction, StopAction.ID, StopAction.LABEL));
|
|
} else if (element instanceof Thread) {
|
|
const thread = <Thread>element;
|
|
if (thread.stopped) {
|
|
actions.push(this.instantiationService.createInstance(ContinueAction, ContinueAction.ID, ContinueAction.LABEL));
|
|
actions.push(this.instantiationService.createInstance(StepOverAction, StepOverAction.ID, StepOverAction.LABEL));
|
|
actions.push(this.instantiationService.createInstance(StepIntoAction, StepIntoAction.ID, StepIntoAction.LABEL));
|
|
actions.push(this.instantiationService.createInstance(StepOutAction, StepOutAction.ID, StepOutAction.LABEL));
|
|
} else {
|
|
actions.push(this.instantiationService.createInstance(PauseAction, PauseAction.ID, PauseAction.LABEL));
|
|
}
|
|
} else if (element instanceof StackFrame) {
|
|
if (element.thread.process.session.capabilities.supportsRestartFrame) {
|
|
actions.push(this.instantiationService.createInstance(RestartFrameAction, RestartFrameAction.ID, RestartFrameAction.LABEL));
|
|
}
|
|
actions.push(new CopyStackTraceAction(CopyStackTraceAction.ID, CopyStackTraceAction.LABEL));
|
|
}
|
|
|
|
return TPromise.as(actions);
|
|
}
|
|
|
|
public getActionItem(tree: ITree, element: any, action: IAction): IActionItem {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export class CallStackDataSource implements IDataSource {
|
|
|
|
public getId(tree: ITree, element: any): string {
|
|
if (typeof element === 'string') {
|
|
return element;
|
|
}
|
|
|
|
return element.getId();
|
|
}
|
|
|
|
public hasChildren(tree: ITree, element: any): boolean {
|
|
return element instanceof Model || element instanceof Process || (element instanceof Thread && (<Thread>element).stopped);
|
|
}
|
|
|
|
public getChildren(tree: ITree, element: any): TPromise<any> {
|
|
if (element instanceof Thread) {
|
|
return this.getThreadChildren(element);
|
|
}
|
|
if (element instanceof Model) {
|
|
return TPromise.as(element.getProcesses());
|
|
}
|
|
|
|
const process = <debug.IProcess>element;
|
|
return TPromise.as(process.getAllThreads());
|
|
}
|
|
|
|
private getThreadChildren(thread: Thread): TPromise<any> {
|
|
let callStack: any[] = thread.getCallStack();
|
|
let callStackPromise: TPromise<any> = TPromise.as(null);
|
|
if (!callStack || !callStack.length) {
|
|
callStackPromise = thread.fetchCallStack().then(() => callStack = thread.getCallStack());
|
|
}
|
|
|
|
return callStackPromise.then(() => {
|
|
if (callStack.length === 1 && thread.process.session.capabilities.supportsDelayedStackTraceLoading) {
|
|
// To reduce flashing of the call stack view simply append the stale call stack
|
|
// once we have the correct data the tree will refresh and we will no longer display it.
|
|
callStack = callStack.concat(thread.getStaleCallStack().slice(1));
|
|
}
|
|
|
|
if (thread.stoppedDetails && thread.stoppedDetails.framesErrorMessage) {
|
|
callStack = callStack.concat([thread.stoppedDetails.framesErrorMessage]);
|
|
}
|
|
if (thread.stoppedDetails && thread.stoppedDetails.totalFrames > callStack.length && callStack.length > 1) {
|
|
callStack = callStack.concat([new ThreadAndProcessIds(thread.process.getId(), thread.threadId)]);
|
|
}
|
|
|
|
return callStack;
|
|
});
|
|
}
|
|
|
|
public getParent(tree: ITree, element: any): TPromise<any> {
|
|
return TPromise.as(null);
|
|
}
|
|
}
|
|
|
|
interface IThreadTemplateData {
|
|
thread: HTMLElement;
|
|
name: HTMLElement;
|
|
state: HTMLElement;
|
|
stateLabel: HTMLSpanElement;
|
|
}
|
|
|
|
interface IProcessTemplateData {
|
|
process: HTMLElement;
|
|
name: HTMLElement;
|
|
state: HTMLElement;
|
|
stateLabel: HTMLSpanElement;
|
|
}
|
|
|
|
interface IErrorTemplateData {
|
|
label: HTMLElement;
|
|
}
|
|
|
|
interface ILoadMoreTemplateData {
|
|
label: HTMLElement;
|
|
}
|
|
|
|
interface IStackFrameTemplateData {
|
|
stackFrame: HTMLElement;
|
|
label: HTMLElement;
|
|
file: HTMLElement;
|
|
fileName: HTMLElement;
|
|
lineNumber: HTMLElement;
|
|
}
|
|
|
|
export class CallStackRenderer implements IRenderer {
|
|
|
|
private static THREAD_TEMPLATE_ID = 'thread';
|
|
private static STACK_FRAME_TEMPLATE_ID = 'stackFrame';
|
|
private static ERROR_TEMPLATE_ID = 'error';
|
|
private static LOAD_MORE_TEMPLATE_ID = 'loadMore';
|
|
private static PROCESS_TEMPLATE_ID = 'process';
|
|
|
|
constructor(
|
|
@IWorkspaceContextService private contextService: IWorkspaceContextService,
|
|
@IEnvironmentService private environmentService: IEnvironmentService
|
|
) {
|
|
// noop
|
|
}
|
|
|
|
public getHeight(tree: ITree, element: any): number {
|
|
return 22;
|
|
}
|
|
|
|
public getTemplateId(tree: ITree, element: any): string {
|
|
if (element instanceof Process) {
|
|
return CallStackRenderer.PROCESS_TEMPLATE_ID;
|
|
}
|
|
if (element instanceof Thread) {
|
|
return CallStackRenderer.THREAD_TEMPLATE_ID;
|
|
}
|
|
if (element instanceof StackFrame) {
|
|
return CallStackRenderer.STACK_FRAME_TEMPLATE_ID;
|
|
}
|
|
if (typeof element === 'string') {
|
|
return CallStackRenderer.ERROR_TEMPLATE_ID;
|
|
}
|
|
|
|
return CallStackRenderer.LOAD_MORE_TEMPLATE_ID;
|
|
}
|
|
|
|
public renderTemplate(tree: ITree, templateId: string, container: HTMLElement): any {
|
|
if (templateId === CallStackRenderer.PROCESS_TEMPLATE_ID) {
|
|
let data: IProcessTemplateData = Object.create(null);
|
|
data.process = dom.append(container, $('.process'));
|
|
data.name = dom.append(data.process, $('.name'));
|
|
data.state = dom.append(data.process, $('.state'));
|
|
data.stateLabel = dom.append(data.state, $('span.label'));
|
|
|
|
return data;
|
|
}
|
|
|
|
if (templateId === CallStackRenderer.LOAD_MORE_TEMPLATE_ID) {
|
|
let data: ILoadMoreTemplateData = Object.create(null);
|
|
data.label = dom.append(container, $('.load-more'));
|
|
|
|
return data;
|
|
}
|
|
if (templateId === CallStackRenderer.ERROR_TEMPLATE_ID) {
|
|
let data: ILoadMoreTemplateData = Object.create(null);
|
|
data.label = dom.append(container, $('.error'));
|
|
|
|
return data;
|
|
}
|
|
if (templateId === CallStackRenderer.THREAD_TEMPLATE_ID) {
|
|
let data: IThreadTemplateData = Object.create(null);
|
|
data.thread = dom.append(container, $('.thread'));
|
|
data.name = dom.append(data.thread, $('.name'));
|
|
data.state = dom.append(data.thread, $('.state'));
|
|
data.stateLabel = dom.append(data.state, $('span.label'));
|
|
|
|
return data;
|
|
}
|
|
|
|
let data: IStackFrameTemplateData = Object.create(null);
|
|
data.stackFrame = dom.append(container, $('.stack-frame'));
|
|
data.label = dom.append(data.stackFrame, $('span.label.expression'));
|
|
data.file = dom.append(data.stackFrame, $('.file'));
|
|
data.fileName = dom.append(data.file, $('span.file-name'));
|
|
data.lineNumber = dom.append(data.file, $('span.line-number'));
|
|
|
|
return data;
|
|
}
|
|
|
|
public renderElement(tree: ITree, element: any, templateId: string, templateData: any): void {
|
|
if (templateId === CallStackRenderer.PROCESS_TEMPLATE_ID) {
|
|
this.renderProcess(element, templateData);
|
|
} else if (templateId === CallStackRenderer.THREAD_TEMPLATE_ID) {
|
|
this.renderThread(element, templateData);
|
|
} else if (templateId === CallStackRenderer.STACK_FRAME_TEMPLATE_ID) {
|
|
this.renderStackFrame(element, templateData);
|
|
} else if (templateId === CallStackRenderer.ERROR_TEMPLATE_ID) {
|
|
this.renderError(element, templateData);
|
|
} else if (templateId === CallStackRenderer.LOAD_MORE_TEMPLATE_ID) {
|
|
this.renderLoadMore(element, templateData);
|
|
}
|
|
}
|
|
|
|
private renderProcess(process: debug.IProcess, data: IProcessTemplateData): void {
|
|
data.process.title = nls.localize({ key: 'process', comment: ['Process is a noun'] }, "Process");
|
|
data.name.textContent = process.getName(this.contextService.hasMultiFolderWorkspace());
|
|
const stoppedThread = process.getAllThreads().filter(t => t.stopped).pop();
|
|
|
|
data.stateLabel.textContent = stoppedThread ? nls.localize('paused', "Paused")
|
|
: nls.localize({ key: 'running', comment: ['indicates state'] }, "Running");
|
|
}
|
|
|
|
private renderThread(thread: debug.IThread, data: IThreadTemplateData): void {
|
|
data.thread.title = nls.localize('thread', "Thread");
|
|
data.name.textContent = thread.name;
|
|
|
|
if (thread.stopped) {
|
|
data.stateLabel.textContent = thread.stoppedDetails.description ||
|
|
thread.stoppedDetails.reason ? nls.localize({ key: 'pausedOn', comment: ['indicates reason for program being paused'] }, "Paused on {0}", thread.stoppedDetails.reason) : nls.localize('paused', "Paused");
|
|
} else {
|
|
data.stateLabel.textContent = nls.localize({ key: 'running', comment: ['indicates state'] }, "Running");
|
|
}
|
|
}
|
|
|
|
private renderError(element: string, data: IErrorTemplateData) {
|
|
data.label.textContent = element;
|
|
data.label.title = element;
|
|
}
|
|
|
|
private renderLoadMore(element: any, data: ILoadMoreTemplateData): void {
|
|
data.label.textContent = nls.localize('loadMoreStackFrames', "Load More Stack Frames");
|
|
}
|
|
|
|
private renderStackFrame(stackFrame: debug.IStackFrame, data: IStackFrameTemplateData): void {
|
|
dom.toggleClass(data.stackFrame, 'disabled', !stackFrame.source.available || stackFrame.source.presentationHint === 'deemphasize');
|
|
dom.toggleClass(data.stackFrame, 'label', stackFrame.presentationHint === 'label');
|
|
dom.toggleClass(data.stackFrame, 'subtle', stackFrame.presentationHint === 'subtle');
|
|
|
|
data.file.title = stackFrame.source.raw.path || stackFrame.source.name;
|
|
if (stackFrame.source.raw.origin) {
|
|
data.file.title += `\n${stackFrame.source.raw.origin}`;
|
|
}
|
|
data.label.textContent = stackFrame.name;
|
|
data.label.title = stackFrame.name;
|
|
data.fileName.textContent = getSourceName(stackFrame.source, this.contextService, this.environmentService);
|
|
if (stackFrame.range.startLineNumber !== undefined) {
|
|
data.lineNumber.textContent = `${stackFrame.range.startLineNumber}`;
|
|
if (stackFrame.range.startColumn) {
|
|
data.lineNumber.textContent += `:${stackFrame.range.startColumn}`;
|
|
}
|
|
dom.removeClass(data.lineNumber, 'unavailable');
|
|
} else {
|
|
dom.addClass(data.lineNumber, 'unavailable');
|
|
}
|
|
}
|
|
|
|
public disposeTemplate(tree: ITree, templateId: string, templateData: any): void {
|
|
// noop
|
|
}
|
|
}
|
|
|
|
export class CallstackAccessibilityProvider implements IAccessibilityProvider {
|
|
|
|
constructor( @IWorkspaceContextService private contextService: IWorkspaceContextService) {
|
|
// noop
|
|
}
|
|
|
|
public getAriaLabel(tree: ITree, element: any): string {
|
|
if (element instanceof Thread) {
|
|
return nls.localize('threadAriaLabel', "Thread {0}, callstack, debug", (<Thread>element).name);
|
|
}
|
|
if (element instanceof StackFrame) {
|
|
return nls.localize('stackFrameAriaLabel', "Stack Frame {0} line {1} {2}, callstack, debug", (<StackFrame>element).name, (<StackFrame>element).range.startLineNumber, getSourceName((<StackFrame>element).source, this.contextService));
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// variables
|
|
|
|
export class VariablesActionProvider implements IActionProvider {
|
|
|
|
constructor(private instantiationService: IInstantiationService) {
|
|
// noop
|
|
}
|
|
|
|
public hasActions(tree: ITree, element: any): boolean {
|
|
return false;
|
|
}
|
|
|
|
public getActions(tree: ITree, element: any): TPromise<IAction[]> {
|
|
return TPromise.as([]);
|
|
}
|
|
|
|
public hasSecondaryActions(tree: ITree, element: any): boolean {
|
|
// Only show context menu on "real" variables. Not on array chunk nodes.
|
|
return element instanceof Variable && !!element.value;
|
|
}
|
|
|
|
public getSecondaryActions(tree: ITree, element: any): TPromise<IAction[]> {
|
|
const actions: IAction[] = [];
|
|
const variable = <Variable>element;
|
|
actions.push(this.instantiationService.createInstance(SetValueAction, SetValueAction.ID, SetValueAction.LABEL, variable));
|
|
actions.push(this.instantiationService.createInstance(CopyValueAction, CopyValueAction.ID, CopyValueAction.LABEL, variable));
|
|
actions.push(new Separator());
|
|
actions.push(this.instantiationService.createInstance(AddToWatchExpressionsAction, AddToWatchExpressionsAction.ID, AddToWatchExpressionsAction.LABEL, variable));
|
|
|
|
return TPromise.as(actions);
|
|
}
|
|
|
|
public getActionItem(tree: ITree, element: any, action: IAction): IActionItem {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export class VariablesDataSource implements IDataSource {
|
|
|
|
public getId(tree: ITree, element: any): string {
|
|
return element.getId();
|
|
}
|
|
|
|
public hasChildren(tree: ITree, element: any): boolean {
|
|
if (element instanceof ViewModel || element instanceof Scope) {
|
|
return true;
|
|
}
|
|
|
|
let variable = <Variable>element;
|
|
return variable.hasChildren && !equalsIgnoreCase(variable.value, 'null');
|
|
}
|
|
|
|
public getChildren(tree: ITree, element: any): TPromise<any> {
|
|
if (element instanceof ViewModel) {
|
|
const focusedStackFrame = (<ViewModel>element).focusedStackFrame;
|
|
return focusedStackFrame ? focusedStackFrame.getScopes() : TPromise.as([]);
|
|
}
|
|
|
|
let scope = <Scope>element;
|
|
return scope.getChildren();
|
|
}
|
|
|
|
public getParent(tree: ITree, element: any): TPromise<any> {
|
|
return TPromise.as(null);
|
|
}
|
|
}
|
|
|
|
interface IScopeTemplateData {
|
|
name: HTMLElement;
|
|
}
|
|
|
|
export interface IVariableTemplateData {
|
|
expression: HTMLElement;
|
|
name: HTMLElement;
|
|
value: HTMLElement;
|
|
}
|
|
|
|
export class VariablesRenderer implements IRenderer {
|
|
|
|
private static SCOPE_TEMPLATE_ID = 'scope';
|
|
private static VARIABLE_TEMPLATE_ID = 'variable';
|
|
|
|
constructor(
|
|
@debug.IDebugService private debugService: debug.IDebugService,
|
|
@IContextViewService private contextViewService: IContextViewService,
|
|
@IThemeService private themeService: IThemeService
|
|
) {
|
|
// noop
|
|
}
|
|
|
|
public getHeight(tree: ITree, element: any): number {
|
|
return 22;
|
|
}
|
|
|
|
public getTemplateId(tree: ITree, element: any): string {
|
|
if (element instanceof Scope) {
|
|
return VariablesRenderer.SCOPE_TEMPLATE_ID;
|
|
}
|
|
if (element instanceof Variable) {
|
|
return VariablesRenderer.VARIABLE_TEMPLATE_ID;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public renderTemplate(tree: ITree, templateId: string, container: HTMLElement): any {
|
|
if (templateId === VariablesRenderer.SCOPE_TEMPLATE_ID) {
|
|
let data: IScopeTemplateData = Object.create(null);
|
|
data.name = dom.append(container, $('.scope'));
|
|
|
|
return data;
|
|
}
|
|
|
|
let data: IVariableTemplateData = Object.create(null);
|
|
data.expression = dom.append(container, $('.expression'));
|
|
data.name = dom.append(data.expression, $('span.name'));
|
|
data.value = dom.append(data.expression, $('span.value'));
|
|
|
|
return data;
|
|
}
|
|
|
|
public renderElement(tree: ITree, element: any, templateId: string, templateData: any): void {
|
|
if (templateId === VariablesRenderer.SCOPE_TEMPLATE_ID) {
|
|
this.renderScope(element, templateData);
|
|
} else {
|
|
const variable = <Variable>element;
|
|
if (variable === this.debugService.getViewModel().getSelectedExpression() || variable.errorMessage) {
|
|
renderRenameBox(this.debugService, this.contextViewService, this.themeService, tree, variable, (<IVariableTemplateData>templateData).expression, {
|
|
initialValue: variable.value,
|
|
ariaLabel: nls.localize('variableValueAriaLabel', "Type new variable value"),
|
|
validationOptions: {
|
|
validation: (value: string) => variable.errorMessage ? ({ content: variable.errorMessage }) : null
|
|
}
|
|
});
|
|
} else {
|
|
renderVariable(tree, variable, templateData, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
private renderScope(scope: Scope, data: IScopeTemplateData): void {
|
|
data.name.textContent = scope.name;
|
|
}
|
|
|
|
public disposeTemplate(tree: ITree, templateId: string, templateData: any): void {
|
|
// noop
|
|
}
|
|
}
|
|
|
|
export class VariablesAccessibilityProvider implements IAccessibilityProvider {
|
|
|
|
public getAriaLabel(tree: ITree, element: any): string {
|
|
if (element instanceof Scope) {
|
|
return nls.localize('variableScopeAriaLabel', "Scope {0}, variables, debug", (<Scope>element).name);
|
|
}
|
|
if (element instanceof Variable) {
|
|
return nls.localize('variableAriaLabel', "{0} value {1}, variables, debug", (<Variable>element).name, (<Variable>element).value);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export class VariablesController extends BaseDebugController {
|
|
|
|
protected onLeftClick(tree: ITree, element: any, event: IMouseEvent): boolean {
|
|
// double click on primitive value: open input box to be able to set the value
|
|
if (element instanceof Variable && event.detail === 2) {
|
|
const expression = <debug.IExpression>element;
|
|
this.debugService.getViewModel().setSelectedExpression(expression);
|
|
return true;
|
|
}
|
|
|
|
return super.onLeftClick(tree, element, event);
|
|
}
|
|
}
|
|
|
|
// watch expressions
|
|
|
|
export class WatchExpressionsActionProvider implements IActionProvider {
|
|
|
|
private instantiationService: IInstantiationService;
|
|
|
|
constructor(instantiationService: IInstantiationService) {
|
|
this.instantiationService = instantiationService;
|
|
}
|
|
|
|
public hasActions(tree: ITree, element: any): boolean {
|
|
return element instanceof Expression && !!element.name;
|
|
}
|
|
|
|
public hasSecondaryActions(tree: ITree, element: any): boolean {
|
|
return true;
|
|
}
|
|
|
|
public getActions(tree: ITree, element: any): TPromise<IAction[]> {
|
|
return TPromise.as([]);
|
|
}
|
|
|
|
public getSecondaryActions(tree: ITree, element: any): TPromise<IAction[]> {
|
|
const actions: IAction[] = [];
|
|
if (element instanceof Expression) {
|
|
const expression = <Expression>element;
|
|
actions.push(this.instantiationService.createInstance(AddWatchExpressionAction, AddWatchExpressionAction.ID, AddWatchExpressionAction.LABEL));
|
|
actions.push(this.instantiationService.createInstance(EditWatchExpressionAction, EditWatchExpressionAction.ID, EditWatchExpressionAction.LABEL));
|
|
if (!expression.hasChildren) {
|
|
actions.push(this.instantiationService.createInstance(CopyValueAction, CopyValueAction.ID, CopyValueAction.LABEL, expression.value));
|
|
}
|
|
actions.push(new Separator());
|
|
|
|
actions.push(this.instantiationService.createInstance(RemoveWatchExpressionAction, RemoveWatchExpressionAction.ID, RemoveWatchExpressionAction.LABEL));
|
|
actions.push(this.instantiationService.createInstance(RemoveAllWatchExpressionsAction, RemoveAllWatchExpressionsAction.ID, RemoveAllWatchExpressionsAction.LABEL));
|
|
} else {
|
|
actions.push(this.instantiationService.createInstance(AddWatchExpressionAction, AddWatchExpressionAction.ID, AddWatchExpressionAction.LABEL));
|
|
if (element instanceof Variable) {
|
|
const variable = <Variable>element;
|
|
if (!variable.hasChildren) {
|
|
actions.push(this.instantiationService.createInstance(CopyValueAction, CopyValueAction.ID, CopyValueAction.LABEL, variable.value));
|
|
}
|
|
actions.push(new Separator());
|
|
}
|
|
actions.push(this.instantiationService.createInstance(RemoveAllWatchExpressionsAction, RemoveAllWatchExpressionsAction.ID, RemoveAllWatchExpressionsAction.LABEL));
|
|
}
|
|
|
|
return TPromise.as(actions);
|
|
}
|
|
|
|
public getActionItem(tree: ITree, element: any, action: IAction): IActionItem {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export class WatchExpressionsDataSource implements IDataSource {
|
|
|
|
public getId(tree: ITree, element: any): string {
|
|
return element.getId();
|
|
}
|
|
|
|
public hasChildren(tree: ITree, element: any): boolean {
|
|
if (element instanceof Model) {
|
|
return true;
|
|
}
|
|
|
|
const watchExpression = <Expression>element;
|
|
return watchExpression.hasChildren && !equalsIgnoreCase(watchExpression.value, 'null');
|
|
}
|
|
|
|
public getChildren(tree: ITree, element: any): TPromise<any> {
|
|
if (element instanceof Model) {
|
|
return TPromise.as((<Model>element).getWatchExpressions());
|
|
}
|
|
|
|
let expression = <Expression>element;
|
|
return expression.getChildren();
|
|
}
|
|
|
|
public getParent(tree: ITree, element: any): TPromise<any> {
|
|
return TPromise.as(null);
|
|
}
|
|
}
|
|
|
|
interface IWatchExpressionTemplateData {
|
|
watchExpression: HTMLElement;
|
|
expression: HTMLElement;
|
|
name: HTMLSpanElement;
|
|
value: HTMLSpanElement;
|
|
}
|
|
|
|
export class WatchExpressionsRenderer implements IRenderer {
|
|
|
|
private static WATCH_EXPRESSION_TEMPLATE_ID = 'watchExpression';
|
|
private static VARIABLE_TEMPLATE_ID = 'variables';
|
|
private toDispose: lifecycle.IDisposable[];
|
|
private actionProvider: WatchExpressionsActionProvider;
|
|
|
|
constructor(
|
|
actionProvider: IActionProvider,
|
|
private actionRunner: IActionRunner,
|
|
@debug.IDebugService private debugService: debug.IDebugService,
|
|
@IContextViewService private contextViewService: IContextViewService,
|
|
@IThemeService private themeService: IThemeService
|
|
) {
|
|
this.toDispose = [];
|
|
this.actionProvider = <WatchExpressionsActionProvider>actionProvider;
|
|
}
|
|
|
|
public getHeight(tree: ITree, element: any): number {
|
|
return 22;
|
|
}
|
|
|
|
public getTemplateId(tree: ITree, element: any): string {
|
|
if (element instanceof Expression) {
|
|
return WatchExpressionsRenderer.WATCH_EXPRESSION_TEMPLATE_ID;
|
|
}
|
|
|
|
return WatchExpressionsRenderer.VARIABLE_TEMPLATE_ID;
|
|
}
|
|
|
|
public renderTemplate(tree: ITree, templateId: string, container: HTMLElement): any {
|
|
const createVariableTemplate = ((data: IVariableTemplateData, container: HTMLElement) => {
|
|
data.expression = dom.append(container, $('.expression'));
|
|
data.name = dom.append(data.expression, $('span.name'));
|
|
data.value = dom.append(data.expression, $('span.value'));
|
|
});
|
|
|
|
if (templateId === WatchExpressionsRenderer.WATCH_EXPRESSION_TEMPLATE_ID) {
|
|
const data: IWatchExpressionTemplateData = Object.create(null);
|
|
data.watchExpression = dom.append(container, $('.watch-expression'));
|
|
createVariableTemplate(data, data.watchExpression);
|
|
|
|
return data;
|
|
}
|
|
|
|
const data: IVariableTemplateData = Object.create(null);
|
|
createVariableTemplate(data, container);
|
|
|
|
return data;
|
|
}
|
|
|
|
public renderElement(tree: ITree, element: any, templateId: string, templateData: any): void {
|
|
if (templateId === WatchExpressionsRenderer.WATCH_EXPRESSION_TEMPLATE_ID) {
|
|
this.renderWatchExpression(tree, element, templateData);
|
|
} else {
|
|
renderVariable(tree, element, templateData, true);
|
|
}
|
|
}
|
|
|
|
private renderWatchExpression(tree: ITree, watchExpression: debug.IExpression, data: IWatchExpressionTemplateData): void {
|
|
let selectedExpression = this.debugService.getViewModel().getSelectedExpression();
|
|
if ((selectedExpression instanceof Expression && selectedExpression.getId() === watchExpression.getId()) || (watchExpression instanceof Expression && !watchExpression.name)) {
|
|
renderRenameBox(this.debugService, this.contextViewService, this.themeService, tree, watchExpression, data.expression, {
|
|
initialValue: watchExpression.name,
|
|
placeholder: nls.localize('watchExpressionPlaceholder', "Expression to watch"),
|
|
ariaLabel: nls.localize('watchExpressionInputAriaLabel', "Type watch expression")
|
|
});
|
|
}
|
|
|
|
data.name.textContent = watchExpression.name;
|
|
if (watchExpression.value) {
|
|
data.name.textContent += ':';
|
|
renderExpressionValue(watchExpression, data.value, {
|
|
showChanged: true,
|
|
maxValueLength: MAX_VALUE_RENDER_LENGTH_IN_VIEWLET,
|
|
preserveWhitespace: false,
|
|
showHover: true
|
|
});
|
|
data.name.title = watchExpression.type ? watchExpression.type : watchExpression.value;
|
|
}
|
|
}
|
|
|
|
public disposeTemplate(tree: ITree, templateId: string, templateData: any): void {
|
|
// noop
|
|
}
|
|
|
|
public dispose(): void {
|
|
this.toDispose = lifecycle.dispose(this.toDispose);
|
|
}
|
|
}
|
|
|
|
export class WatchExpressionsAccessibilityProvider implements IAccessibilityProvider {
|
|
|
|
public getAriaLabel(tree: ITree, element: any): string {
|
|
if (element instanceof Expression) {
|
|
return nls.localize('watchExpressionAriaLabel', "{0} value {1}, watch, debug", (<Expression>element).name, (<Expression>element).value);
|
|
}
|
|
if (element instanceof Variable) {
|
|
return nls.localize('watchVariableAriaLabel', "{0} value {1}, watch, debug", (<Variable>element).name, (<Variable>element).value);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export class WatchExpressionsController extends BaseDebugController {
|
|
|
|
protected onLeftClick(tree: ITree, element: any, event: IMouseEvent): boolean {
|
|
// double click on primitive value: open input box to be able to select and copy value.
|
|
if (element instanceof Expression && event.detail === 2) {
|
|
const expression = <debug.IExpression>element;
|
|
this.debugService.getViewModel().setSelectedExpression(expression);
|
|
return true;
|
|
}
|
|
|
|
return super.onLeftClick(tree, element, event);
|
|
}
|
|
}
|
|
|
|
export class WatchExpressionsDragAndDrop extends DefaultDragAndDrop {
|
|
|
|
constructor( @debug.IDebugService private debugService: debug.IDebugService) {
|
|
super();
|
|
}
|
|
|
|
public getDragURI(tree: ITree, element: Expression): string {
|
|
if (!(element instanceof Expression)) {
|
|
return null;
|
|
}
|
|
|
|
return element.getId();
|
|
}
|
|
|
|
public getDragLabel(tree: ITree, elements: Expression[]): string {
|
|
if (elements.length > 1) {
|
|
return String(elements.length);
|
|
}
|
|
|
|
return elements[0].name;
|
|
}
|
|
|
|
public onDragOver(tree: ITree, data: IDragAndDropData, target: Expression | Model, originalEvent: DragMouseEvent): IDragOverReaction {
|
|
if (target instanceof Expression || target instanceof Model) {
|
|
return {
|
|
accept: true,
|
|
autoExpand: false
|
|
};
|
|
}
|
|
|
|
return DRAG_OVER_REJECT;
|
|
}
|
|
|
|
public drop(tree: ITree, data: IDragAndDropData, target: Expression | Model, originalEvent: DragMouseEvent): void {
|
|
const draggedData = data.getData();
|
|
if (Array.isArray(draggedData)) {
|
|
const draggedElement = <Expression>draggedData[0];
|
|
const watches = this.debugService.getModel().getWatchExpressions();
|
|
const position = target instanceof Model ? watches.length - 1 : watches.indexOf(target);
|
|
this.debugService.moveWatchExpression(draggedElement.getId(), position);
|
|
}
|
|
}
|
|
|
|
// {{SQL CARBON EDIT}}
|
|
public dropAbort(tree: ITree, data: IDragAndDropData): void { }
|
|
}
|
|
|
|
// breakpoints
|
|
|
|
export class BreakpointsActionProvider implements IActionProvider {
|
|
|
|
constructor(private instantiationService: IInstantiationService, private debugService: debug.IDebugService) {
|
|
// noop
|
|
}
|
|
|
|
public hasActions(tree: ITree, element: any): boolean {
|
|
return false;
|
|
}
|
|
|
|
public hasSecondaryActions(tree: ITree, element: any): boolean {
|
|
return element instanceof Breakpoint || element instanceof ExceptionBreakpoint || element instanceof FunctionBreakpoint;
|
|
}
|
|
|
|
public getActions(tree: ITree, element: any): TPromise<IAction[]> {
|
|
return TPromise.as([]);
|
|
}
|
|
|
|
public getSecondaryActions(tree: ITree, element: any): TPromise<IAction[]> {
|
|
const actions: IAction[] = [];
|
|
|
|
if (element instanceof Breakpoint || element instanceof FunctionBreakpoint) {
|
|
actions.push(this.instantiationService.createInstance(RemoveBreakpointAction, RemoveBreakpointAction.ID, RemoveBreakpointAction.LABEL));
|
|
}
|
|
if (this.debugService.getModel().getBreakpoints().length + this.debugService.getModel().getFunctionBreakpoints().length > 1) {
|
|
actions.push(this.instantiationService.createInstance(RemoveAllBreakpointsAction, RemoveAllBreakpointsAction.ID, RemoveAllBreakpointsAction.LABEL));
|
|
actions.push(new Separator());
|
|
|
|
actions.push(this.instantiationService.createInstance(EnableAllBreakpointsAction, EnableAllBreakpointsAction.ID, EnableAllBreakpointsAction.LABEL));
|
|
actions.push(this.instantiationService.createInstance(DisableAllBreakpointsAction, DisableAllBreakpointsAction.ID, DisableAllBreakpointsAction.LABEL));
|
|
}
|
|
|
|
actions.push(new Separator());
|
|
actions.push(this.instantiationService.createInstance(ReapplyBreakpointsAction, ReapplyBreakpointsAction.ID, ReapplyBreakpointsAction.LABEL));
|
|
|
|
return TPromise.as(actions);
|
|
}
|
|
|
|
public getActionItem(tree: ITree, element: any, action: IAction): IActionItem {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export class BreakpointsDataSource implements IDataSource {
|
|
|
|
public getId(tree: ITree, element: any): string {
|
|
return element.getId();
|
|
}
|
|
|
|
public hasChildren(tree: ITree, element: any): boolean {
|
|
return element instanceof Model;
|
|
}
|
|
|
|
public getChildren(tree: ITree, element: any): TPromise<any> {
|
|
const model = <Model>element;
|
|
const exBreakpoints = <debug.IEnablement[]>model.getExceptionBreakpoints();
|
|
|
|
return TPromise.as(exBreakpoints.concat(model.getFunctionBreakpoints()).concat(model.getBreakpoints()));
|
|
}
|
|
|
|
public getParent(tree: ITree, element: any): TPromise<any> {
|
|
return TPromise.as(null);
|
|
}
|
|
}
|
|
|
|
interface IBaseBreakpointTemplateData {
|
|
breakpoint: HTMLElement;
|
|
name: HTMLElement;
|
|
checkbox: HTMLInputElement;
|
|
context: debug.IEnablement;
|
|
toDispose: lifecycle.IDisposable[];
|
|
}
|
|
|
|
interface IBreakpointTemplateData extends IBaseBreakpointTemplateData {
|
|
lineNumber: HTMLElement;
|
|
filePath: HTMLElement;
|
|
}
|
|
|
|
export class BreakpointsRenderer implements 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: IActionRunner,
|
|
@IWorkspaceContextService private contextService: IWorkspaceContextService,
|
|
@debug.IDebugService private debugService: debug.IDebugService,
|
|
@IContextViewService private contextViewService: IContextViewService,
|
|
@IThemeService private themeService: IThemeService,
|
|
@IEnvironmentService private environmentService: IEnvironmentService
|
|
) {
|
|
// noop
|
|
}
|
|
|
|
public getHeight(tree: ITree, element: any): number {
|
|
return 22;
|
|
}
|
|
|
|
public getTemplateId(tree: ITree, element: any): string {
|
|
if (element instanceof Breakpoint) {
|
|
return BreakpointsRenderer.BREAKPOINT_TEMPLATE_ID;
|
|
}
|
|
if (element instanceof FunctionBreakpoint) {
|
|
return BreakpointsRenderer.FUNCTION_BREAKPOINT_TEMPLATE_ID;
|
|
}
|
|
if (element instanceof ExceptionBreakpoint) {
|
|
return BreakpointsRenderer.EXCEPTION_BREAKPOINT_TEMPLATE_ID;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public renderTemplate(tree: ITree, templateId: string, container: HTMLElement): any {
|
|
const data: IBreakpointTemplateData = Object.create(null);
|
|
data.breakpoint = dom.append(container, $('.breakpoint'));
|
|
|
|
data.checkbox = <HTMLInputElement>$('input');
|
|
data.checkbox.type = 'checkbox';
|
|
data.toDispose = [];
|
|
data.toDispose.push(dom.addStandardDisposableListener(data.checkbox, 'change', (e) => {
|
|
this.debugService.enableOrDisableBreakpoints(!data.context.enabled, data.context);
|
|
}));
|
|
|
|
dom.append(data.breakpoint, data.checkbox);
|
|
|
|
data.name = dom.append(data.breakpoint, $('span.name'));
|
|
|
|
if (templateId === BreakpointsRenderer.BREAKPOINT_TEMPLATE_ID) {
|
|
data.filePath = dom.append(data.breakpoint, $('span.file-path'));
|
|
const lineNumberContainer = dom.append(data.breakpoint, $('.line-number-container'));
|
|
data.lineNumber = dom.append(lineNumberContainer, $('span.line-number'));
|
|
}
|
|
if (templateId === BreakpointsRenderer.EXCEPTION_BREAKPOINT_TEMPLATE_ID) {
|
|
dom.addClass(data.breakpoint, 'exception');
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
public renderElement(tree: ITree, element: any, templateId: string, templateData: any): void {
|
|
templateData.context = 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: IBaseBreakpointTemplateData): void {
|
|
data.name.textContent = exceptionBreakpoint.label || `${exceptionBreakpoint.filter} exceptions`;
|
|
data.breakpoint.title = data.name.textContent;
|
|
data.checkbox.checked = exceptionBreakpoint.enabled;
|
|
}
|
|
|
|
private renderFunctionBreakpoint(tree: ITree, functionBreakpoint: debug.IFunctionBreakpoint, data: IBaseBreakpointTemplateData): void {
|
|
const selected = this.debugService.getViewModel().getSelectedFunctionBreakpoint();
|
|
if (!functionBreakpoint.name || (selected && selected.getId() === functionBreakpoint.getId())) {
|
|
data.name.textContent = '';
|
|
renderRenameBox(this.debugService, this.contextViewService, this.themeService, tree, functionBreakpoint, data.breakpoint, {
|
|
initialValue: functionBreakpoint.name,
|
|
placeholder: nls.localize('functionBreakpointPlaceholder', "Function to break on"),
|
|
ariaLabel: nls.localize('functionBreakPointInputAriaLabel', "Type function breakpoint")
|
|
});
|
|
} else {
|
|
data.name.textContent = functionBreakpoint.name;
|
|
data.checkbox.checked = functionBreakpoint.enabled;
|
|
data.breakpoint.title = functionBreakpoint.name;
|
|
|
|
// Mark function breakpoints as disabled if deactivated or if debug type does not support them #9099
|
|
const process = this.debugService.getViewModel().focusedProcess;
|
|
if ((process && !process.session.capabilities.supportsFunctionBreakpoints) || !this.debugService.getModel().areBreakpointsActivated()) {
|
|
tree.addTraits('disabled', [functionBreakpoint]);
|
|
if (process && !process.session.capabilities.supportsFunctionBreakpoints) {
|
|
data.breakpoint.title = nls.localize('functionBreakpointsNotSupported', "Function breakpoints are not supported by this debug type");
|
|
}
|
|
} else {
|
|
tree.removeTraits('disabled', [functionBreakpoint]);
|
|
}
|
|
}
|
|
}
|
|
|
|
private renderBreakpoint(tree: ITree, breakpoint: debug.IBreakpoint, data: IBreakpointTemplateData): void {
|
|
this.debugService.getModel().areBreakpointsActivated() ? tree.removeTraits('disabled', [breakpoint]) : tree.addTraits('disabled', [breakpoint]);
|
|
|
|
data.name.textContent = paths.basename(getPathLabel(breakpoint.uri, this.contextService));
|
|
data.lineNumber.textContent = breakpoint.lineNumber.toString();
|
|
if (breakpoint.column) {
|
|
data.lineNumber.textContent += `:${breakpoint.column}`;
|
|
}
|
|
data.filePath.textContent = getPathLabel(paths.dirname(breakpoint.uri.fsPath), this.contextService, this.environmentService);
|
|
data.checkbox.checked = breakpoint.enabled;
|
|
|
|
const debugActive = this.debugService.state === debug.State.Running || this.debugService.state === debug.State.Stopped || this.debugService.state === debug.State.Initializing;
|
|
if (debugActive && !breakpoint.verified) {
|
|
tree.addTraits('disabled', [breakpoint]);
|
|
if (breakpoint.message) {
|
|
data.breakpoint.title = breakpoint.message;
|
|
}
|
|
} else if (breakpoint.condition || breakpoint.hitCondition) {
|
|
data.breakpoint.title = breakpoint.condition ? breakpoint.condition : breakpoint.hitCondition;
|
|
}
|
|
}
|
|
|
|
public disposeTemplate(tree: ITree, templateId: string, templateData: any): void {
|
|
lifecycle.dispose(templateData.toDispose);
|
|
}
|
|
}
|
|
|
|
export class BreakpointsAccessibilityProvider implements IAccessibilityProvider {
|
|
|
|
constructor( @IWorkspaceContextService private contextService: IWorkspaceContextService) {
|
|
// noop
|
|
}
|
|
|
|
public getAriaLabel(tree: ITree, element: any): string {
|
|
if (element instanceof Breakpoint) {
|
|
return nls.localize('breakpointAriaLabel', "Breakpoint line {0} {1}, breakpoints, debug", (<Breakpoint>element).lineNumber, getPathLabel(paths.basename((<Breakpoint>element).uri.fsPath), this.contextService), this.contextService);
|
|
}
|
|
if (element instanceof FunctionBreakpoint) {
|
|
return nls.localize('functionBreakpointAriaLabel', "Function breakpoint {0}, breakpoints, debug", (<FunctionBreakpoint>element).name);
|
|
}
|
|
if (element instanceof ExceptionBreakpoint) {
|
|
return nls.localize('exceptionBreakpointAriaLabel', "Exception breakpoint {0}, breakpoints, debug", (<ExceptionBreakpoint>element).filter);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export class BreakpointsController extends BaseDebugController {
|
|
|
|
protected onLeftClick(tree: ITree, element: any, event: IMouseEvent): boolean {
|
|
if (element instanceof FunctionBreakpoint && event.detail === 2) {
|
|
this.debugService.getViewModel().setSelectedFunctionBreakpoint(element);
|
|
return true;
|
|
}
|
|
if (element instanceof Breakpoint) {
|
|
super.onLeftClick(tree, element, event);
|
|
this.openBreakpointSource(element, event, event.detail !== 2);
|
|
return true;
|
|
}
|
|
|
|
return super.onLeftClick(tree, element, event);
|
|
}
|
|
|
|
public openBreakpointSource(breakpoint: Breakpoint, event: IKeyboardEvent | IMouseEvent, preserveFocus: boolean): void {
|
|
const sideBySide = (event && (event.ctrlKey || event.metaKey));
|
|
const selection = breakpoint.endLineNumber ? {
|
|
startLineNumber: breakpoint.lineNumber,
|
|
endLineNumber: breakpoint.endLineNumber,
|
|
startColumn: breakpoint.column,
|
|
endColumn: breakpoint.endColumn
|
|
} : {
|
|
startLineNumber: breakpoint.lineNumber,
|
|
startColumn: breakpoint.column || 1,
|
|
endLineNumber: breakpoint.lineNumber,
|
|
endColumn: breakpoint.column || Constants.MAX_SAFE_SMALL_INTEGER
|
|
};
|
|
|
|
this.editorService.openEditor({
|
|
resource: breakpoint.uri,
|
|
options: {
|
|
preserveFocus,
|
|
selection,
|
|
revealIfVisible: true,
|
|
revealInCenterIfOutsideViewport: true,
|
|
pinned: !preserveFocus
|
|
}
|
|
}, sideBySide).done(undefined, errors.onUnexpectedError);
|
|
}
|
|
}
|