mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-03-26 23:00:29 -04:00
Initial VS Code 1.19 source merge (#571)
* Initial 1.19 xcopy * Fix yarn build * Fix numerous build breaks * Next batch of build break fixes * More build break fixes * Runtime breaks * Additional post merge fixes * Fix windows setup file * Fix test failures. * Update license header blocks to refer to source eula
This commit is contained in:
248
src/vs/workbench/parts/debug/electron-browser/baseDebugView.ts
Normal file
248
src/vs/workbench/parts/debug/electron-browser/baseDebugView.ts
Normal file
@@ -0,0 +1,248 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { IExpression, IDebugService, IEnablement } from 'vs/workbench/parts/debug/common/debug';
|
||||
import { Expression, FunctionBreakpoint, Variable } from 'vs/workbench/parts/debug/common/debugModel';
|
||||
import { IContextViewService, IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { ITree, ContextMenuEvent, IActionProvider } from 'vs/base/parts/tree/browser/tree';
|
||||
import { InputBox, IInputValidationOptions } from 'vs/base/browser/ui/inputbox/inputBox';
|
||||
import { attachInputBoxStyler } from 'vs/platform/theme/common/styler';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { once } from 'vs/base/common/functional';
|
||||
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IMenuService, MenuId, IMenu } from 'vs/platform/actions/common/actions';
|
||||
import { ClickBehavior, DefaultController } from 'vs/base/parts/tree/browser/treeDefaults';
|
||||
import { fillInActions } from 'vs/platform/actions/browser/menuItemActionItem';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
|
||||
export const MAX_VALUE_RENDER_LENGTH_IN_VIEWLET = 1024;
|
||||
export const twistiePixels = 20;
|
||||
const booleanRegex = /^true|false$/i;
|
||||
const stringRegex = /^(['"]).*\1$/;
|
||||
const $ = dom.$;
|
||||
|
||||
export interface IRenderValueOptions {
|
||||
preserveWhitespace?: boolean;
|
||||
showChanged?: boolean;
|
||||
maxValueLength?: number;
|
||||
showHover?: boolean;
|
||||
colorize?: boolean;
|
||||
}
|
||||
|
||||
export interface IVariableTemplateData {
|
||||
expression: HTMLElement;
|
||||
name: HTMLElement;
|
||||
value: HTMLElement;
|
||||
}
|
||||
|
||||
export function renderViewTree(container: HTMLElement): HTMLElement {
|
||||
const treeContainer = document.createElement('div');
|
||||
dom.addClass(treeContainer, 'debug-view-content');
|
||||
container.appendChild(treeContainer);
|
||||
return treeContainer;
|
||||
}
|
||||
|
||||
function replaceWhitespace(value: string): string {
|
||||
const map: { [x: string]: string } = { '\n': '\\n', '\r': '\\r', '\t': '\\t' };
|
||||
return value.replace(/[\n\r\t]/g, char => map[char]);
|
||||
}
|
||||
|
||||
export function renderExpressionValue(expressionOrValue: 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');
|
||||
}
|
||||
}
|
||||
|
||||
if (options.colorize && typeof expressionOrValue !== 'string') {
|
||||
if (expressionOrValue.type === 'number' || expressionOrValue.type === 'boolean' || expressionOrValue.type === 'string') {
|
||||
dom.addClass(container, expressionOrValue.type);
|
||||
} 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;
|
||||
dom.toggleClass(data.name, 'virtual', !!variable.presentationHint && variable.presentationHint.kind === 'virtual');
|
||||
}
|
||||
|
||||
if (variable.value) {
|
||||
data.name.textContent += variable.name ? ':' : '';
|
||||
renderExpressionValue(variable, data.value, {
|
||||
showChanged,
|
||||
maxValueLength: MAX_VALUE_RENDER_LENGTH_IN_VIEWLET,
|
||||
preserveWhitespace: false,
|
||||
showHover: true,
|
||||
colorize: true
|
||||
});
|
||||
} else {
|
||||
data.value.textContent = '';
|
||||
data.value.title = '';
|
||||
}
|
||||
}
|
||||
|
||||
export interface IRenameBoxOptions {
|
||||
initialValue: string;
|
||||
ariaLabel: string;
|
||||
placeholder?: string;
|
||||
validationOptions?: IInputValidationOptions;
|
||||
}
|
||||
|
||||
export function renderRenameBox(debugService: 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: 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, 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, onUnexpectedError);
|
||||
} else if (element instanceof FunctionBreakpoint && !element.name) {
|
||||
debugService.removeFunctionBreakpoints(element.getId()).done(null, 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);
|
||||
debugService.evaluateWatchExpressions();
|
||||
}, onUnexpectedError);
|
||||
}
|
||||
}
|
||||
|
||||
tree.clearHighlight();
|
||||
tree.DOMFocus();
|
||||
tree.setFocus(element);
|
||||
|
||||
// need to remove the input box since this template will be reused.
|
||||
container.removeChild(inputBoxContainer);
|
||||
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);
|
||||
}));
|
||||
}
|
||||
|
||||
export class BaseDebugController extends DefaultController {
|
||||
|
||||
private contributedContextMenu: IMenu;
|
||||
|
||||
constructor(
|
||||
private actionProvider: IActionProvider,
|
||||
menuId: MenuId,
|
||||
@IDebugService protected debugService: 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: IEnablement, event: ContextMenuEvent, focusElement = true): boolean {
|
||||
if (event.target && event.target.tagName && event.target.tagName.toLowerCase() === 'input') {
|
||||
return false;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
if (focusElement) {
|
||||
tree.setFocus(element);
|
||||
}
|
||||
|
||||
if (this.actionProvider.hasSecondaryActions(tree, element)) {
|
||||
const anchor = { x: event.posx, 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;
|
||||
}
|
||||
}
|
||||
518
src/vs/workbench/parts/debug/electron-browser/breakpointsView.ts
Normal file
518
src/vs/workbench/parts/debug/electron-browser/breakpointsView.ts
Normal file
@@ -0,0 +1,518 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 * as resources from 'vs/base/common/resources';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { IAction, Action } from 'vs/base/common/actions';
|
||||
import { IDebugService, IBreakpoint, CONTEXT_BREAKPOINTS_FOCUSED, EDITOR_CONTRIBUTION_ID, State, DEBUG_SCHEME, IFunctionBreakpoint, IExceptionBreakpoint, IEnablement, IDebugEditorContribution } from 'vs/workbench/parts/debug/common/debug';
|
||||
import { ExceptionBreakpoint, FunctionBreakpoint, Breakpoint } from 'vs/workbench/parts/debug/common/debugModel';
|
||||
import { AddFunctionBreakpointAction, ToggleBreakpointsActivatedAction, RemoveAllBreakpointsAction, RemoveBreakpointAction, EnableAllBreakpointsAction, DisableAllBreakpointsAction, ReapplyBreakpointsAction } from 'vs/workbench/parts/debug/browser/debugActions';
|
||||
import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { Constants } from 'vs/editor/common/core/uint';
|
||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
import { getPathLabel } from 'vs/base/common/labels';
|
||||
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { basename } from 'vs/base/common/paths';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { Separator } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { IDelegate, IListContextMenuEvent, IRenderer } from 'vs/base/browser/ui/list/list';
|
||||
import { IEditorService, IEditor } from 'vs/platform/editor/common/editor';
|
||||
import { InputBox } from 'vs/base/browser/ui/inputbox/inputBox';
|
||||
import { IKeyboardEvent, StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { WorkbenchList, IListService } from 'vs/platform/list/browser/listService';
|
||||
import { ViewsViewletPanel, IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { attachInputBoxStyler } from 'vs/platform/theme/common/styler';
|
||||
import { isCodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
|
||||
const $ = dom.$;
|
||||
|
||||
export class BreakpointsView extends ViewsViewletPanel {
|
||||
|
||||
private static readonly MAX_VISIBLE_FILES = 9;
|
||||
private static readonly MEMENTO = 'breakopintsview.memento';
|
||||
private settings: any;
|
||||
private list: WorkbenchList<IEnablement>;
|
||||
private needsRefresh: boolean;
|
||||
|
||||
constructor(
|
||||
options: IViewletViewOptions,
|
||||
@IContextMenuService contextMenuService: IContextMenuService,
|
||||
@IDebugService private debugService: IDebugService,
|
||||
@IKeybindingService keybindingService: IKeybindingService,
|
||||
@IInstantiationService private instantiationService: IInstantiationService,
|
||||
@IListService private listService: IListService,
|
||||
@IThemeService private themeService: IThemeService,
|
||||
@IEditorService private editorService: IEditorService,
|
||||
@IContextViewService private contextViewService: IContextViewService,
|
||||
@IContextKeyService private contextKeyService: IContextKeyService
|
||||
) {
|
||||
super(options, keybindingService, contextMenuService);
|
||||
|
||||
this.minimumBodySize = this.maximumBodySize = this.getExpandedBodySize();
|
||||
this.settings = options.viewletSettings;
|
||||
this.disposables.push(this.debugService.getModel().onDidChangeBreakpoints(() => this.onBreakpointsChange()));
|
||||
}
|
||||
|
||||
public renderBody(container: HTMLElement): void {
|
||||
dom.addClass(container, 'debug-breakpoints');
|
||||
const delegate = new BreakpointsDelegate(this.debugService);
|
||||
|
||||
this.list = new WorkbenchList<IEnablement>(container, delegate, [
|
||||
this.instantiationService.createInstance(BreakpointsRenderer),
|
||||
new ExceptionBreakpointsRenderer(this.debugService),
|
||||
new FunctionBreakpointsRenderer(this.debugService),
|
||||
new FunctionBreakpointInputRenderer(this.debugService, this.contextViewService, this.themeService)
|
||||
], {
|
||||
identityProvider: element => element.getId(),
|
||||
multipleSelectionSupport: false
|
||||
}, this.contextKeyService, this.listService, this.themeService);
|
||||
|
||||
CONTEXT_BREAKPOINTS_FOCUSED.bindTo(this.list.contextKeyService);
|
||||
|
||||
this.list.onContextMenu(this.onListContextMenu, this, this.disposables);
|
||||
|
||||
const handleBreakpointFocus = (preserveFocuse: boolean, sideBySide: boolean, selectFunctionBreakpoint: boolean) => {
|
||||
const focused = this.list.getFocusedElements();
|
||||
const element = focused.length ? focused[0] : undefined;
|
||||
if (element instanceof Breakpoint) {
|
||||
openBreakpointSource(element, sideBySide, preserveFocuse, this.debugService, this.editorService).done(undefined, onUnexpectedError);
|
||||
}
|
||||
if (selectFunctionBreakpoint && element instanceof FunctionBreakpoint && element !== this.debugService.getViewModel().getSelectedFunctionBreakpoint()) {
|
||||
this.debugService.getViewModel().setSelectedFunctionBreakpoint(element);
|
||||
this.onBreakpointsChange();
|
||||
}
|
||||
};
|
||||
this.disposables.push(this.list.onKeyUp(e => {
|
||||
const event = new StandardKeyboardEvent(e);
|
||||
if (event.equals(KeyCode.Enter)) {
|
||||
handleBreakpointFocus(false, event && (event.ctrlKey || event.metaKey), false);
|
||||
}
|
||||
}));
|
||||
this.disposables.push(this.list.onMouseDblClick(e => {
|
||||
handleBreakpointFocus(false, false, true);
|
||||
}));
|
||||
this.disposables.push(this.list.onMouseClick(e => {
|
||||
handleBreakpointFocus(true, false, false);
|
||||
}));
|
||||
|
||||
this.list.splice(0, this.list.length, this.elements);
|
||||
}
|
||||
|
||||
protected layoutBody(size: number): void {
|
||||
if (this.list) {
|
||||
this.list.layout(size);
|
||||
}
|
||||
}
|
||||
|
||||
private onListContextMenu(e: IListContextMenuEvent<IEnablement>): void {
|
||||
const actions: IAction[] = [];
|
||||
const element = e.element;
|
||||
|
||||
if (element instanceof Breakpoint) {
|
||||
actions.push(new Action('workbench.action.debug.openEditorAndEditBreakpoint', nls.localize('editConditionalBreakpoint', "Edit Breakpoint..."), undefined, true, () => {
|
||||
return openBreakpointSource(element, false, false, this.debugService, this.editorService).then(editor => {
|
||||
const codeEditor = editor.getControl();
|
||||
if (isCodeEditor(codeEditor)) {
|
||||
codeEditor.getContribution<IDebugEditorContribution>(EDITOR_CONTRIBUTION_ID).showBreakpointWidget(element.lineNumber, element.column);
|
||||
}
|
||||
});
|
||||
}));
|
||||
actions.push(new Separator());
|
||||
}
|
||||
|
||||
actions.push(new RemoveBreakpointAction(RemoveBreakpointAction.ID, RemoveBreakpointAction.LABEL, this.debugService, this.keybindingService));
|
||||
|
||||
|
||||
if (this.debugService.getModel().getBreakpoints().length + this.debugService.getModel().getFunctionBreakpoints().length > 1) {
|
||||
actions.push(new RemoveAllBreakpointsAction(RemoveAllBreakpointsAction.ID, RemoveAllBreakpointsAction.LABEL, this.debugService, this.keybindingService));
|
||||
actions.push(new Separator());
|
||||
|
||||
actions.push(new EnableAllBreakpointsAction(EnableAllBreakpointsAction.ID, EnableAllBreakpointsAction.LABEL, this.debugService, this.keybindingService));
|
||||
actions.push(new DisableAllBreakpointsAction(DisableAllBreakpointsAction.ID, DisableAllBreakpointsAction.LABEL, this.debugService, this.keybindingService));
|
||||
}
|
||||
|
||||
actions.push(new Separator());
|
||||
actions.push(new ReapplyBreakpointsAction(ReapplyBreakpointsAction.ID, ReapplyBreakpointsAction.LABEL, this.debugService, this.keybindingService));
|
||||
|
||||
this.contextMenuService.showContextMenu({
|
||||
getAnchor: () => e.anchor,
|
||||
getActions: () => TPromise.as(actions),
|
||||
getActionsContext: () => element
|
||||
});
|
||||
}
|
||||
|
||||
public getActions(): IAction[] {
|
||||
return [
|
||||
new AddFunctionBreakpointAction(AddFunctionBreakpointAction.ID, AddFunctionBreakpointAction.LABEL, this.debugService, this.keybindingService),
|
||||
new ToggleBreakpointsActivatedAction(ToggleBreakpointsActivatedAction.ID, ToggleBreakpointsActivatedAction.ACTIVATE_LABEL, this.debugService, this.keybindingService),
|
||||
new RemoveAllBreakpointsAction(RemoveAllBreakpointsAction.ID, RemoveAllBreakpointsAction.LABEL, this.debugService, this.keybindingService)
|
||||
];
|
||||
}
|
||||
|
||||
public setExpanded(expanded: boolean): void {
|
||||
super.setExpanded(expanded);
|
||||
if (expanded && this.needsRefresh) {
|
||||
this.onBreakpointsChange();
|
||||
}
|
||||
}
|
||||
|
||||
public setVisible(visible: boolean): TPromise<void> {
|
||||
return super.setVisible(visible).then(() => {
|
||||
if (visible && this.needsRefresh) {
|
||||
this.onBreakpointsChange();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private onBreakpointsChange(): void {
|
||||
if (this.isExpanded() && this.isVisible()) {
|
||||
this.minimumBodySize = this.getExpandedBodySize();
|
||||
if (this.maximumBodySize < Number.POSITIVE_INFINITY) {
|
||||
this.maximumBodySize = this.minimumBodySize;
|
||||
}
|
||||
if (this.list) {
|
||||
this.list.splice(0, this.list.length, this.elements);
|
||||
this.needsRefresh = false;
|
||||
}
|
||||
} else {
|
||||
this.needsRefresh = true;
|
||||
}
|
||||
}
|
||||
|
||||
private get elements(): IEnablement[] {
|
||||
const model = this.debugService.getModel();
|
||||
const elements = (<IEnablement[]>model.getExceptionBreakpoints()).concat(model.getFunctionBreakpoints()).concat(model.getBreakpoints());
|
||||
|
||||
return elements;
|
||||
}
|
||||
|
||||
private getExpandedBodySize(): number {
|
||||
const model = this.debugService.getModel();
|
||||
const length = model.getBreakpoints().length + model.getExceptionBreakpoints().length + model.getFunctionBreakpoints().length;
|
||||
return Math.min(BreakpointsView.MAX_VISIBLE_FILES, length) * 22;
|
||||
}
|
||||
|
||||
public shutdown(): void {
|
||||
this.settings[BreakpointsView.MEMENTO] = !this.isExpanded();
|
||||
}
|
||||
}
|
||||
|
||||
class BreakpointsDelegate implements IDelegate<IEnablement> {
|
||||
|
||||
constructor(private debugService: IDebugService) {
|
||||
// noop
|
||||
}
|
||||
|
||||
getHeight(element: IEnablement): number {
|
||||
return 22;
|
||||
}
|
||||
|
||||
getTemplateId(element: IEnablement): string {
|
||||
if (element instanceof Breakpoint) {
|
||||
return BreakpointsRenderer.ID;
|
||||
}
|
||||
if (element instanceof FunctionBreakpoint) {
|
||||
const selected = this.debugService.getViewModel().getSelectedFunctionBreakpoint();
|
||||
if (!element.name || (selected && selected.getId() === element.getId())) {
|
||||
return FunctionBreakpointInputRenderer.ID;
|
||||
}
|
||||
|
||||
return FunctionBreakpointsRenderer.ID;
|
||||
}
|
||||
if (element instanceof ExceptionBreakpoint) {
|
||||
return ExceptionBreakpointsRenderer.ID;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
interface IBaseBreakpointTemplateData {
|
||||
breakpoint: HTMLElement;
|
||||
name: HTMLElement;
|
||||
checkbox: HTMLInputElement;
|
||||
context: IEnablement;
|
||||
toDispose: IDisposable[];
|
||||
}
|
||||
|
||||
interface IBreakpointTemplateData extends IBaseBreakpointTemplateData {
|
||||
lineNumber: HTMLElement;
|
||||
filePath: HTMLElement;
|
||||
}
|
||||
|
||||
interface IInputTemplateData {
|
||||
inputBox: InputBox;
|
||||
breakpoint: IFunctionBreakpoint;
|
||||
reactedOnEvent: boolean;
|
||||
toDispose: IDisposable[];
|
||||
}
|
||||
|
||||
class BreakpointsRenderer implements IRenderer<IBreakpoint, IBreakpointTemplateData> {
|
||||
|
||||
constructor(
|
||||
@IDebugService private debugService: IDebugService,
|
||||
@IWorkspaceContextService private contextService: IWorkspaceContextService,
|
||||
@IEnvironmentService private environmentService: IEnvironmentService
|
||||
) {
|
||||
// noop
|
||||
}
|
||||
|
||||
static ID = 'breakpoints';
|
||||
|
||||
get templateId() {
|
||||
return BreakpointsRenderer.ID;
|
||||
}
|
||||
|
||||
renderTemplate(container: HTMLElement): IBreakpointTemplateData {
|
||||
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'));
|
||||
|
||||
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'));
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
renderElement(breakpoint: IBreakpoint, index: number, data: IBreakpointTemplateData): void {
|
||||
data.context = breakpoint;
|
||||
dom.toggleClass(data.breakpoint, 'disabled', !this.debugService.getModel().areBreakpointsActivated());
|
||||
|
||||
data.name.textContent = basename(getPathLabel(breakpoint.uri, this.contextService));
|
||||
data.lineNumber.textContent = breakpoint.lineNumber.toString();
|
||||
if (breakpoint.column) {
|
||||
data.lineNumber.textContent += `:${breakpoint.column}`;
|
||||
}
|
||||
data.filePath.textContent = getPathLabel(resources.dirname(breakpoint.uri), this.contextService, this.environmentService);
|
||||
data.checkbox.checked = breakpoint.enabled;
|
||||
|
||||
const debugActive = this.debugService.state === State.Running || this.debugService.state === State.Stopped;
|
||||
if (debugActive && !breakpoint.verified) {
|
||||
dom.addClass(data.breakpoint, 'disabled');
|
||||
if (breakpoint.message) {
|
||||
data.breakpoint.title = breakpoint.message;
|
||||
}
|
||||
} else if (breakpoint.condition || breakpoint.hitCondition) {
|
||||
data.breakpoint.title = breakpoint.condition ? breakpoint.condition : breakpoint.hitCondition;
|
||||
}
|
||||
}
|
||||
|
||||
disposeTemplate(templateData: IBreakpointTemplateData): void {
|
||||
dispose(templateData.toDispose);
|
||||
}
|
||||
}
|
||||
|
||||
class ExceptionBreakpointsRenderer implements IRenderer<IExceptionBreakpoint, IBaseBreakpointTemplateData> {
|
||||
|
||||
constructor(
|
||||
private debugService: IDebugService
|
||||
) {
|
||||
// noop
|
||||
}
|
||||
|
||||
static ID = 'exceptionbreakpoints';
|
||||
|
||||
get templateId() {
|
||||
return ExceptionBreakpointsRenderer.ID;
|
||||
}
|
||||
|
||||
renderTemplate(container: HTMLElement): IBaseBreakpointTemplateData {
|
||||
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'));
|
||||
dom.addClass(data.breakpoint, 'exception');
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
renderElement(exceptionBreakpoint: IExceptionBreakpoint, index: number, data: IBaseBreakpointTemplateData): void {
|
||||
data.context = exceptionBreakpoint;
|
||||
data.name.textContent = exceptionBreakpoint.label || `${exceptionBreakpoint.filter} exceptions`;
|
||||
data.breakpoint.title = data.name.textContent;
|
||||
data.checkbox.checked = exceptionBreakpoint.enabled;
|
||||
}
|
||||
|
||||
disposeTemplate(templateData: IBaseBreakpointTemplateData): void {
|
||||
dispose(templateData.toDispose);
|
||||
}
|
||||
}
|
||||
|
||||
class FunctionBreakpointsRenderer implements IRenderer<IFunctionBreakpoint, IBaseBreakpointTemplateData> {
|
||||
|
||||
constructor(
|
||||
private debugService: IDebugService
|
||||
) {
|
||||
// noop
|
||||
}
|
||||
|
||||
static ID = 'functionbreakpoints';
|
||||
|
||||
get templateId() {
|
||||
return FunctionBreakpointsRenderer.ID;
|
||||
}
|
||||
|
||||
renderTemplate(container: HTMLElement): IBaseBreakpointTemplateData {
|
||||
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'));
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
renderElement(functionBreakpoint: IFunctionBreakpoint, index: number, data: IBaseBreakpointTemplateData): void {
|
||||
data.context = functionBreakpoint;
|
||||
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;
|
||||
dom.toggleClass(data.breakpoint, 'disalbed', (process && !process.session.capabilities.supportsFunctionBreakpoints) || !this.debugService.getModel().areBreakpointsActivated());
|
||||
if (process && !process.session.capabilities.supportsFunctionBreakpoints) {
|
||||
data.breakpoint.title = nls.localize('functionBreakpointsNotSupported', "Function breakpoints are not supported by this debug type");
|
||||
}
|
||||
}
|
||||
|
||||
disposeTemplate(templateData: IBaseBreakpointTemplateData): void {
|
||||
dispose(templateData.toDispose);
|
||||
}
|
||||
}
|
||||
|
||||
class FunctionBreakpointInputRenderer implements IRenderer<IFunctionBreakpoint, IInputTemplateData> {
|
||||
|
||||
constructor(
|
||||
private debugService: IDebugService,
|
||||
private contextViewService: IContextViewService,
|
||||
private themeService: IThemeService
|
||||
) {
|
||||
// noop
|
||||
}
|
||||
|
||||
static ID = 'functionbreakpointinput';
|
||||
|
||||
get templateId() {
|
||||
return FunctionBreakpointInputRenderer.ID;
|
||||
}
|
||||
|
||||
renderTemplate(container: HTMLElement): IInputTemplateData {
|
||||
const template: IInputTemplateData = Object.create(null);
|
||||
const inputBoxContainer = dom.append(container, $('.inputBoxContainer'));
|
||||
const inputBox = new InputBox(inputBoxContainer, this.contextViewService, {
|
||||
placeholder: nls.localize('functionBreakpointPlaceholder', "Function to break on"),
|
||||
ariaLabel: nls.localize('functionBreakPointInputAriaLabel', "Type function breakpoint")
|
||||
});
|
||||
const styler = attachInputBoxStyler(inputBox, this.themeService);
|
||||
const toDispose: IDisposable[] = [inputBox, styler];
|
||||
|
||||
const wrapUp = (renamed: boolean) => {
|
||||
if (!template.reactedOnEvent) {
|
||||
template.reactedOnEvent = true;
|
||||
this.debugService.getViewModel().setSelectedFunctionBreakpoint(undefined);
|
||||
if (inputBox.value && (renamed || template.breakpoint.name)) {
|
||||
this.debugService.renameFunctionBreakpoint(template.breakpoint.getId(), renamed ? inputBox.value : template.breakpoint.name).done(null, onUnexpectedError);
|
||||
} else {
|
||||
this.debugService.removeFunctionBreakpoints(template.breakpoint.getId()).done(null, onUnexpectedError);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
}));
|
||||
|
||||
template.inputBox = inputBox;
|
||||
template.toDispose = toDispose;
|
||||
return template;
|
||||
}
|
||||
|
||||
renderElement(functionBreakpoint: IFunctionBreakpoint, index: number, data: IInputTemplateData): void {
|
||||
data.breakpoint = functionBreakpoint;
|
||||
data.reactedOnEvent = false;
|
||||
data.inputBox.value = functionBreakpoint.name || '';
|
||||
data.inputBox.focus();
|
||||
data.inputBox.select();
|
||||
}
|
||||
|
||||
disposeTemplate(templateData: IInputTemplateData): void {
|
||||
dispose(templateData.toDispose);
|
||||
}
|
||||
}
|
||||
|
||||
function openBreakpointSource(breakpoint: Breakpoint, sideBySide: boolean, preserveFocus: boolean, debugService: IDebugService, editorService: IEditorService): TPromise<IEditor> {
|
||||
if (breakpoint.uri.scheme === DEBUG_SCHEME && debugService.state === State.Inactive) {
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
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
|
||||
};
|
||||
|
||||
return editorService.openEditor({
|
||||
resource: breakpoint.uri,
|
||||
options: {
|
||||
preserveFocus,
|
||||
selection,
|
||||
revealIfVisible: true,
|
||||
revealInCenterIfOutsideViewport: true,
|
||||
pinned: !preserveFocus
|
||||
}
|
||||
}, sideBySide);
|
||||
}
|
||||
543
src/vs/workbench/parts/debug/electron-browser/callStackView.ts
Normal file
543
src/vs/workbench/parts/debug/electron-browser/callStackView.ts
Normal file
@@ -0,0 +1,543 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { RunOnceScheduler } from 'vs/base/common/async';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import * as errors from 'vs/base/common/errors';
|
||||
import { TreeViewsViewletPanel, IViewletViewOptions, IViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet';
|
||||
import { IDebugService, State, IStackFrame, IProcess, IThread } from 'vs/workbench/parts/debug/common/debug';
|
||||
import { Thread, StackFrame, ThreadAndProcessIds, Process, Model } from 'vs/workbench/parts/debug/common/debugModel';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { MenuId } from 'vs/platform/actions/common/actions';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { BaseDebugController, twistiePixels, renderViewTree } from 'vs/workbench/parts/debug/electron-browser/baseDebugView';
|
||||
import { ITree, IActionProvider, IDataSource, IRenderer, IAccessibilityProvider } from 'vs/base/parts/tree/browser/tree';
|
||||
import { IMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||
import { IAction, IActionItem } from 'vs/base/common/actions';
|
||||
import { RestartAction, StopAction, ContinueAction, StepOverAction, StepIntoAction, StepOutAction, PauseAction, RestartFrameAction } from 'vs/workbench/parts/debug/browser/debugActions';
|
||||
import { CopyStackTraceAction } from 'vs/workbench/parts/debug/electron-browser/electronDebugActions';
|
||||
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { Source } from 'vs/workbench/parts/debug/common/debugSource';
|
||||
import { basenameOrAuthority } from 'vs/base/common/resources';
|
||||
import { WorkbenchTree, IListService } from 'vs/platform/list/browser/listService';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
|
||||
const $ = dom.$;
|
||||
|
||||
export class CallStackView extends TreeViewsViewletPanel {
|
||||
|
||||
private static readonly MEMENTO = 'callstackview.memento';
|
||||
private pauseMessage: HTMLSpanElement;
|
||||
private pauseMessageLabel: HTMLSpanElement;
|
||||
private onCallStackChangeScheduler: RunOnceScheduler;
|
||||
private settings: any;
|
||||
private needsRefresh: boolean;
|
||||
|
||||
constructor(
|
||||
private options: IViewletViewOptions,
|
||||
@IContextMenuService contextMenuService: IContextMenuService,
|
||||
@IContextKeyService private contextKeyService: IContextKeyService,
|
||||
@IDebugService private debugService: IDebugService,
|
||||
@IKeybindingService keybindingService: IKeybindingService,
|
||||
@IInstantiationService private instantiationService: IInstantiationService,
|
||||
@IThemeService private themeService: IThemeService,
|
||||
@IListService private listService: IListService
|
||||
) {
|
||||
super({ ...(options as IViewOptions), ariaHeaderLabel: nls.localize('callstackSection', "Call Stack Section") }, keybindingService, contextMenuService);
|
||||
this.settings = options.viewletSettings;
|
||||
|
||||
// Create scheduler to prevent unnecessary flashing of tree when reacting to changes
|
||||
this.onCallStackChangeScheduler = new RunOnceScheduler(() => {
|
||||
let newTreeInput: any = this.debugService.getModel();
|
||||
const processes = this.debugService.getModel().getProcesses();
|
||||
if (!this.debugService.getViewModel().isMultiProcessView() && processes.length) {
|
||||
const threads = processes[0].getAllThreads();
|
||||
// Only show the threads in the call stack if there is more than 1 thread.
|
||||
newTreeInput = threads.length === 1 ? threads[0] : processes[0];
|
||||
}
|
||||
|
||||
// Only show the global pause message if we do not display threads.
|
||||
// Otherwise there will be a pause message per thread and there is no need for a global one.
|
||||
if (newTreeInput instanceof Thread && newTreeInput.stoppedDetails) {
|
||||
this.pauseMessageLabel.textContent = newTreeInput.stoppedDetails.description || nls.localize('debugStopped', "Paused on {0}", newTreeInput.stoppedDetails.reason);
|
||||
if (newTreeInput.stoppedDetails.text) {
|
||||
this.pauseMessageLabel.title = newTreeInput.stoppedDetails.text;
|
||||
}
|
||||
dom.toggleClass(this.pauseMessageLabel, 'exception', newTreeInput.stoppedDetails.reason === 'exception');
|
||||
this.pauseMessage.hidden = false;
|
||||
} else {
|
||||
this.pauseMessage.hidden = true;
|
||||
}
|
||||
|
||||
this.needsRefresh = false;
|
||||
(this.tree.getInput() === newTreeInput ? this.tree.refresh() : this.tree.setInput(newTreeInput))
|
||||
.done(() => this.updateTreeSelection(), errors.onUnexpectedError);
|
||||
}, 50);
|
||||
}
|
||||
|
||||
protected renderHeaderTitle(container: HTMLElement): void {
|
||||
const title = dom.append(container, $('.title.debug-call-stack-title'));
|
||||
const name = dom.append(title, $('span'));
|
||||
name.textContent = this.options.name;
|
||||
this.pauseMessage = dom.append(title, $('span.pause-message'));
|
||||
this.pauseMessage.hidden = true;
|
||||
this.pauseMessageLabel = dom.append(this.pauseMessage, $('span.label'));
|
||||
}
|
||||
|
||||
public renderBody(container: HTMLElement): void {
|
||||
dom.addClass(container, 'debug-call-stack');
|
||||
this.treeContainer = renderViewTree(container);
|
||||
const actionProvider = new CallStackActionProvider(this.debugService, this.keybindingService);
|
||||
const controller = this.instantiationService.createInstance(CallStackController, actionProvider, MenuId.DebugCallStackContext);
|
||||
|
||||
this.tree = new WorkbenchTree(this.treeContainer, {
|
||||
dataSource: new CallStackDataSource(),
|
||||
renderer: this.instantiationService.createInstance(CallStackRenderer),
|
||||
accessibilityProvider: this.instantiationService.createInstance(CallstackAccessibilityProvider),
|
||||
controller
|
||||
}, {
|
||||
ariaLabel: nls.localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'callStackAriaLabel' }, "Debug Call Stack"),
|
||||
twistiePixels,
|
||||
keyboardSupport: false
|
||||
}, this.contextKeyService, this.listService, this.themeService);
|
||||
|
||||
this.disposables.push(this.tree.onDidChangeSelection(event => {
|
||||
if (event && event.payload && event.payload.origin === 'keyboard') {
|
||||
const element = this.tree.getFocus();
|
||||
if (element instanceof ThreadAndProcessIds) {
|
||||
controller.showMoreStackFrames(this.tree, element);
|
||||
} else if (element instanceof StackFrame) {
|
||||
controller.focusStackFrame(element, event, false);
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
this.disposables.push(this.debugService.getModel().onDidChangeCallStack(() => {
|
||||
if (!this.isVisible()) {
|
||||
this.needsRefresh = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.onCallStackChangeScheduler.isScheduled()) {
|
||||
this.onCallStackChangeScheduler.schedule();
|
||||
}
|
||||
}));
|
||||
this.disposables.push(this.debugService.getViewModel().onDidFocusStackFrame(() =>
|
||||
this.updateTreeSelection().done(undefined, errors.onUnexpectedError)));
|
||||
|
||||
// Schedule the update of the call stack tree if the viewlet is opened after a session started #14684
|
||||
if (this.debugService.state === State.Stopped) {
|
||||
this.onCallStackChangeScheduler.schedule();
|
||||
}
|
||||
}
|
||||
|
||||
private updateTreeSelection(): TPromise<void> {
|
||||
if (!this.tree.getInput()) {
|
||||
// Tree not initialized yet
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
const stackFrame = this.debugService.getViewModel().focusedStackFrame;
|
||||
const thread = this.debugService.getViewModel().focusedThread;
|
||||
const process = this.debugService.getViewModel().focusedProcess;
|
||||
if (!thread) {
|
||||
if (!process) {
|
||||
this.tree.clearSelection();
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
this.tree.setSelection([process]);
|
||||
return this.tree.reveal(process);
|
||||
}
|
||||
|
||||
return this.tree.expandAll([thread.process, thread]).then(() => {
|
||||
if (!stackFrame) {
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
this.tree.setSelection([stackFrame]);
|
||||
return this.tree.reveal(stackFrame);
|
||||
});
|
||||
}
|
||||
|
||||
public setVisible(visible: boolean): TPromise<void> {
|
||||
return super.setVisible(visible).then(() => {
|
||||
if (visible && this.needsRefresh) {
|
||||
this.onCallStackChangeScheduler.schedule();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public shutdown(): void {
|
||||
this.settings[CallStackView.MEMENTO] = !this.isExpanded();
|
||||
super.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
if (element instanceof Thread) {
|
||||
return element.threadId;
|
||||
}
|
||||
}
|
||||
|
||||
// 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: IStackFrame, event: any, 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class CallStackActionProvider implements IActionProvider {
|
||||
|
||||
constructor(private debugService: IDebugService, private keybindingService: IKeybindingService) {
|
||||
// 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(new RestartAction(RestartAction.ID, RestartAction.LABEL, this.debugService, this.keybindingService));
|
||||
actions.push(new StopAction(StopAction.ID, StopAction.LABEL, this.debugService, this.keybindingService));
|
||||
} else if (element instanceof Thread) {
|
||||
const thread = <Thread>element;
|
||||
if (thread.stopped) {
|
||||
actions.push(new ContinueAction(ContinueAction.ID, ContinueAction.LABEL, this.debugService, this.keybindingService));
|
||||
actions.push(new StepOverAction(StepOverAction.ID, StepOverAction.LABEL, this.debugService, this.keybindingService));
|
||||
actions.push(new StepIntoAction(StepIntoAction.ID, StepIntoAction.LABEL, this.debugService, this.keybindingService));
|
||||
actions.push(new StepOutAction(StepOutAction.ID, StepOutAction.LABEL, this.debugService, this.keybindingService));
|
||||
} else {
|
||||
actions.push(new PauseAction(PauseAction.ID, PauseAction.LABEL, this.debugService, this.keybindingService));
|
||||
}
|
||||
} else if (element instanceof StackFrame) {
|
||||
if (element.thread.process.session.capabilities.supportsRestartFrame) {
|
||||
actions.push(new RestartFrameAction(RestartFrameAction.ID, RestartFrameAction.LABEL, this.debugService, this.keybindingService));
|
||||
}
|
||||
actions.push(new CopyStackTraceAction(CopyStackTraceAction.ID, CopyStackTraceAction.LABEL));
|
||||
}
|
||||
|
||||
return TPromise.as(actions);
|
||||
}
|
||||
|
||||
public getActionItem(tree: ITree, element: any, action: IAction): IActionItem {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
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 = <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;
|
||||
}
|
||||
|
||||
class CallStackRenderer implements IRenderer {
|
||||
|
||||
private static readonly THREAD_TEMPLATE_ID = 'thread';
|
||||
private static readonly STACK_FRAME_TEMPLATE_ID = 'stackFrame';
|
||||
private static readonly ERROR_TEMPLATE_ID = 'error';
|
||||
private static readonly LOAD_MORE_TEMPLATE_ID = 'loadMore';
|
||||
private static readonly 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'));
|
||||
const wrapper = dom.append(data.file, $('span.line-number-wrapper'));
|
||||
data.lineNumber = dom.append(wrapper, $('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: IProcess, data: IProcessTemplateData): void {
|
||||
data.process.title = nls.localize({ key: 'process', comment: ['Process is a noun'] }, "Process");
|
||||
data.name.textContent = process.getName(this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE);
|
||||
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: 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: 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
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
function getSourceName(source: Source, contextService: IWorkspaceContextService, environmentService?: IEnvironmentService): string {
|
||||
if (source.name) {
|
||||
return source.name;
|
||||
}
|
||||
|
||||
return basenameOrAuthority(source.uri);
|
||||
}
|
||||
@@ -16,7 +16,10 @@ import { IWorkbenchActionRegistry, Extensions as WorkbenchActionRegistryExtensio
|
||||
import { ToggleViewletAction, Extensions as ViewletExtensions, ViewletRegistry, ViewletDescriptor } from 'vs/workbench/browser/viewlet';
|
||||
import { TogglePanelAction, Extensions as PanelExtensions, PanelRegistry, PanelDescriptor } from 'vs/workbench/browser/panel';
|
||||
import { StatusbarItemDescriptor, StatusbarAlignment, IStatusbarRegistry, Extensions as StatusExtensions } from 'vs/workbench/browser/parts/statusbar/statusbar';
|
||||
import { VariablesView, WatchExpressionsView, CallStackView, BreakpointsView } from 'vs/workbench/parts/debug/electron-browser/debugViews';
|
||||
import { VariablesView } from 'vs/workbench/parts/debug/electron-browser/variablesView';
|
||||
import { BreakpointsView } from 'vs/workbench/parts/debug/electron-browser/breakpointsView';
|
||||
import { WatchExpressionsView } from 'vs/workbench/parts/debug/electron-browser/watchExpressionsView';
|
||||
import { CallStackView } from 'vs/workbench/parts/debug/electron-browser/callStackView';
|
||||
import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions';
|
||||
import {
|
||||
IDebugService, VIEWLET_ID, REPL_ID, CONTEXT_NOT_IN_DEBUG_MODE, CONTEXT_IN_DEBUG_MODE, INTERNAL_CONSOLE_OPTIONS_SCHEMA,
|
||||
@@ -46,10 +49,11 @@ import { DebugViewlet, FocusVariablesViewAction, FocusBreakpointsViewAction, Foc
|
||||
import { Repl } from 'vs/workbench/parts/debug/electron-browser/repl';
|
||||
import { DebugQuickOpenHandler } from 'vs/workbench/parts/debug/browser/debugQuickOpen';
|
||||
import { DebugStatus } from 'vs/workbench/parts/debug/browser/debugStatus';
|
||||
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
|
||||
|
||||
class OpenDebugViewletAction extends ToggleViewletAction {
|
||||
public static ID = VIEWLET_ID;
|
||||
public static LABEL = nls.localize('toggleDebugViewlet', "Show Debug");
|
||||
public static readonly ID = VIEWLET_ID;
|
||||
public static readonly LABEL = nls.localize('toggleDebugViewlet', "Show Debug");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
@@ -62,8 +66,8 @@ class OpenDebugViewletAction extends ToggleViewletAction {
|
||||
}
|
||||
|
||||
class OpenDebugPanelAction extends TogglePanelAction {
|
||||
public static ID = 'workbench.debug.action.toggleRepl';
|
||||
public static LABEL = nls.localize('toggleDebugPanel', "Debug Console");
|
||||
public static readonly ID = 'workbench.debug.action.toggleRepl';
|
||||
public static readonly LABEL = nls.localize('toggleDebugPanel', "Debug Console");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
@@ -113,10 +117,10 @@ const registry = Registry.as<IWorkbenchActionRegistry>(WorkbenchActionRegistryEx
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(OpenDebugPanelAction, OpenDebugPanelAction.ID, OpenDebugPanelAction.LABEL, openPanelKb), 'View: Debug Console', nls.localize('view', "View"));
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(OpenDebugViewletAction, OpenDebugViewletAction.ID, OpenDebugViewletAction.LABEL, openViewletKb), 'View: Show Debug', nls.localize('view', "View"));
|
||||
|
||||
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(DebugEditorModelManager);
|
||||
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(DebugActionsWidget);
|
||||
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(DebugContentProvider);
|
||||
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(StatusBarColorProvider);
|
||||
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(DebugEditorModelManager, LifecyclePhase.Running);
|
||||
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(DebugActionsWidget, LifecyclePhase.Running);
|
||||
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(DebugContentProvider, LifecyclePhase.Eventually);
|
||||
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(StatusBarColorProvider, LifecyclePhase.Eventually);
|
||||
|
||||
const debugCategory = nls.localize('debugCategory', "Debug");
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(
|
||||
@@ -187,7 +191,18 @@ configurationRegistry.registerConfiguration({
|
||||
description: nls.localize({ comment: ['This is the description for a setting'], key: 'hideActionBar' }, "Controls if the floating debug action bar should be hidden"),
|
||||
default: false
|
||||
},
|
||||
'debug.showInStatusBar': {
|
||||
enum: ['never', 'always', 'onFirstSessionStart'],
|
||||
enumDescriptions: [nls.localize('never', "Never show debug in status bar"), nls.localize('always', "Always show debug in status bar"), nls.localize('onFirstSessionStart', "Show debug in status bar only after debug was started for the first time")],
|
||||
description: nls.localize({ comment: ['This is the description for a setting'], key: 'showInStatusBar' }, "Controls when the debug status bar should be visible"),
|
||||
default: 'onFirstSessionStart'
|
||||
},
|
||||
'debug.internalConsoleOptions': INTERNAL_CONSOLE_OPTIONS_SCHEMA,
|
||||
'debug.openDebug': {
|
||||
enum: ['neverOpen', 'openOnSessionStart', 'openOnFirstSessionStart'],
|
||||
default: 'openOnFirstSessionStart',
|
||||
description: nls.localize('openDebug', "Controls whether debug viewlet should be open on debugging session start.")
|
||||
},
|
||||
'launch': {
|
||||
type: 'object',
|
||||
description: nls.localize({ comment: ['This is the description for a setting'], key: 'launch' }, "Global debug launch configuration. Should be used as an alternative to 'launch.json' that is shared across workspaces"),
|
||||
|
||||
@@ -9,7 +9,6 @@ import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import severity from 'vs/base/common/severity';
|
||||
import { List } from 'vs/base/browser/ui/list/listWidget';
|
||||
import * as errors from 'vs/base/common/errors';
|
||||
import { ICommonCodeEditor } from 'vs/editor/common/editorCommon';
|
||||
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { IListService } from 'vs/platform/list/browser/listService';
|
||||
@@ -19,13 +18,14 @@ import { IDebugService, IEnablement, CONTEXT_BREAKPOINTS_FOCUSED, CONTEXT_WATCH_
|
||||
import { Expression, Variable, Breakpoint, FunctionBreakpoint } from 'vs/workbench/parts/debug/common/debugModel';
|
||||
import { IExtensionsViewlet, VIEWLET_ID as EXTENSIONS_VIEWLET_ID } from 'vs/workbench/parts/extensions/common/extensions';
|
||||
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
|
||||
export function registerCommands(): void {
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: 'debug.logToDebugConsole',
|
||||
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
|
||||
handler(accessor: ServicesAccessor, value: string) {
|
||||
handler: (accessor: ServicesAccessor, value: string) => {
|
||||
if (typeof value === 'string') {
|
||||
const debugService = accessor.get(IDebugService);
|
||||
// Use warning as severity to get the orange color for messages coming from the debug extension
|
||||
@@ -44,7 +44,7 @@ export function registerCommands(): void {
|
||||
handler: (accessor) => {
|
||||
const listService = accessor.get(IListService);
|
||||
const debugService = accessor.get(IDebugService);
|
||||
const focused = listService.getFocused();
|
||||
const focused = listService.lastFocusedList;
|
||||
|
||||
// Tree only
|
||||
if (!(focused instanceof List)) {
|
||||
@@ -64,7 +64,7 @@ export function registerCommands(): void {
|
||||
handler: (accessor) => {
|
||||
const listService = accessor.get(IListService);
|
||||
const debugService = accessor.get(IDebugService);
|
||||
const focused = listService.getFocused();
|
||||
const focused = listService.lastFocusedList;
|
||||
|
||||
// Tree only
|
||||
if (!(focused instanceof List)) {
|
||||
@@ -85,7 +85,7 @@ export function registerCommands(): void {
|
||||
handler: (accessor) => {
|
||||
const listService = accessor.get(IListService);
|
||||
const debugService = accessor.get(IDebugService);
|
||||
const focused = listService.getFocused();
|
||||
const focused = listService.lastFocusedList;
|
||||
|
||||
// Tree only
|
||||
if (!(focused instanceof List)) {
|
||||
@@ -106,7 +106,7 @@ export function registerCommands(): void {
|
||||
handler: (accessor) => {
|
||||
const listService = accessor.get(IListService);
|
||||
const debugService = accessor.get(IDebugService);
|
||||
const focused = listService.getFocused();
|
||||
const focused = listService.lastFocusedList;
|
||||
|
||||
// Tree only
|
||||
if (!(focused instanceof List)) {
|
||||
@@ -127,7 +127,7 @@ export function registerCommands(): void {
|
||||
handler: (accessor) => {
|
||||
const listService = accessor.get(IListService);
|
||||
const debugService = accessor.get(IDebugService);
|
||||
const focused = listService.getFocused();
|
||||
const focused = listService.lastFocusedList;
|
||||
|
||||
// Tree only
|
||||
if (!(focused instanceof List)) {
|
||||
@@ -172,7 +172,7 @@ export function registerCommands(): void {
|
||||
|
||||
return launch.openConfigFile(false).done(editor => {
|
||||
if (editor) {
|
||||
const codeEditor = <ICommonCodeEditor>editor.getControl();
|
||||
const codeEditor = <ICodeEditor>editor.getControl();
|
||||
if (codeEditor) {
|
||||
return codeEditor.getContribution<IDebugEditorContribution>(EDITOR_CONTRIBUTION_ID).addLaunchConfiguration();
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ import * as objects from 'vs/base/common/objects';
|
||||
import uri from 'vs/base/common/uri';
|
||||
import * as paths from 'vs/base/common/paths';
|
||||
import { IJSONSchema } from 'vs/base/common/jsonSchema';
|
||||
import { IModel, isCommonCodeEditor } from 'vs/editor/common/editorCommon';
|
||||
import { IModel } from 'vs/editor/common/editorCommon';
|
||||
import { IEditor } from 'vs/platform/editor/common/editor';
|
||||
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
@@ -24,7 +24,6 @@ import { IExtensionService } from 'vs/platform/extensions/common/extensions';
|
||||
import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/platform/jsonschemas/common/jsonContributionRegistry';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
@@ -33,6 +32,7 @@ import { Adapter } from 'vs/workbench/parts/debug/node/debugAdapter';
|
||||
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen';
|
||||
import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver';
|
||||
import { isCodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
|
||||
// debuggers extension point
|
||||
export const debuggersExtPoint = extensionsRegistry.ExtensionsRegistry.registerExtensionPoint<IRawAdapter[]>('debuggers', [], {
|
||||
@@ -217,16 +217,14 @@ export class ConfigurationManager implements IConfigurationManager {
|
||||
|
||||
constructor(
|
||||
@IWorkspaceContextService private contextService: IWorkspaceContextService,
|
||||
@IFileService private fileService: IFileService,
|
||||
@ITelemetryService private telemetryService: ITelemetryService,
|
||||
@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
|
||||
@IConfigurationService private configurationService: IConfigurationService,
|
||||
@IQuickOpenService private quickOpenService: IQuickOpenService,
|
||||
@IConfigurationResolverService private configurationResolverService: IConfigurationResolverService,
|
||||
@IInstantiationService private instantiationService: IInstantiationService,
|
||||
@ICommandService private commandService: ICommandService,
|
||||
@IStorageService private storageService: IStorageService,
|
||||
@ILifecycleService lifecycleService: ILifecycleService
|
||||
@ILifecycleService lifecycleService: ILifecycleService,
|
||||
@IExtensionService private extensionService: IExtensionService
|
||||
) {
|
||||
this.providers = [];
|
||||
this.adapters = [];
|
||||
@@ -258,8 +256,11 @@ export class ConfigurationManager implements IConfigurationManager {
|
||||
}
|
||||
|
||||
public resolveConfigurationByProviders(folderUri: uri | undefined, type: string | undefined, debugConfiguration: IConfig): TPromise<IConfig> {
|
||||
// pipe the config through the promises sequentially
|
||||
return this.providers.filter(p => p.type === type && p.resolveDebugConfiguration).reduce((promise, provider) => {
|
||||
// pipe the config through the promises sequentially. append at the end the '*' types
|
||||
const providers = this.providers.filter(p => p.type === type && p.resolveDebugConfiguration)
|
||||
.concat(this.providers.filter(p => p.type === '*' && p.resolveDebugConfiguration));
|
||||
|
||||
return providers.reduce((promise, provider) => {
|
||||
return promise.then(config => {
|
||||
if (config) {
|
||||
return provider.resolveDebugConfiguration(folderUri, config);
|
||||
@@ -292,7 +293,7 @@ export class ConfigurationManager implements IConfigurationManager {
|
||||
if (duplicate) {
|
||||
duplicate.merge(rawAdapter, extension.description);
|
||||
} else {
|
||||
this.adapters.push(this.instantiationService.createInstance(Adapter, rawAdapter, extension.description));
|
||||
this.adapters.push(new Adapter(rawAdapter, extension.description, this.configurationService, this.commandService));
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -379,11 +380,11 @@ export class ConfigurationManager implements IConfigurationManager {
|
||||
|
||||
public canSetBreakpointsIn(model: IModel): boolean {
|
||||
const modeId = model ? model.getLanguageIdentifier().language : null;
|
||||
if (!modeId || modeId === 'json') {
|
||||
if (!modeId || modeId === 'jsonc') {
|
||||
// do not allow breakpoints in our settings files
|
||||
return false;
|
||||
}
|
||||
if (this.configurationService.getConfiguration<IDebugConfiguration>('debug').allowBreakpointsEverywhere) {
|
||||
if (this.configurationService.getValue<IDebugConfiguration>('debug').allowBreakpointsEverywhere) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -395,34 +396,36 @@ export class ConfigurationManager implements IConfigurationManager {
|
||||
}
|
||||
|
||||
public guessAdapter(type?: string): TPromise<Adapter> {
|
||||
if (type) {
|
||||
const adapter = this.getAdapter(type);
|
||||
return TPromise.as(adapter);
|
||||
}
|
||||
return this.extensionService.activateByEvent('onDebugInitialConfigurations').then(() => this.extensionService.activateByEvent('onDebug').then(() => {
|
||||
if (type) {
|
||||
const adapter = this.getAdapter(type);
|
||||
return TPromise.as(adapter);
|
||||
}
|
||||
|
||||
const editor = this.editorService.getActiveEditor();
|
||||
if (editor) {
|
||||
const codeEditor = editor.getControl();
|
||||
if (isCommonCodeEditor(codeEditor)) {
|
||||
const model = codeEditor.getModel();
|
||||
const language = model ? model.getLanguageIdentifier().language : undefined;
|
||||
const adapters = this.adapters.filter(a => a.languages && a.languages.indexOf(language) >= 0);
|
||||
if (adapters.length === 1) {
|
||||
return TPromise.as(adapters[0]);
|
||||
const editor = this.editorService.getActiveEditor();
|
||||
if (editor) {
|
||||
const codeEditor = editor.getControl();
|
||||
if (isCodeEditor(codeEditor)) {
|
||||
const model = codeEditor.getModel();
|
||||
const language = model ? model.getLanguageIdentifier().language : undefined;
|
||||
const adapters = this.adapters.filter(a => a.languages && a.languages.indexOf(language) >= 0);
|
||||
if (adapters.length === 1) {
|
||||
return TPromise.as(adapters[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this.quickOpenService.pick([...this.adapters.filter(a => a.hasInitialConfiguration() || a.hasConfigurationProvider), { label: 'More...', separator: { border: true } }], { placeHolder: nls.localize('selectDebug', "Select Environment") })
|
||||
.then(picked => {
|
||||
if (picked instanceof Adapter) {
|
||||
return picked;
|
||||
}
|
||||
if (picked) {
|
||||
this.commandService.executeCommand('debug.installAdditionalDebuggers');
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
return this.quickOpenService.pick([...this.adapters.filter(a => a.hasInitialConfiguration() || a.hasConfigurationProvider), { label: 'More...', separator: { border: true } }], { placeHolder: nls.localize('selectDebug', "Select Environment") })
|
||||
.then(picked => {
|
||||
if (picked instanceof Adapter) {
|
||||
return picked;
|
||||
}
|
||||
if (picked) {
|
||||
this.commandService.executeCommand('debug.installAdditionalDebuggers');
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
private store(): void {
|
||||
@@ -445,14 +448,13 @@ class Launch implements ILaunch {
|
||||
@IFileService private fileService: IFileService,
|
||||
@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
|
||||
@IConfigurationService private configurationService: IConfigurationService,
|
||||
@IConfigurationResolverService private configurationResolverService: IConfigurationResolverService,
|
||||
@IExtensionService private extensionService: IExtensionService
|
||||
@IConfigurationResolverService private configurationResolverService: IConfigurationResolverService
|
||||
) {
|
||||
// noop
|
||||
}
|
||||
|
||||
public getCompound(name: string): ICompound {
|
||||
const config = this.configurationService.getConfiguration<IGlobalConfig>('launch', { resource: this.workspace.uri });
|
||||
const config = this.configurationService.getValue<IGlobalConfig>('launch', { resource: this.workspace.uri });
|
||||
if (!config || !config.compounds) {
|
||||
return null;
|
||||
}
|
||||
@@ -461,7 +463,7 @@ class Launch implements ILaunch {
|
||||
}
|
||||
|
||||
public getConfigurationNames(): string[] {
|
||||
const config = this.configurationService.getConfiguration<IGlobalConfig>('launch', { resource: this.workspace.uri });
|
||||
const config = this.configurationService.getValue<IGlobalConfig>('launch', { resource: this.workspace.uri });
|
||||
if (!config || !config.configurations || !Array.isArray(config.configurations)) {
|
||||
return [];
|
||||
} else {
|
||||
@@ -478,7 +480,7 @@ class Launch implements ILaunch {
|
||||
}
|
||||
|
||||
public getConfiguration(name: string): IConfig {
|
||||
const config = this.configurationService.getConfiguration<IGlobalConfig>('launch', { resource: this.workspace.uri });
|
||||
const config = objects.deepClone(this.configurationService.getValue<IGlobalConfig>('launch', { resource: this.workspace.uri }));
|
||||
if (!config || !config.configurations) {
|
||||
return null;
|
||||
}
|
||||
@@ -487,7 +489,7 @@ class Launch implements ILaunch {
|
||||
}
|
||||
|
||||
public resolveConfiguration(config: IConfig): TPromise<IConfig> {
|
||||
const result = objects.clone(config) as IConfig;
|
||||
const result = objects.deepClone(config) as IConfig;
|
||||
// Set operating system specific properties #1873
|
||||
const setOSProperties = (flag: boolean, osConfig: IEnvConfig) => {
|
||||
if (flag && osConfig) {
|
||||
@@ -514,59 +516,57 @@ class Launch implements ILaunch {
|
||||
}
|
||||
|
||||
public openConfigFile(sideBySide: boolean, type?: string): TPromise<IEditor> {
|
||||
return this.extensionService.activateByEvent('onDebug').then(() => {
|
||||
const resource = this.uri;
|
||||
let configFileCreated = false;
|
||||
const resource = this.uri;
|
||||
let configFileCreated = false;
|
||||
|
||||
return this.fileService.resolveContent(resource).then(content => content, err => {
|
||||
return this.fileService.resolveContent(resource).then(content => content, err => {
|
||||
|
||||
// launch.json not found: create one by collecting launch configs from debugConfigProviders
|
||||
// launch.json not found: create one by collecting launch configs from debugConfigProviders
|
||||
|
||||
return this.configurationManager.guessAdapter(type).then(adapter => {
|
||||
if (adapter) {
|
||||
return this.configurationManager.provideDebugConfigurations(this.workspace.uri, adapter.type).then(initialConfigs => {
|
||||
return adapter.getInitialConfigurationContent(initialConfigs);
|
||||
});
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}).then(content => {
|
||||
|
||||
if (!content) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
configFileCreated = true;
|
||||
return this.fileService.updateContent(resource, content).then(() => {
|
||||
// convert string into IContent; see #32135
|
||||
return { value: content };
|
||||
return this.configurationManager.guessAdapter(type).then(adapter => {
|
||||
if (adapter) {
|
||||
return this.configurationManager.provideDebugConfigurations(this.workspace.uri, adapter.type).then(initialConfigs => {
|
||||
return adapter.getInitialConfigurationContent(initialConfigs);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}).then(content => {
|
||||
|
||||
if (!content) {
|
||||
return undefined;
|
||||
}
|
||||
const index = content.value.indexOf(`"${this.configurationManager.selectedName}"`);
|
||||
let startLineNumber = 1;
|
||||
for (let i = 0; i < index; i++) {
|
||||
if (content.value.charAt(i) === '\n') {
|
||||
startLineNumber++;
|
||||
}
|
||||
}
|
||||
const selection = startLineNumber > 1 ? { startLineNumber, startColumn: 4 } : undefined;
|
||||
|
||||
return this.editorService.openEditor({
|
||||
resource: resource,
|
||||
options: {
|
||||
forceOpen: true,
|
||||
selection,
|
||||
pinned: configFileCreated, // pin only if config file is created #8727
|
||||
revealIfVisible: true
|
||||
},
|
||||
}, sideBySide);
|
||||
}, (error) => {
|
||||
throw new Error(nls.localize('DebugConfig.failed', "Unable to create 'launch.json' file inside the '.vscode' folder ({0}).", error));
|
||||
configFileCreated = true;
|
||||
return this.fileService.updateContent(resource, content).then(() => {
|
||||
// convert string into IContent; see #32135
|
||||
return { value: content };
|
||||
});
|
||||
});
|
||||
}).then(content => {
|
||||
if (!content) {
|
||||
return undefined;
|
||||
}
|
||||
const index = content.value.indexOf(`"${this.configurationManager.selectedName}"`);
|
||||
let startLineNumber = 1;
|
||||
for (let i = 0; i < index; i++) {
|
||||
if (content.value.charAt(i) === '\n') {
|
||||
startLineNumber++;
|
||||
}
|
||||
}
|
||||
const selection = startLineNumber > 1 ? { startLineNumber, startColumn: 4 } : undefined;
|
||||
|
||||
return this.editorService.openEditor({
|
||||
resource: resource,
|
||||
options: {
|
||||
forceOpen: true,
|
||||
selection,
|
||||
pinned: configFileCreated, // pin only if config file is created #8727
|
||||
revealIfVisible: true
|
||||
},
|
||||
}, sideBySide);
|
||||
}, (error) => {
|
||||
throw new Error(nls.localize('DebugConfig.failed', "Unable to create 'launch.json' file inside the '.vscode' folder ({0}).", error));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,9 +18,9 @@ import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { StandardTokenType } from 'vs/editor/common/modes';
|
||||
import { DEFAULT_WORD_REGEXP } from 'vs/editor/common/model/wordHelper';
|
||||
import { ICodeEditor, IEditorMouseEvent, MouseTargetType } from 'vs/editor/browser/editorBrowser';
|
||||
import { editorContribution } from 'vs/editor/browser/editorBrowserExtensions';
|
||||
import { registerEditorContribution } from 'vs/editor/browser/editorExtensions';
|
||||
import { IDecorationOptions, IModelDecorationOptions, IModelDeltaDecoration, TrackedRangeStickiness } from 'vs/editor/common/editorCommon';
|
||||
import { ICodeEditorService } from 'vs/editor/common/services/codeEditorService';
|
||||
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
@@ -34,12 +34,13 @@ import { IDebugEditorContribution, IDebugService, State, IBreakpoint, EDITOR_CON
|
||||
import { BreakpointWidget } from 'vs/workbench/parts/debug/browser/breakpointWidget';
|
||||
import { ExceptionWidget } from 'vs/workbench/parts/debug/browser/exceptionWidget';
|
||||
import { FloatingClickWidget } from 'vs/workbench/parts/preferences/browser/preferencesWidgets';
|
||||
import { IListService } from 'vs/platform/list/browser/listService';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { CoreEditingCommands } from 'vs/editor/common/controller/coreCommands';
|
||||
import { CoreEditingCommands } from 'vs/editor/browser/controller/coreCommands';
|
||||
import { first } from 'vs/base/common/arrays';
|
||||
import { IMarginData } from 'vs/editor/browser/controller/mouseTarget';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { IListService } from 'vs/platform/list/browser/listService';
|
||||
|
||||
const HOVER_DELAY = 300;
|
||||
const LAUNCH_JSON_REGEX = /launch\.json$/;
|
||||
@@ -49,7 +50,6 @@ const MAX_NUM_INLINE_VALUES = 100; // JS Global scope can have 700+ entries. We
|
||||
const MAX_INLINE_DECORATOR_LENGTH = 150; // Max string length of each inline decorator when debugging. If exceeded ... is added
|
||||
const MAX_TOKENIZATION_LINE_LEN = 500; // If line is too long, then inline values for the line are skipped
|
||||
|
||||
@editorContribution
|
||||
export class DebugEditorContribution implements IDebugEditorContribution {
|
||||
|
||||
private toDispose: lifecycle.IDisposable[];
|
||||
@@ -79,10 +79,11 @@ export class DebugEditorContribution implements IDebugEditorContribution {
|
||||
@ITelemetryService private telemetryService: ITelemetryService,
|
||||
@IListService listService: IListService,
|
||||
@IConfigurationService private configurationService: IConfigurationService,
|
||||
@IThemeService themeService: IThemeService
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IKeybindingService private keybindingService: IKeybindingService
|
||||
) {
|
||||
this.breakpointHintDecoration = [];
|
||||
this.hoverWidget = new DebugHoverWidget(this.editor, this.debugService, listService, this.instantiationService, themeService);
|
||||
this.hoverWidget = new DebugHoverWidget(this.editor, this.debugService, this.instantiationService, themeService, contextKeyService, listService);
|
||||
this.toDispose = [];
|
||||
this.showHoverScheduler = new RunOnceScheduler(() => this.showHover(this.hoverRange, false), HOVER_DELAY);
|
||||
this.hideHoverScheduler = new RunOnceScheduler(() => this.hoverWidget.hide(), HOVER_DELAY);
|
||||
@@ -97,12 +98,12 @@ export class DebugEditorContribution implements IDebugEditorContribution {
|
||||
private getContextMenuActions(breakpoints: IBreakpoint[], uri: uri, lineNumber: number): TPromise<(IAction | ContextSubMenu)[]> {
|
||||
const actions: (IAction | ContextSubMenu)[] = [];
|
||||
if (breakpoints.length === 1) {
|
||||
actions.push(this.instantiationService.createInstance(RemoveBreakpointAction, RemoveBreakpointAction.ID, RemoveBreakpointAction.LABEL));
|
||||
actions.push(this.instantiationService.createInstance(EditConditionalBreakpointAction, EditConditionalBreakpointAction.ID, EditConditionalBreakpointAction.LABEL, this.editor));
|
||||
actions.push(new RemoveBreakpointAction(RemoveBreakpointAction.ID, RemoveBreakpointAction.LABEL, this.debugService, this.keybindingService));
|
||||
actions.push(new EditConditionalBreakpointAction(EditConditionalBreakpointAction.ID, EditConditionalBreakpointAction.LABEL, this.editor, this.debugService, this.keybindingService));
|
||||
if (breakpoints[0].enabled) {
|
||||
actions.push(this.instantiationService.createInstance(DisableBreakpointAction, DisableBreakpointAction.ID, DisableBreakpointAction.LABEL));
|
||||
actions.push(new DisableBreakpointAction(DisableBreakpointAction.ID, DisableBreakpointAction.LABEL, this.debugService, this.keybindingService));
|
||||
} else {
|
||||
actions.push(this.instantiationService.createInstance(EnableBreakpointAction, EnableBreakpointAction.ID, EnableBreakpointAction.LABEL));
|
||||
actions.push(new EnableBreakpointAction(EnableBreakpointAction.ID, EnableBreakpointAction.LABEL, this.debugService, this.keybindingService));
|
||||
}
|
||||
} else if (breakpoints.length > 1) {
|
||||
const sorted = breakpoints.sort((first, second) => first.column - second.column);
|
||||
@@ -139,7 +140,7 @@ export class DebugEditorContribution implements IDebugEditorContribution {
|
||||
true,
|
||||
() => this.debugService.addBreakpoints(uri, [{ lineNumber }])
|
||||
));
|
||||
actions.push(this.instantiationService.createInstance(AddConditionalBreakpointAction, AddConditionalBreakpointAction.ID, AddConditionalBreakpointAction.LABEL, this.editor, lineNumber));
|
||||
actions.push(new AddConditionalBreakpointAction(AddConditionalBreakpointAction.ID, AddConditionalBreakpointAction.LABEL, this.editor, lineNumber, this.debugService, this.keybindingService));
|
||||
}
|
||||
|
||||
return TPromise.as(actions);
|
||||
@@ -200,7 +201,12 @@ export class DebugEditorContribution implements IDebugEditorContribution {
|
||||
this.toDispose.push(this.editor.onMouseDown((e: IEditorMouseEvent) => this.onEditorMouseDown(e)));
|
||||
this.toDispose.push(this.editor.onMouseMove((e: IEditorMouseEvent) => this.onEditorMouseMove(e)));
|
||||
this.toDispose.push(this.editor.onMouseLeave((e: IEditorMouseEvent) => {
|
||||
const rect = this.hoverWidget.getDomNode().getBoundingClientRect();
|
||||
const hoverDomNode = this.hoverWidget.getDomNode();
|
||||
if (!hoverDomNode) {
|
||||
return;
|
||||
}
|
||||
|
||||
const rect = hoverDomNode.getBoundingClientRect();
|
||||
// Only hide the hover widget if the editor mouse leave event is outside the hover widget #3528
|
||||
if (e.event.posx < rect.left || e.event.posx > rect.right || e.event.posy < rect.top || e.event.posy > rect.bottom) {
|
||||
this.hideHoverWidget();
|
||||
@@ -389,7 +395,7 @@ export class DebugEditorContribution implements IDebugEditorContribution {
|
||||
this.exceptionWidget.dispose();
|
||||
}
|
||||
|
||||
this.exceptionWidget = this.instantiationService.createInstance(ExceptionWidget, this.editor, exceptionInfo, lineNumber);
|
||||
this.exceptionWidget = this.instantiationService.createInstance(ExceptionWidget, this.editor, exceptionInfo);
|
||||
this.exceptionWidget.show({ lineNumber, column }, 0);
|
||||
}
|
||||
|
||||
@@ -469,7 +475,7 @@ export class DebugEditorContribution implements IDebugEditorContribution {
|
||||
// Inline Decorations
|
||||
private updateInlineDecorations(stackFrame: IStackFrame): void {
|
||||
const model = this.editor.getModel();
|
||||
if (!this.configurationService.getConfiguration<IDebugConfiguration>('debug').inlineValues ||
|
||||
if (!this.configurationService.getValue<IDebugConfiguration>('debug').inlineValues ||
|
||||
!model || !stackFrame || model.uri.toString() !== stackFrame.source.uri.toString()) {
|
||||
if (!this.removeInlineValuesScheduler.isScheduled()) {
|
||||
this.removeInlineValuesScheduler.schedule();
|
||||
@@ -623,3 +629,5 @@ export class DebugEditorContribution implements IDebugEditorContribution {
|
||||
this.toDispose = lifecycle.dispose(this.toDispose);
|
||||
}
|
||||
}
|
||||
|
||||
registerEditorContribution(DebugEditorContribution);
|
||||
|
||||
@@ -10,7 +10,6 @@ import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { ScrollbarVisibility } from 'vs/base/common/scrollable';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { ITree } from 'vs/base/parts/tree/browser/tree';
|
||||
import { Tree } from 'vs/base/parts/tree/browser/treeImpl';
|
||||
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { DefaultController, ICancelableEvent, ClickBehavior } from 'vs/base/parts/tree/browser/treeDefaults';
|
||||
import { IConfigurationChangedEvent } from 'vs/editor/common/config/editorOptions';
|
||||
@@ -20,25 +19,27 @@ import { IContentWidget, ICodeEditor, IContentWidgetPosition, ContentWidgetPosit
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IDebugService, IExpression, IExpressionContainer } from 'vs/workbench/parts/debug/common/debug';
|
||||
import { Expression } from 'vs/workbench/parts/debug/common/debugModel';
|
||||
import { VariablesRenderer, renderExpressionValue, VariablesDataSource } from 'vs/workbench/parts/debug/electron-browser/debugViewer';
|
||||
import { IListService } from 'vs/platform/list/browser/listService';
|
||||
import { renderExpressionValue } from 'vs/workbench/parts/debug/electron-browser/baseDebugView';
|
||||
import { VariablesDataSource, VariablesRenderer } from 'vs/workbench/parts/debug/electron-browser/variablesView';
|
||||
import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
|
||||
import { attachListStyler, attachStylerCallback } from 'vs/platform/theme/common/styler';
|
||||
import { attachStylerCallback } from 'vs/platform/theme/common/styler';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { editorHoverBackground, editorHoverBorder } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { WorkbenchTree, IListService } from 'vs/platform/list/browser/listService';
|
||||
|
||||
const $ = dom.$;
|
||||
const MAX_ELEMENTS_SHOWN = 18;
|
||||
|
||||
export class DebugHoverWidget implements IContentWidget {
|
||||
|
||||
public static ID = 'debug.hoverWidget';
|
||||
public static readonly ID = 'debug.hoverWidget';
|
||||
// editor.IContentWidget.allowEditorOverflow
|
||||
public allowEditorOverflow = true;
|
||||
|
||||
private _isVisible: boolean;
|
||||
private domNode: HTMLElement;
|
||||
private tree: ITree;
|
||||
private tree: WorkbenchTree;
|
||||
private showAtPosition: Position;
|
||||
private highlightDecorations: string[];
|
||||
private complexValueContainer: HTMLElement;
|
||||
@@ -52,13 +53,34 @@ export class DebugHoverWidget implements IContentWidget {
|
||||
constructor(
|
||||
private editor: ICodeEditor,
|
||||
private debugService: IDebugService,
|
||||
private listService: IListService,
|
||||
instantiationService: IInstantiationService,
|
||||
private themeService: IThemeService
|
||||
private instantiationService: IInstantiationService,
|
||||
private themeService: IThemeService,
|
||||
private contextKeyService: IContextKeyService,
|
||||
private listService: IListService
|
||||
) {
|
||||
this.toDispose = [];
|
||||
this.create(instantiationService);
|
||||
this.registerListeners();
|
||||
|
||||
this._isVisible = false;
|
||||
this.showAtPosition = null;
|
||||
this.highlightDecorations = [];
|
||||
}
|
||||
|
||||
private create(): void {
|
||||
this.domNode = $('.debug-hover-widget');
|
||||
this.complexValueContainer = dom.append(this.domNode, $('.complex-value'));
|
||||
this.complexValueTitle = dom.append(this.complexValueContainer, $('.title'));
|
||||
this.treeContainer = dom.append(this.complexValueContainer, $('.debug-hover-tree'));
|
||||
this.treeContainer.setAttribute('role', 'tree');
|
||||
this.tree = new WorkbenchTree(this.treeContainer, {
|
||||
dataSource: new VariablesDataSource(),
|
||||
renderer: this.instantiationService.createInstance(VariablesHoverRenderer),
|
||||
controller: new DebugHoverController(this.editor)
|
||||
}, {
|
||||
indentPixels: 6,
|
||||
twistiePixels: 15,
|
||||
ariaLabel: nls.localize('treeAriaLabel', "Debug Hover"),
|
||||
keyboardSupport: false
|
||||
}, this.contextKeyService, this.listService, this.themeService);
|
||||
|
||||
this.valueContainer = $('.value');
|
||||
this.valueContainer.tabIndex = 0;
|
||||
@@ -67,33 +89,8 @@ export class DebugHoverWidget implements IContentWidget {
|
||||
this.domNode.appendChild(this.scrollbar.getDomNode());
|
||||
this.toDispose.push(this.scrollbar);
|
||||
|
||||
this._isVisible = false;
|
||||
this.showAtPosition = null;
|
||||
this.highlightDecorations = [];
|
||||
|
||||
this.editor.addContentWidget(this);
|
||||
this.editor.applyFontInfo(this.domNode);
|
||||
}
|
||||
|
||||
private create(instantiationService: IInstantiationService): void {
|
||||
this.domNode = $('.debug-hover-widget');
|
||||
this.complexValueContainer = dom.append(this.domNode, $('.complex-value'));
|
||||
this.complexValueTitle = dom.append(this.complexValueContainer, $('.title'));
|
||||
this.treeContainer = dom.append(this.complexValueContainer, $('.debug-hover-tree'));
|
||||
this.treeContainer.setAttribute('role', 'tree');
|
||||
this.tree = new Tree(this.treeContainer, {
|
||||
dataSource: new VariablesDataSource(),
|
||||
renderer: instantiationService.createInstance(VariablesHoverRenderer),
|
||||
controller: new DebugHoverController(this.editor)
|
||||
}, {
|
||||
indentPixels: 6,
|
||||
twistiePixels: 15,
|
||||
ariaLabel: nls.localize('treeAriaLabel', "Debug Hover"),
|
||||
keyboardSupport: false
|
||||
});
|
||||
|
||||
this.toDispose.push(attachListStyler(this.tree, this.themeService));
|
||||
this.toDispose.push(this.listService.register(this.tree));
|
||||
this.toDispose.push(attachStylerCallback(this.themeService, { editorHoverBackground, editorHoverBorder }, colors => {
|
||||
this.domNode.style.backgroundColor = colors.editorHoverBackground;
|
||||
if (colors.editorHoverBorder) {
|
||||
@@ -102,13 +99,16 @@ export class DebugHoverWidget implements IContentWidget {
|
||||
this.domNode.style.border = null;
|
||||
}
|
||||
}));
|
||||
|
||||
this.registerListeners();
|
||||
this.editor.addContentWidget(this);
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
this.toDispose.push(this.tree.addListener('item:expanded', () => {
|
||||
this.toDispose.push(this.tree.onDidExpandItem(() => {
|
||||
this.layoutTree();
|
||||
}));
|
||||
this.toDispose.push(this.tree.addListener('item:collapsed', () => {
|
||||
this.toDispose.push(this.tree.onDidCollapseItem(() => {
|
||||
this.layoutTree();
|
||||
}));
|
||||
|
||||
@@ -237,7 +237,8 @@ export class DebugHoverWidget implements IContentWidget {
|
||||
}
|
||||
|
||||
private findExpressionInStackFrame(namesToFind: string[], expressionRange: Range): TPromise<IExpression> {
|
||||
return this.debugService.getViewModel().focusedStackFrame.getMostSpecificScopes(expressionRange)
|
||||
return this.debugService.getViewModel().focusedStackFrame.getScopes()
|
||||
.then(scopes => scopes.filter(s => !s.expensive))
|
||||
.then(scopes => TPromise.join(scopes.map(scope => this.doFindExpression(scope, namesToFind))))
|
||||
.then(expressions => expressions.filter(exp => !!exp))
|
||||
// only show if all expressions found have the same value
|
||||
@@ -245,6 +246,10 @@ export class DebugHoverWidget implements IContentWidget {
|
||||
}
|
||||
|
||||
private doShow(position: Position, expression: IExpression, focus: boolean, forceValueHover = false): TPromise<void> {
|
||||
if (!this.domNode) {
|
||||
this.create();
|
||||
}
|
||||
|
||||
this.showAtPosition = position;
|
||||
this._isVisible = true;
|
||||
this.stoleFocus = focus;
|
||||
|
||||
@@ -26,11 +26,10 @@ import { IExtensionService } from 'vs/platform/extensions/common/extensions';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { FileChangesEvent, FileChangeType, IFileService } from 'vs/platform/files/common/files';
|
||||
import { IMessageService, CloseAction } from 'vs/platform/message/common/message';
|
||||
import { IWindowsService, IWindowService } from 'vs/platform/windows/common/windows';
|
||||
import { IWindowService } from 'vs/platform/windows/common/windows';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { TelemetryService } from 'vs/platform/telemetry/common/telemetryService';
|
||||
import { TelemetryAppenderClient } from 'vs/platform/telemetry/common/telemetryIpc';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import * as debug from 'vs/workbench/parts/debug/common/debug';
|
||||
import { RawDebugSession } from 'vs/workbench/parts/debug/electron-browser/rawDebugSession';
|
||||
@@ -39,7 +38,7 @@ import { ViewModel } from 'vs/workbench/parts/debug/common/debugViewModel';
|
||||
import * as debugactions from 'vs/workbench/parts/debug/browser/debugActions';
|
||||
import { ConfigurationManager } from 'vs/workbench/parts/debug/electron-browser/debugConfigurationManager';
|
||||
import { ToggleMarkersPanelAction } from 'vs/workbench/parts/markers/browser/markersPanelActions';
|
||||
import { ITaskService, TaskServiceEvents, ITaskSummary } from 'vs/workbench/parts/tasks/common/taskService';
|
||||
import { ITaskService, ITaskSummary } from 'vs/workbench/parts/tasks/common/taskService';
|
||||
import { TaskError } from 'vs/workbench/parts/tasks/common/taskSystem';
|
||||
import { VIEWLET_ID as EXPLORER_VIEWLET_ID } from 'vs/workbench/parts/files/common/files';
|
||||
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
|
||||
@@ -53,6 +52,7 @@ import { EXTENSION_LOG_BROADCAST_CHANNEL, EXTENSION_ATTACH_BROADCAST_CHANNEL, EX
|
||||
import { IBroadcastService, IBroadcast } from 'vs/platform/broadcast/electron-browser/broadcastService';
|
||||
import { IRemoteConsoleLog, parse, getFirstFrame } from 'vs/base/node/console';
|
||||
import { Source } from 'vs/workbench/parts/debug/common/debugSource';
|
||||
import { TaskEvent, TaskEventKind } from 'vs/workbench/parts/tasks/common/tasks';
|
||||
|
||||
const DEBUG_BREAKPOINTS_KEY = 'debug.breakpoint';
|
||||
const DEBUG_BREAKPOINTS_ACTIVATED_KEY = 'debug.breakpointactivated';
|
||||
@@ -60,11 +60,6 @@ const DEBUG_FUNCTION_BREAKPOINTS_KEY = 'debug.functionbreakpoint';
|
||||
const DEBUG_EXCEPTION_BREAKPOINTS_KEY = 'debug.exceptionbreakpoint';
|
||||
const DEBUG_WATCH_EXPRESSIONS_KEY = 'debug.watchexpressions';
|
||||
|
||||
interface StartSessionResult {
|
||||
status: 'ok' | 'initialConfiguration' | 'saveConfiguration';
|
||||
content?: string;
|
||||
};
|
||||
|
||||
export class DebugService implements debug.IDebugService {
|
||||
public _serviceBrand: any;
|
||||
|
||||
@@ -84,6 +79,7 @@ export class DebugService implements debug.IDebugService {
|
||||
private debugState: IContextKey<string>;
|
||||
private breakpointsToSendOnResourceSaved: Set<string>;
|
||||
private launchJsonChanged: boolean;
|
||||
private firstSessionStart: boolean;
|
||||
private previousState: debug.State;
|
||||
|
||||
constructor(
|
||||
@@ -94,20 +90,18 @@ export class DebugService implements debug.IDebugService {
|
||||
@IPanelService private panelService: IPanelService,
|
||||
@IMessageService private messageService: IMessageService,
|
||||
@IPartService private partService: IPartService,
|
||||
@IWindowsService private windowsService: IWindowsService,
|
||||
@IWindowService private windowService: IWindowService,
|
||||
@IBroadcastService private broadcastService: IBroadcastService,
|
||||
@ITelemetryService private telemetryService: ITelemetryService,
|
||||
@IWorkspaceContextService private contextService: IWorkspaceContextService,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@ILifecycleService lifecycleService: ILifecycleService,
|
||||
@ILifecycleService private lifecycleService: ILifecycleService,
|
||||
@IInstantiationService private instantiationService: IInstantiationService,
|
||||
@IExtensionService private extensionService: IExtensionService,
|
||||
@IMarkerService private markerService: IMarkerService,
|
||||
@ITaskService private taskService: ITaskService,
|
||||
@IFileService private fileService: IFileService,
|
||||
@IConfigurationService private configurationService: IConfigurationService,
|
||||
@ICommandService private commandService: ICommandService
|
||||
@IConfigurationService private configurationService: IConfigurationService
|
||||
) {
|
||||
this.toDispose = [];
|
||||
this.toDisposeOnSessionEnd = new Map<string, lifecycle.IDisposable[]>();
|
||||
@@ -129,14 +123,15 @@ export class DebugService implements debug.IDebugService {
|
||||
this.loadExceptionBreakpoints(), this.loadWatchExpressions());
|
||||
this.toDispose.push(this.model);
|
||||
this.viewModel = new ViewModel();
|
||||
this.firstSessionStart = true;
|
||||
|
||||
this.registerListeners(lifecycleService);
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
private registerListeners(lifecycleService: ILifecycleService): void {
|
||||
private registerListeners(): void {
|
||||
this.toDispose.push(this.fileService.onFileChanges(e => this.onFileChanges(e)));
|
||||
lifecycleService.onShutdown(this.store, this);
|
||||
lifecycleService.onShutdown(this.dispose, this);
|
||||
this.lifecycleService.onShutdown(this.store, this);
|
||||
this.lifecycleService.onShutdown(this.dispose, this);
|
||||
this.toDispose.push(this.broadcastService.onBroadcast(this.onBroadcast, this));
|
||||
}
|
||||
|
||||
@@ -330,6 +325,7 @@ export class DebugService implements debug.IDebugService {
|
||||
this.updateStateAndEmit(session.getId(), debug.State.Running);
|
||||
}));
|
||||
|
||||
let outputPromises: TPromise<void>[] = [];
|
||||
this.toDisposeOnSessionEnd.get(session.getId()).push(session.onDidOutput(event => {
|
||||
if (!event.body) {
|
||||
return;
|
||||
@@ -347,24 +343,26 @@ export class DebugService implements debug.IDebugService {
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure to append output in the correct order by properly waiting on preivous promises #33822
|
||||
const waitFor = outputPromises.slice();
|
||||
const source = event.body.source ? {
|
||||
lineNumber: event.body.line,
|
||||
column: event.body.column,
|
||||
source: process.getSource(event.body.source)
|
||||
} : undefined;
|
||||
|
||||
if (event.body.variablesReference) {
|
||||
const container = new ExpressionContainer(process, event.body.variablesReference, generateUuid());
|
||||
container.getChildren().then(children => {
|
||||
children.forEach(child => {
|
||||
outputPromises.push(container.getChildren().then(children => {
|
||||
return TPromise.join(waitFor).then(() => children.forEach(child => {
|
||||
// Since we can not display multiple trees in a row, we are displaying these variables one after the other (ignoring their names)
|
||||
child.name = null;
|
||||
this.logToRepl(child, outputSeverity, source);
|
||||
});
|
||||
});
|
||||
}));
|
||||
}));
|
||||
} else if (typeof event.body.output === 'string') {
|
||||
this.logToRepl(event.body.output, outputSeverity, source);
|
||||
TPromise.join(waitFor).then(() => this.logToRepl(event.body.output, outputSeverity, source));
|
||||
}
|
||||
TPromise.join(outputPromises).then(() => outputPromises = []);
|
||||
}));
|
||||
|
||||
this.toDisposeOnSessionEnd.get(session.getId()).push(session.onDidBreakpoint(event => {
|
||||
@@ -393,9 +391,7 @@ export class DebugService implements debug.IDebugService {
|
||||
}
|
||||
}
|
||||
|
||||
// For compatibilty reasons check if wrong reason and source not present
|
||||
// TODO@Isidor clean up these checks in October
|
||||
if (event.body.reason === 'changed' || (event.body.reason === 'new' && !event.body.breakpoint.source) || event.body.reason === 'update') {
|
||||
if (event.body.reason === 'changed') {
|
||||
if (breakpoint) {
|
||||
if (!breakpoint.column) {
|
||||
event.body.breakpoint.column = undefined;
|
||||
@@ -411,7 +407,7 @@ export class DebugService implements debug.IDebugService {
|
||||
this.toDisposeOnSessionEnd.get(session.getId()).push(session.onDidExitAdapter(event => {
|
||||
// 'Run without debugging' mode VSCode must terminate the extension host. More details: #3905
|
||||
if (strings.equalsIgnoreCase(process.configuration.type, 'extensionhost') && this.sessionStates.get(session.getId()) === debug.State.Running &&
|
||||
process && this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY && process.configuration.noDebug) {
|
||||
process && process.session.root && process.configuration.noDebug) {
|
||||
this.broadcastService.broadcast({
|
||||
channel: EXTENSION_CLOSE_EXTHOST_BROADCAST_CHANNEL,
|
||||
payload: [process.session.root.uri.fsPath]
|
||||
@@ -578,12 +574,18 @@ export class DebugService implements debug.IDebugService {
|
||||
return this.sendBreakpoints(uri);
|
||||
}
|
||||
|
||||
public updateBreakpoints(uri: uri, data: { [id: string]: DebugProtocol.Breakpoint }): TPromise<void> {
|
||||
this.model.updateBreakpoints(data);
|
||||
return this.sendBreakpoints(uri);
|
||||
}
|
||||
|
||||
public removeBreakpoints(id?: string): TPromise<any> {
|
||||
const toRemove = this.model.getBreakpoints().filter(bp => !id || bp.getId() === id);
|
||||
toRemove.forEach(bp => aria.status(nls.localize('breakpointRemoved', "Removed breakpoint, line {0}, file {1}", bp.lineNumber, bp.uri.fsPath)));
|
||||
const urisToClear = distinct(toRemove, bp => bp.uri.toString()).map(bp => bp.uri);
|
||||
|
||||
this.model.removeBreakpoints(toRemove);
|
||||
|
||||
return TPromise.join(urisToClear.map(uri => this.sendBreakpoints(uri)));
|
||||
}
|
||||
|
||||
@@ -593,7 +595,8 @@ export class DebugService implements debug.IDebugService {
|
||||
}
|
||||
|
||||
public addFunctionBreakpoint(): void {
|
||||
this.model.addFunctionBreakpoint('');
|
||||
const newFunctionBreakpoint = this.model.addFunctionBreakpoint('');
|
||||
this.viewModel.setSelectedFunctionBreakpoint(newFunctionBreakpoint);
|
||||
}
|
||||
|
||||
public renameFunctionBreakpoint(id: string, newFunctionName: string): TPromise<void> {
|
||||
@@ -607,10 +610,6 @@ export class DebugService implements debug.IDebugService {
|
||||
}
|
||||
|
||||
public addReplExpression(name: string): TPromise<void> {
|
||||
/* __GDPR__
|
||||
"debugService/addReplExpression" : {}
|
||||
*/
|
||||
this.telemetryService.publicLog('debugService/addReplExpression');
|
||||
return this.model.addReplExpression(this.viewModel.focusedProcess, this.viewModel.focusedStackFrame, name)
|
||||
// Evaluate all watch expressions and fetch variables again since repl evaluation might have changed some.
|
||||
.then(() => this.focusStackFrameAndEvaluate(this.viewModel.focusedStackFrame, this.viewModel.focusedProcess));
|
||||
@@ -653,10 +652,11 @@ export class DebugService implements debug.IDebugService {
|
||||
|
||||
// make sure to save all files and that the configuration is up to date
|
||||
return this.extensionService.activateByEvent('onDebug').then(() => this.textFileService.saveAll().then(() => this.configurationService.reloadConfiguration(root).then(() =>
|
||||
this.extensionService.onReady().then(() => {
|
||||
this.extensionService.whenInstalledExtensionsRegistered().then(() => {
|
||||
if (this.model.getProcesses().length === 0) {
|
||||
this.removeReplExpressions();
|
||||
this.allProcesses.clear();
|
||||
this.model.getBreakpoints().forEach(bp => bp.verified = false);
|
||||
}
|
||||
this.launchJsonChanged = false;
|
||||
const manager = this.getConfigurationManager();
|
||||
@@ -686,7 +686,9 @@ export class DebugService implements debug.IDebugService {
|
||||
return TPromise.join(compound.configurations.map(name => name !== compound.name ? this.startDebugging(root, name, noDebug, topCompoundName || compound.name) : TPromise.as(null)));
|
||||
}
|
||||
if (configOrName && !config) {
|
||||
return TPromise.wrapError(new Error(nls.localize('configMissing', "Configuration '{0}' is missing in 'launch.json'.", configOrName)));
|
||||
const message = !!launch ? nls.localize('configMissing', "Configuration '{0}' is missing in 'launch.json'.", configOrName) :
|
||||
nls.localize('launchJsonDoesNotExist', "'launch.json' does not exist.");
|
||||
return TPromise.wrapError(new Error(message));
|
||||
}
|
||||
|
||||
// We keep the debug type in a separate variable 'type' so that a no-folder config has no attributes.
|
||||
@@ -711,23 +713,27 @@ export class DebugService implements debug.IDebugService {
|
||||
};
|
||||
|
||||
return (type ? TPromise.as(null) : this.configurationManager.guessAdapter().then(a => type = a && a.type)).then(() =>
|
||||
this.configurationManager.resolveConfigurationByProviders(launch ? launch.workspace.uri : undefined, type, config).then(config => {
|
||||
// a falsy config indicates an aborted launch
|
||||
if (config && config.type) {
|
||||
return this.createProcess(root, config, sessionId);
|
||||
}
|
||||
(type ? this.extensionService.activateByEvent(`onDebugResolve:${type}`) : TPromise.as(null)).then(() =>
|
||||
this.configurationManager.resolveConfigurationByProviders(launch ? launch.workspace.uri : undefined, type, config).then(config => {
|
||||
// a falsy config indicates an aborted launch
|
||||
if (config && config.type) {
|
||||
return this.createProcess(root, config, sessionId);
|
||||
}
|
||||
if (launch) {
|
||||
return launch.openConfigFile(false, type).then(editor => undefined);
|
||||
}
|
||||
|
||||
return <any>launch.openConfigFile(false, type); // cast to ignore weird compile error
|
||||
})
|
||||
).then(() => wrapUpState(), err => {
|
||||
wrapUpState();
|
||||
return <any>TPromise.wrapError(err);
|
||||
});
|
||||
return undefined;
|
||||
})
|
||||
).then(() => wrapUpState(), err => {
|
||||
wrapUpState();
|
||||
return <any>TPromise.wrapError(err);
|
||||
}));
|
||||
})
|
||||
)));
|
||||
}
|
||||
|
||||
private createProcess(root: IWorkspaceFolder, config: debug.IConfig, sessionId: string): TPromise<debug.IProcess> {
|
||||
private createProcess(root: IWorkspaceFolder, config: debug.IConfig, sessionId: string): TPromise<void> {
|
||||
return this.textFileService.saveAll().then(() =>
|
||||
(this.configurationManager.selectedLaunch ? this.configurationManager.selectedLaunch.resolveConfiguration(config) : TPromise.as(config)).then(resolvedConfig => {
|
||||
if (!resolvedConfig) {
|
||||
@@ -749,12 +755,13 @@ export class DebugService implements debug.IDebugService {
|
||||
return TPromise.wrapError(errors.create(message, { actions: [this.instantiationService.createInstance(debugactions.ConfigureAction, debugactions.ConfigureAction.ID, debugactions.ConfigureAction.LABEL), CloseAction] }));
|
||||
}
|
||||
|
||||
this.toDisposeOnSessionEnd.set(sessionId, []);
|
||||
const debugAnywayAction = new Action('debug.continue', nls.localize('debugAnyway', "Debug Anyway"), null, true, () => {
|
||||
this.messageService.hideAll();
|
||||
return this.doCreateProcess(root, resolvedConfig, sessionId);
|
||||
});
|
||||
|
||||
return this.runPreLaunchTask(root, resolvedConfig.preLaunchTask).then((taskSummary: ITaskSummary) => {
|
||||
return this.runPreLaunchTask(sessionId, root, resolvedConfig.preLaunchTask).then((taskSummary: ITaskSummary) => {
|
||||
const errorCount = resolvedConfig.preLaunchTask ? this.markerService.getStatistics().errors : 0;
|
||||
const successExitCode = taskSummary && taskSummary.exitCode === 0;
|
||||
const failureExitCode = taskSummary && taskSummary.exitCode !== undefined && taskSummary.exitCode !== 0;
|
||||
@@ -841,7 +848,6 @@ export class DebugService implements debug.IDebugService {
|
||||
const process = this.model.addProcess(configuration, session);
|
||||
this.allProcesses.set(process.getId(), process);
|
||||
|
||||
this.toDisposeOnSessionEnd.set(session.getId(), []);
|
||||
if (client) {
|
||||
this.toDisposeOnSessionEnd.get(session.getId()).push(client);
|
||||
}
|
||||
@@ -867,16 +873,17 @@ export class DebugService implements debug.IDebugService {
|
||||
this._onDidNewProcess.fire(process);
|
||||
this.focusStackFrameAndEvaluate(null, process);
|
||||
|
||||
const internalConsoleOptions = configuration.internalConsoleOptions || this.configurationService.getConfiguration<debug.IDebugConfiguration>('debug').internalConsoleOptions;
|
||||
if (internalConsoleOptions === 'openOnSessionStart' || (!this.viewModel.changedWorkbenchViewState && internalConsoleOptions === 'openOnFirstSessionStart')) {
|
||||
const internalConsoleOptions = configuration.internalConsoleOptions || this.configurationService.getValue<debug.IDebugConfiguration>('debug').internalConsoleOptions;
|
||||
if (internalConsoleOptions === 'openOnSessionStart' || (this.firstSessionStart && internalConsoleOptions === 'openOnFirstSessionStart')) {
|
||||
this.panelService.openPanel(debug.REPL_ID, false).done(undefined, errors.onUnexpectedError);
|
||||
}
|
||||
|
||||
if (!this.viewModel.changedWorkbenchViewState && (this.partService.isVisible(Parts.SIDEBAR_PART) || this.contextService.getWorkbenchState() === WorkbenchState.EMPTY)) {
|
||||
// We only want to change the workbench view state on the first debug session #5738 and if the side bar is not hidden
|
||||
this.viewModel.changedWorkbenchViewState = true;
|
||||
const openDebugOptions = this.configurationService.getValue<debug.IDebugConfiguration>('debug').openDebug;
|
||||
// Open debug viewlet based on the visibility of the side bar and openDebug setting
|
||||
if (openDebugOptions === 'openOnSessionStart' || (openDebugOptions === 'openOnFirstSessionStart' && this.firstSessionStart)) {
|
||||
this.viewletService.openViewlet(debug.VIEWLET_ID);
|
||||
}
|
||||
this.firstSessionStart = false;
|
||||
|
||||
this.debugType.set(configuration.type);
|
||||
if (this.model.getProcesses().length > 1) {
|
||||
@@ -902,7 +909,7 @@ export class DebugService implements debug.IDebugService {
|
||||
watchExpressionsCount: this.model.getWatchExpressions().length,
|
||||
extensionName: `${adapter.extensionDescription.publisher}.${adapter.extensionDescription.name}`,
|
||||
isBuiltin: adapter.extensionDescription.isBuiltin,
|
||||
launchJsonExists: this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY && !!this.configurationService.getConfiguration<debug.IGlobalConfig>('launch', { resource: root.uri })
|
||||
launchJsonExists: root && !!this.configurationService.getValue<debug.IGlobalConfig>('launch', { resource: root.uri })
|
||||
});
|
||||
}).then(() => process, (error: any) => {
|
||||
if (error instanceof Error && error.message === 'Canceled') {
|
||||
@@ -941,7 +948,7 @@ export class DebugService implements debug.IDebugService {
|
||||
});
|
||||
}
|
||||
|
||||
private runPreLaunchTask(root: IWorkspaceFolder, taskName: string): TPromise<ITaskSummary> {
|
||||
private runPreLaunchTask(sessionId: string, root: IWorkspaceFolder, taskName: string): TPromise<ITaskSummary> {
|
||||
if (!taskName) {
|
||||
return TPromise.as(null);
|
||||
}
|
||||
@@ -952,6 +959,17 @@ export class DebugService implements debug.IDebugService {
|
||||
return TPromise.wrapError(errors.create(nls.localize('DebugTaskNotFound', "Could not find the preLaunchTask \'{0}\'.", taskName)));
|
||||
}
|
||||
|
||||
function once(kind: TaskEventKind, event: Event<TaskEvent>): Event<TaskEvent> {
|
||||
return (listener, thisArgs = null, disposables?) => {
|
||||
const result = event(e => {
|
||||
if (e.kind === kind) {
|
||||
result.dispose();
|
||||
return listener.call(thisArgs, e);
|
||||
}
|
||||
}, null, disposables);
|
||||
return result;
|
||||
};
|
||||
}
|
||||
// If a task is missing the problem matcher the promise will never complete, so we need to have a workaround #35340
|
||||
let taskStarted = false;
|
||||
const promise = this.taskService.getActiveTasks().then(tasks => {
|
||||
@@ -959,11 +977,16 @@ export class DebugService implements debug.IDebugService {
|
||||
// task is already running - nothing to do.
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
this.toDispose.push(this.taskService.addOneTimeListener(TaskServiceEvents.Active, () => taskStarted = true));
|
||||
this.toDisposeOnSessionEnd.get(sessionId).push(
|
||||
once(TaskEventKind.Active, this.taskService.onDidStateChange)(() => {
|
||||
taskStarted = true;
|
||||
})
|
||||
);
|
||||
const taskPromise = this.taskService.run(task);
|
||||
if (task.isBackground) {
|
||||
return new TPromise((c, e) => this.toDispose.push(this.taskService.addOneTimeListener(TaskServiceEvents.Inactive, () => c(null))));
|
||||
return new TPromise((c, e) => this.toDisposeOnSessionEnd.get(sessionId).push(
|
||||
once(TaskEventKind.Inactive, this.taskService.onDidStateChange)(() => c(null)))
|
||||
);
|
||||
}
|
||||
|
||||
return taskPromise;
|
||||
@@ -989,43 +1012,45 @@ export class DebugService implements debug.IDebugService {
|
||||
}
|
||||
|
||||
public restartProcess(process: debug.IProcess, restartData?: any): TPromise<any> {
|
||||
if (process.session.capabilities.supportsRestartRequest) {
|
||||
return this.textFileService.saveAll().then(() => process.session.custom('restart', null));
|
||||
}
|
||||
const focusedProcess = this.viewModel.focusedProcess;
|
||||
const preserveFocus = focusedProcess && process.getId() === focusedProcess.getId();
|
||||
|
||||
return process.session.disconnect(true).then(() => {
|
||||
if (strings.equalsIgnoreCase(process.configuration.type, 'extensionHost')) {
|
||||
return this.broadcastService.broadcast({
|
||||
channel: EXTENSION_RELOAD_BROADCAST_CHANNEL,
|
||||
payload: [process.session.root.uri.fsPath]
|
||||
});
|
||||
return this.textFileService.saveAll().then(() => {
|
||||
if (process.session.capabilities.supportsRestartRequest) {
|
||||
return <TPromise>process.session.custom('restart', null);
|
||||
}
|
||||
const focusedProcess = this.viewModel.focusedProcess;
|
||||
const preserveFocus = focusedProcess && process.getId() === focusedProcess.getId();
|
||||
|
||||
return new TPromise<void>((c, e) => {
|
||||
setTimeout(() => {
|
||||
// Read the configuration again if a launch.json has been changed, if not just use the inmemory configuration
|
||||
let config = process.configuration;
|
||||
if (this.launchJsonChanged && this.configurationManager.selectedLaunch) {
|
||||
this.launchJsonChanged = false;
|
||||
config = this.configurationManager.selectedLaunch.getConfiguration(process.configuration.name) || config;
|
||||
// Take the type from the process since the debug extension might overwrite it #21316
|
||||
config.type = process.configuration.type;
|
||||
config.noDebug = process.configuration.noDebug;
|
||||
}
|
||||
config.__restart = restartData;
|
||||
this.createProcess(process.session.root, config, process.getId()).then(() => c(null), err => e(err));
|
||||
}, 300);
|
||||
});
|
||||
}).then(() => {
|
||||
if (preserveFocus) {
|
||||
// Restart should preserve the focused process
|
||||
const restartedProcess = this.model.getProcesses().filter(p => p.configuration.name === process.configuration.name).pop();
|
||||
if (restartedProcess && restartedProcess !== this.viewModel.focusedProcess) {
|
||||
this.focusStackFrameAndEvaluate(null, restartedProcess);
|
||||
return process.session.disconnect(true).then(() => {
|
||||
if (strings.equalsIgnoreCase(process.configuration.type, 'extensionHost')) {
|
||||
return this.broadcastService.broadcast({
|
||||
channel: EXTENSION_RELOAD_BROADCAST_CHANNEL,
|
||||
payload: [process.session.root.uri.fsPath]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return new TPromise<void>((c, e) => {
|
||||
setTimeout(() => {
|
||||
// Read the configuration again if a launch.json has been changed, if not just use the inmemory configuration
|
||||
let config = process.configuration;
|
||||
if (this.launchJsonChanged && this.configurationManager.selectedLaunch) {
|
||||
this.launchJsonChanged = false;
|
||||
config = this.configurationManager.selectedLaunch.getConfiguration(process.configuration.name) || config;
|
||||
// Take the type from the process since the debug extension might overwrite it #21316
|
||||
config.type = process.configuration.type;
|
||||
config.noDebug = process.configuration.noDebug;
|
||||
}
|
||||
config.__restart = restartData;
|
||||
this.createProcess(process.session.root, config, process.getId()).then(() => c(null), err => e(err));
|
||||
}, 300);
|
||||
});
|
||||
}).then(() => {
|
||||
if (preserveFocus) {
|
||||
// Restart should preserve the focused process
|
||||
const restartedProcess = this.model.getProcesses().filter(p => p.configuration.name === process.configuration.name).pop();
|
||||
if (restartedProcess && restartedProcess !== this.viewModel.focusedProcess) {
|
||||
this.focusStackFrameAndEvaluate(null, restartedProcess);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1089,7 +1114,7 @@ export class DebugService implements debug.IDebugService {
|
||||
this.debugType.reset();
|
||||
this.viewModel.setMultiProcessView(false);
|
||||
|
||||
if (this.partService.isVisible(Parts.SIDEBAR_PART) && this.configurationService.getConfiguration<debug.IDebugConfiguration>('debug').openExplorerOnEnd) {
|
||||
if (this.partService.isVisible(Parts.SIDEBAR_PART) && this.configurationService.getValue<debug.IDebugConfiguration>('debug').openExplorerOnEnd) {
|
||||
this.viewletService.openViewlet(EXPLORER_VIEWLET_ID).done(null, errors.onUnexpectedError);
|
||||
}
|
||||
}
|
||||
@@ -1216,8 +1241,11 @@ export class DebugService implements debug.IDebugService {
|
||||
}
|
||||
|
||||
private onFileChanges(fileChangesEvent: FileChangesEvent): void {
|
||||
this.model.removeBreakpoints(this.model.getBreakpoints().filter(bp =>
|
||||
fileChangesEvent.contains(bp.uri, FileChangeType.DELETED)));
|
||||
const toRemove = this.model.getBreakpoints().filter(bp =>
|
||||
fileChangesEvent.contains(bp.uri, FileChangeType.DELETED));
|
||||
if (toRemove.length) {
|
||||
this.model.removeBreakpoints(toRemove);
|
||||
}
|
||||
|
||||
fileChangesEvent.getUpdated().forEach(event => {
|
||||
if (this.breakpointsToSendOnResourceSaved.has(event.resource.toString())) {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,514 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 * as resources from 'vs/base/common/resources';
|
||||
import { RunOnceScheduler, sequence } from 'vs/base/common/async';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import * as builder from 'vs/base/browser/builder';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import * as errors from 'vs/base/common/errors';
|
||||
import { EventType } from 'vs/base/common/events';
|
||||
import { IAction } from 'vs/base/common/actions';
|
||||
import { prepareActions } from 'vs/workbench/browser/actions';
|
||||
import { IHighlightEvent, ITree } from 'vs/base/parts/tree/browser/tree';
|
||||
import { Tree } from 'vs/base/parts/tree/browser/treeImpl';
|
||||
import { CollapseAction } from 'vs/workbench/browser/viewlet';
|
||||
import { ViewsViewletPanel, IViewletViewOptions, IViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet';
|
||||
import { IDebugService, State, IBreakpoint, IExpression, CONTEXT_BREAKPOINTS_FOCUSED, CONTEXT_WATCH_EXPRESSIONS_FOCUSED, CONTEXT_VARIABLES_FOCUSED } from 'vs/workbench/parts/debug/common/debug';
|
||||
import { Expression, Variable, ExceptionBreakpoint, FunctionBreakpoint, Thread, StackFrame, Breakpoint, ThreadAndProcessIds } from 'vs/workbench/parts/debug/common/debugModel';
|
||||
import * as viewer from 'vs/workbench/parts/debug/electron-browser/debugViewer';
|
||||
import { AddWatchExpressionAction, RemoveAllWatchExpressionsAction, AddFunctionBreakpointAction, ToggleBreakpointsActivatedAction, RemoveAllBreakpointsAction } from 'vs/workbench/parts/debug/browser/debugActions';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { MenuId } from 'vs/platform/actions/common/actions';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IListService } from 'vs/platform/list/browser/listService';
|
||||
import { attachListStyler } from 'vs/platform/theme/common/styler';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
|
||||
function renderViewTree(container: HTMLElement): HTMLElement {
|
||||
const treeContainer = document.createElement('div');
|
||||
dom.addClass(treeContainer, 'debug-view-content');
|
||||
container.appendChild(treeContainer);
|
||||
return treeContainer;
|
||||
}
|
||||
|
||||
const $ = builder.$;
|
||||
const twistiePixels = 20;
|
||||
|
||||
export class VariablesView extends ViewsViewletPanel {
|
||||
|
||||
private static MEMENTO = 'variablesview.memento';
|
||||
private onFocusStackFrameScheduler: RunOnceScheduler;
|
||||
private variablesFocusedContext: IContextKey<boolean>;
|
||||
private settings: any;
|
||||
private expandedElements: any[];
|
||||
|
||||
constructor(
|
||||
private options: IViewletViewOptions,
|
||||
@IContextMenuService contextMenuService: IContextMenuService,
|
||||
@ITelemetryService private telemetryService: ITelemetryService,
|
||||
@IDebugService private debugService: IDebugService,
|
||||
@IKeybindingService keybindingService: IKeybindingService,
|
||||
@IInstantiationService private instantiationService: IInstantiationService,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@IListService private listService: IListService,
|
||||
@IThemeService private themeService: IThemeService
|
||||
) {
|
||||
super({ ...(options as IViewOptions), ariaHeaderLabel: nls.localize('variablesSection', "Variables Section") }, keybindingService, contextMenuService);
|
||||
|
||||
this.settings = options.viewletSettings;
|
||||
this.variablesFocusedContext = CONTEXT_VARIABLES_FOCUSED.bindTo(contextKeyService);
|
||||
this.expandedElements = [];
|
||||
// Use scheduler to prevent unnecessary flashing
|
||||
this.onFocusStackFrameScheduler = new RunOnceScheduler(() => {
|
||||
// Remember expanded elements when there are some (otherwise don't override/erase the previous ones)
|
||||
const expanded = this.tree.getExpandedElements();
|
||||
if (expanded.length > 0) {
|
||||
this.expandedElements = expanded;
|
||||
}
|
||||
|
||||
// Always clear tree highlight to avoid ending up in a broken state #12203
|
||||
this.tree.clearHighlight();
|
||||
this.tree.refresh().then(() => {
|
||||
const stackFrame = this.debugService.getViewModel().focusedStackFrame;
|
||||
return sequence(this.expandedElements.map(e => () => this.tree.expand(e))).then(() => {
|
||||
// If there is no preserved expansion state simply expand the first scope
|
||||
if (stackFrame && this.tree.getExpandedElements().length === 0) {
|
||||
return stackFrame.getScopes().then(scopes => {
|
||||
if (scopes.length > 0 && !scopes[0].expensive) {
|
||||
return this.tree.expand(scopes[0]);
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
}).done(null, errors.onUnexpectedError);
|
||||
}, 400);
|
||||
}
|
||||
|
||||
public renderBody(container: HTMLElement): void {
|
||||
dom.addClass(container, 'debug-variables');
|
||||
this.treeContainer = renderViewTree(container);
|
||||
|
||||
this.tree = new Tree(this.treeContainer, {
|
||||
dataSource: new viewer.VariablesDataSource(),
|
||||
renderer: this.instantiationService.createInstance(viewer.VariablesRenderer),
|
||||
accessibilityProvider: new viewer.VariablesAccessibilityProvider(),
|
||||
controller: this.instantiationService.createInstance(viewer.VariablesController, new viewer.VariablesActionProvider(this.instantiationService), MenuId.DebugVariablesContext)
|
||||
}, {
|
||||
ariaLabel: nls.localize('variablesAriaTreeLabel', "Debug Variables"),
|
||||
twistiePixels,
|
||||
keyboardSupport: false
|
||||
});
|
||||
|
||||
this.disposables.push(attachListStyler(this.tree, this.themeService));
|
||||
this.disposables.push(this.listService.register(this.tree, [this.variablesFocusedContext]));
|
||||
|
||||
const viewModel = this.debugService.getViewModel();
|
||||
|
||||
this.tree.setInput(viewModel);
|
||||
|
||||
const collapseAction = this.instantiationService.createInstance(CollapseAction, this.tree, false, 'explorer-action collapse-explorer');
|
||||
this.toolbar.setActions(prepareActions([collapseAction]))();
|
||||
|
||||
this.disposables.push(viewModel.onDidFocusStackFrame(sf => {
|
||||
// Refresh the tree immediately if it is not visible.
|
||||
// Otherwise postpone the refresh until user stops stepping.
|
||||
if (!this.tree.getContentHeight() || sf.explicit) {
|
||||
this.onFocusStackFrameScheduler.schedule(0);
|
||||
} else {
|
||||
this.onFocusStackFrameScheduler.schedule();
|
||||
}
|
||||
}));
|
||||
this.disposables.push(this.debugService.onDidChangeState(state => {
|
||||
collapseAction.enabled = state === State.Running || state === State.Stopped;
|
||||
}));
|
||||
|
||||
this.disposables.push(this.debugService.getViewModel().onDidSelectExpression(expression => {
|
||||
if (!expression || !(expression instanceof Variable)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.tree.refresh(expression, false).then(() => {
|
||||
this.tree.setHighlight(expression);
|
||||
this.tree.addOneTimeListener(EventType.HIGHLIGHT, (e: IHighlightEvent) => {
|
||||
if (!e.highlight) {
|
||||
this.debugService.getViewModel().setSelectedExpression(null);
|
||||
}
|
||||
});
|
||||
}).done(null, errors.onUnexpectedError);
|
||||
}));
|
||||
}
|
||||
|
||||
public shutdown(): void {
|
||||
this.settings[VariablesView.MEMENTO] = !this.isExpanded();
|
||||
super.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
export class WatchExpressionsView extends ViewsViewletPanel {
|
||||
|
||||
private static MEMENTO = 'watchexpressionsview.memento';
|
||||
private onWatchExpressionsUpdatedScheduler: RunOnceScheduler;
|
||||
private toReveal: IExpression;
|
||||
private watchExpressionsFocusedContext: IContextKey<boolean>;
|
||||
private settings: any;
|
||||
|
||||
constructor(
|
||||
private options: IViewletViewOptions,
|
||||
@IContextMenuService contextMenuService: IContextMenuService,
|
||||
@IDebugService private debugService: IDebugService,
|
||||
@IKeybindingService keybindingService: IKeybindingService,
|
||||
@IInstantiationService private instantiationService: IInstantiationService,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@IListService private listService: IListService,
|
||||
@IThemeService private themeService: IThemeService
|
||||
) {
|
||||
super({ ...(options as IViewOptions), ariaHeaderLabel: nls.localize('expressionsSection', "Expressions Section") }, keybindingService, contextMenuService);
|
||||
this.settings = options.viewletSettings;
|
||||
|
||||
this.disposables.push(this.debugService.getModel().onDidChangeWatchExpressions(we => {
|
||||
// only expand when a new watch expression is added.
|
||||
if (we instanceof Expression) {
|
||||
this.setExpanded(true);
|
||||
}
|
||||
}));
|
||||
this.watchExpressionsFocusedContext = CONTEXT_WATCH_EXPRESSIONS_FOCUSED.bindTo(contextKeyService);
|
||||
|
||||
this.onWatchExpressionsUpdatedScheduler = new RunOnceScheduler(() => {
|
||||
this.tree.refresh().done(() => {
|
||||
return this.toReveal instanceof Expression ? this.tree.reveal(this.toReveal) : TPromise.as(true);
|
||||
}, errors.onUnexpectedError);
|
||||
}, 50);
|
||||
}
|
||||
|
||||
public renderBody(container: HTMLElement): void {
|
||||
dom.addClass(container, 'debug-watch');
|
||||
this.treeContainer = renderViewTree(container);
|
||||
|
||||
const actionProvider = new viewer.WatchExpressionsActionProvider(this.instantiationService);
|
||||
this.tree = new Tree(this.treeContainer, {
|
||||
dataSource: new viewer.WatchExpressionsDataSource(),
|
||||
renderer: this.instantiationService.createInstance(viewer.WatchExpressionsRenderer, actionProvider, this.actionRunner),
|
||||
accessibilityProvider: new viewer.WatchExpressionsAccessibilityProvider(),
|
||||
controller: this.instantiationService.createInstance(viewer.WatchExpressionsController, actionProvider, MenuId.DebugWatchContext),
|
||||
dnd: this.instantiationService.createInstance(viewer.WatchExpressionsDragAndDrop)
|
||||
}, {
|
||||
ariaLabel: nls.localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'watchAriaTreeLabel' }, "Debug Watch Expressions"),
|
||||
twistiePixels,
|
||||
keyboardSupport: false
|
||||
});
|
||||
|
||||
this.disposables.push(attachListStyler(this.tree, this.themeService));
|
||||
this.disposables.push(this.listService.register(this.tree, [this.watchExpressionsFocusedContext]));
|
||||
|
||||
this.tree.setInput(this.debugService.getModel());
|
||||
|
||||
const addWatchExpressionAction = this.instantiationService.createInstance(AddWatchExpressionAction, AddWatchExpressionAction.ID, AddWatchExpressionAction.LABEL);
|
||||
const collapseAction = this.instantiationService.createInstance(CollapseAction, this.tree, true, 'explorer-action collapse-explorer');
|
||||
const removeAllWatchExpressionsAction = this.instantiationService.createInstance(RemoveAllWatchExpressionsAction, RemoveAllWatchExpressionsAction.ID, RemoveAllWatchExpressionsAction.LABEL);
|
||||
this.toolbar.setActions(prepareActions([addWatchExpressionAction, collapseAction, removeAllWatchExpressionsAction]))();
|
||||
|
||||
this.disposables.push(this.debugService.getModel().onDidChangeWatchExpressions(we => {
|
||||
if (!this.onWatchExpressionsUpdatedScheduler.isScheduled()) {
|
||||
this.onWatchExpressionsUpdatedScheduler.schedule();
|
||||
}
|
||||
this.toReveal = we;
|
||||
}));
|
||||
|
||||
this.disposables.push(this.debugService.getViewModel().onDidSelectExpression(expression => {
|
||||
if (!expression || !(expression instanceof Expression)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.tree.refresh(expression, false).then(() => {
|
||||
this.tree.setHighlight(expression);
|
||||
this.tree.addOneTimeListener(EventType.HIGHLIGHT, (e: IHighlightEvent) => {
|
||||
if (!e.highlight) {
|
||||
this.debugService.getViewModel().setSelectedExpression(null);
|
||||
}
|
||||
});
|
||||
}).done(null, errors.onUnexpectedError);
|
||||
}));
|
||||
}
|
||||
|
||||
public shutdown(): void {
|
||||
this.settings[WatchExpressionsView.MEMENTO] = !this.isExpanded();
|
||||
super.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
export class CallStackView extends ViewsViewletPanel {
|
||||
|
||||
private static MEMENTO = 'callstackview.memento';
|
||||
private pauseMessage: builder.Builder;
|
||||
private pauseMessageLabel: builder.Builder;
|
||||
private onCallStackChangeScheduler: RunOnceScheduler;
|
||||
private settings: any;
|
||||
|
||||
constructor(
|
||||
private options: IViewletViewOptions,
|
||||
@IContextMenuService contextMenuService: IContextMenuService,
|
||||
@ITelemetryService private telemetryService: ITelemetryService,
|
||||
@IDebugService private debugService: IDebugService,
|
||||
@IKeybindingService keybindingService: IKeybindingService,
|
||||
@IInstantiationService private instantiationService: IInstantiationService,
|
||||
@IListService private listService: IListService,
|
||||
@IThemeService private themeService: IThemeService
|
||||
) {
|
||||
super({ ...(options as IViewOptions), ariaHeaderLabel: nls.localize('callstackSection', "Call Stack Section") }, keybindingService, contextMenuService);
|
||||
this.settings = options.viewletSettings;
|
||||
|
||||
// Create scheduler to prevent unnecessary flashing of tree when reacting to changes
|
||||
this.onCallStackChangeScheduler = new RunOnceScheduler(() => {
|
||||
let newTreeInput: any = this.debugService.getModel();
|
||||
const processes = this.debugService.getModel().getProcesses();
|
||||
if (!this.debugService.getViewModel().isMultiProcessView() && processes.length) {
|
||||
const threads = processes[0].getAllThreads();
|
||||
// Only show the threads in the call stack if there is more than 1 thread.
|
||||
newTreeInput = threads.length === 1 ? threads[0] : processes[0];
|
||||
}
|
||||
|
||||
// Only show the global pause message if we do not display threads.
|
||||
// Otherwise there will be a pause message per thread and there is no need for a global one.
|
||||
if (newTreeInput instanceof Thread && newTreeInput.stoppedDetails) {
|
||||
this.pauseMessageLabel.text(newTreeInput.stoppedDetails.description || nls.localize('debugStopped', "Paused on {0}", newTreeInput.stoppedDetails.reason));
|
||||
if (newTreeInput.stoppedDetails.text) {
|
||||
this.pauseMessageLabel.title(newTreeInput.stoppedDetails.text);
|
||||
}
|
||||
newTreeInput.stoppedDetails.reason === 'exception' ? this.pauseMessageLabel.addClass('exception') : this.pauseMessageLabel.removeClass('exception');
|
||||
this.pauseMessage.show();
|
||||
} else {
|
||||
this.pauseMessage.hide();
|
||||
}
|
||||
|
||||
(this.tree.getInput() === newTreeInput ? this.tree.refresh() : this.tree.setInput(newTreeInput))
|
||||
.done(() => this.updateTreeSelection(), errors.onUnexpectedError);
|
||||
}, 50);
|
||||
}
|
||||
|
||||
protected renderHeaderTitle(container: HTMLElement): void {
|
||||
const title = $('.title.debug-call-stack-title').appendTo(container);
|
||||
$('span').text(this.options.name).appendTo(title);
|
||||
this.pauseMessage = $('span.pause-message').appendTo(title);
|
||||
this.pauseMessage.hide();
|
||||
this.pauseMessageLabel = $('span.label').appendTo(this.pauseMessage);
|
||||
}
|
||||
|
||||
public renderBody(container: HTMLElement): void {
|
||||
dom.addClass(container, 'debug-call-stack');
|
||||
this.treeContainer = renderViewTree(container);
|
||||
const actionProvider = this.instantiationService.createInstance(viewer.CallStackActionProvider);
|
||||
const controller = this.instantiationService.createInstance(viewer.CallStackController, actionProvider, MenuId.DebugCallStackContext);
|
||||
|
||||
this.tree = new Tree(this.treeContainer, {
|
||||
dataSource: this.instantiationService.createInstance(viewer.CallStackDataSource),
|
||||
renderer: this.instantiationService.createInstance(viewer.CallStackRenderer),
|
||||
accessibilityProvider: this.instantiationService.createInstance(viewer.CallstackAccessibilityProvider),
|
||||
controller
|
||||
}, {
|
||||
ariaLabel: nls.localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'callStackAriaLabel' }, "Debug Call Stack"),
|
||||
twistiePixels,
|
||||
keyboardSupport: false
|
||||
});
|
||||
|
||||
this.disposables.push(attachListStyler(this.tree, this.themeService));
|
||||
this.disposables.push(this.listService.register(this.tree));
|
||||
|
||||
this.disposables.push(this.tree.addListener('selection', event => {
|
||||
if (event && event.payload && event.payload.origin === 'keyboard') {
|
||||
const element = this.tree.getFocus();
|
||||
if (element instanceof ThreadAndProcessIds) {
|
||||
controller.showMoreStackFrames(this.tree, element);
|
||||
} else if (element instanceof StackFrame) {
|
||||
controller.focusStackFrame(element, event, false);
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
this.disposables.push(this.debugService.getModel().onDidChangeCallStack(() => {
|
||||
if (!this.onCallStackChangeScheduler.isScheduled()) {
|
||||
this.onCallStackChangeScheduler.schedule();
|
||||
}
|
||||
}));
|
||||
this.disposables.push(this.debugService.getViewModel().onDidFocusStackFrame(() =>
|
||||
this.updateTreeSelection().done(undefined, errors.onUnexpectedError)));
|
||||
|
||||
// Schedule the update of the call stack tree if the viewlet is opened after a session started #14684
|
||||
if (this.debugService.state === State.Stopped) {
|
||||
this.onCallStackChangeScheduler.schedule();
|
||||
}
|
||||
}
|
||||
|
||||
private updateTreeSelection(): TPromise<void> {
|
||||
if (!this.tree.getInput()) {
|
||||
// Tree not initialized yet
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
const stackFrame = this.debugService.getViewModel().focusedStackFrame;
|
||||
const thread = this.debugService.getViewModel().focusedThread;
|
||||
const process = this.debugService.getViewModel().focusedProcess;
|
||||
if (!thread) {
|
||||
if (!process) {
|
||||
this.tree.clearSelection();
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
this.tree.setSelection([process]);
|
||||
return this.tree.reveal(process);
|
||||
}
|
||||
|
||||
return this.tree.expandAll([thread.process, thread]).then(() => {
|
||||
if (!stackFrame) {
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
this.tree.setSelection([stackFrame]);
|
||||
return this.tree.reveal(stackFrame);
|
||||
});
|
||||
}
|
||||
|
||||
public shutdown(): void {
|
||||
this.settings[CallStackView.MEMENTO] = !this.isExpanded();
|
||||
super.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
export class BreakpointsView extends ViewsViewletPanel {
|
||||
|
||||
private static MAX_VISIBLE_FILES = 9;
|
||||
private static MEMENTO = 'breakopintsview.memento';
|
||||
private breakpointsFocusedContext: IContextKey<boolean>;
|
||||
private settings: any;
|
||||
|
||||
constructor(
|
||||
private options: IViewletViewOptions,
|
||||
@IContextMenuService contextMenuService: IContextMenuService,
|
||||
@IDebugService private debugService: IDebugService,
|
||||
@IKeybindingService keybindingService: IKeybindingService,
|
||||
@IInstantiationService private instantiationService: IInstantiationService,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@IListService private listService: IListService,
|
||||
@IThemeService private themeService: IThemeService
|
||||
) {
|
||||
super({
|
||||
...(options as IViewOptions),
|
||||
ariaHeaderLabel: nls.localize('breakpointsSection', "Breakpoints Section")
|
||||
}, keybindingService, contextMenuService);
|
||||
|
||||
this.minimumBodySize = this.maximumBodySize = this.getExpandedBodySize();
|
||||
this.settings = options.viewletSettings;
|
||||
this.breakpointsFocusedContext = CONTEXT_BREAKPOINTS_FOCUSED.bindTo(contextKeyService);
|
||||
this.disposables.push(this.debugService.getModel().onDidChangeBreakpoints(() => this.onBreakpointsChange()));
|
||||
}
|
||||
|
||||
public renderBody(container: HTMLElement): void {
|
||||
dom.addClass(container, 'debug-breakpoints');
|
||||
this.treeContainer = renderViewTree(container);
|
||||
const actionProvider = new viewer.BreakpointsActionProvider(this.instantiationService, this.debugService);
|
||||
const controller = this.instantiationService.createInstance(viewer.BreakpointsController, actionProvider, MenuId.DebugBreakpointsContext);
|
||||
|
||||
this.tree = new Tree(this.treeContainer, {
|
||||
dataSource: new viewer.BreakpointsDataSource(),
|
||||
renderer: this.instantiationService.createInstance(viewer.BreakpointsRenderer, actionProvider, this.actionRunner),
|
||||
accessibilityProvider: this.instantiationService.createInstance(viewer.BreakpointsAccessibilityProvider),
|
||||
controller,
|
||||
sorter: {
|
||||
compare(tree: ITree, element: any, otherElement: any): number {
|
||||
const first = <IBreakpoint>element;
|
||||
const second = <IBreakpoint>otherElement;
|
||||
if (first instanceof ExceptionBreakpoint) {
|
||||
return -1;
|
||||
}
|
||||
if (second instanceof ExceptionBreakpoint) {
|
||||
return 1;
|
||||
}
|
||||
if (first instanceof FunctionBreakpoint) {
|
||||
return -1;
|
||||
}
|
||||
if (second instanceof FunctionBreakpoint) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (first.uri.toString() !== second.uri.toString()) {
|
||||
return resources.basenameOrAuthority(first.uri).localeCompare(resources.basenameOrAuthority(second.uri));
|
||||
}
|
||||
if (first.lineNumber === second.lineNumber) {
|
||||
return first.column - second.column;
|
||||
}
|
||||
|
||||
return first.lineNumber - second.lineNumber;
|
||||
}
|
||||
}
|
||||
}, {
|
||||
ariaLabel: nls.localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'breakpointsAriaTreeLabel' }, "Debug Breakpoints"),
|
||||
twistiePixels,
|
||||
keyboardSupport: false
|
||||
});
|
||||
|
||||
this.disposables.push(attachListStyler(this.tree, this.themeService));
|
||||
this.disposables.push(this.listService.register(this.tree, [this.breakpointsFocusedContext]));
|
||||
|
||||
this.disposables.push(this.tree.addListener('selection', event => {
|
||||
if (event && event.payload && event.payload.origin === 'keyboard') {
|
||||
const element = this.tree.getFocus();
|
||||
if (element instanceof Breakpoint) {
|
||||
controller.openBreakpointSource(element, event, false);
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
const debugModel = this.debugService.getModel();
|
||||
|
||||
this.tree.setInput(debugModel);
|
||||
|
||||
this.disposables.push(this.debugService.getViewModel().onDidSelectFunctionBreakpoint(fbp => {
|
||||
if (!fbp || !(fbp instanceof FunctionBreakpoint)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.tree.refresh(fbp, false).then(() => {
|
||||
this.tree.setHighlight(fbp);
|
||||
this.tree.addOneTimeListener(EventType.HIGHLIGHT, (e: IHighlightEvent) => {
|
||||
if (!e.highlight) {
|
||||
this.debugService.getViewModel().setSelectedFunctionBreakpoint(null);
|
||||
}
|
||||
});
|
||||
}).done(null, errors.onUnexpectedError);
|
||||
}));
|
||||
}
|
||||
|
||||
public getActions(): IAction[] {
|
||||
return [
|
||||
this.instantiationService.createInstance(AddFunctionBreakpointAction, AddFunctionBreakpointAction.ID, AddFunctionBreakpointAction.LABEL),
|
||||
this.instantiationService.createInstance(ToggleBreakpointsActivatedAction, ToggleBreakpointsActivatedAction.ID, ToggleBreakpointsActivatedAction.ACTIVATE_LABEL),
|
||||
this.instantiationService.createInstance(RemoveAllBreakpointsAction, RemoveAllBreakpointsAction.ID, RemoveAllBreakpointsAction.LABEL)
|
||||
];
|
||||
}
|
||||
|
||||
private onBreakpointsChange(): void {
|
||||
this.minimumBodySize = this.maximumBodySize = this.getExpandedBodySize();
|
||||
if (this.tree) {
|
||||
this.tree.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
private getExpandedBodySize(): number {
|
||||
const model = this.debugService.getModel();
|
||||
const length = model.getBreakpoints().length + model.getExceptionBreakpoints().length + model.getFunctionBreakpoints().length;
|
||||
return Math.min(BreakpointsView.MAX_VISIBLE_FILES, length) * 22;
|
||||
}
|
||||
|
||||
public shutdown(): void {
|
||||
this.settings[BreakpointsView.MEMENTO] = !this.isExpanded();
|
||||
super.shutdown();
|
||||
}
|
||||
}
|
||||
@@ -16,14 +16,13 @@ import { isMacintosh } from 'vs/base/common/platform';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { ITree, ITreeOptions } from 'vs/base/parts/tree/browser/tree';
|
||||
import { Tree } from 'vs/base/parts/tree/browser/treeImpl';
|
||||
import { Context as SuggestContext } from 'vs/editor/contrib/suggest/browser/suggest';
|
||||
import { SuggestController } from 'vs/editor/contrib/suggest/browser/suggestController';
|
||||
import { IReadOnlyModel, ICommonCodeEditor } from 'vs/editor/common/editorCommon';
|
||||
import { Context as SuggestContext } from 'vs/editor/contrib/suggest/suggest';
|
||||
import { SuggestController } from 'vs/editor/contrib/suggest/suggestController';
|
||||
import { IReadOnlyModel } from 'vs/editor/common/editorCommon';
|
||||
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import * as modes from 'vs/editor/common/modes';
|
||||
import { editorAction, ServicesAccessor, EditorAction, EditorCommand, CommonEditorRegistry } from 'vs/editor/common/editorCommonExtensions';
|
||||
import { registerEditorAction, ServicesAccessor, EditorAction, EditorCommand, registerEditorCommand } from 'vs/editor/browser/editorExtensions';
|
||||
import { IModelService } from 'vs/editor/common/services/modelService';
|
||||
import { MenuId } from 'vs/platform/actions/common/actions';
|
||||
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
|
||||
@@ -38,11 +37,12 @@ import { ClearReplAction } from 'vs/workbench/parts/debug/browser/debugActions';
|
||||
import { ReplHistory } from 'vs/workbench/parts/debug/common/replHistory';
|
||||
import { Panel } from 'vs/workbench/browser/panel';
|
||||
import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
|
||||
import { IListService } from 'vs/platform/list/browser/listService';
|
||||
import { attachListStyler } from 'vs/platform/theme/common/styler';
|
||||
import { IEditorOptions } from 'vs/editor/common/config/editorOptions';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { clipboard } from 'electron';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { WorkbenchTree, IListService } from 'vs/platform/list/browser/listService';
|
||||
import { memoize } from 'vs/base/common/decorators';
|
||||
|
||||
const $ = dom.$;
|
||||
|
||||
@@ -65,16 +65,16 @@ export interface IPrivateReplService {
|
||||
export class Repl extends Panel implements IPrivateReplService {
|
||||
public _serviceBrand: any;
|
||||
|
||||
private static HALF_WIDTH_TYPICAL = 'n';
|
||||
private static readonly HALF_WIDTH_TYPICAL = 'n';
|
||||
|
||||
private static HISTORY: ReplHistory;
|
||||
private static REFRESH_DELAY = 500; // delay in ms to refresh the repl for new elements to show
|
||||
private static REPL_INPUT_INITIAL_HEIGHT = 19;
|
||||
private static REPL_INPUT_MAX_HEIGHT = 170;
|
||||
private static readonly REFRESH_DELAY = 500; // delay in ms to refresh the repl for new elements to show
|
||||
private static readonly REPL_INPUT_INITIAL_HEIGHT = 19;
|
||||
private static readonly REPL_INPUT_MAX_HEIGHT = 170;
|
||||
|
||||
private tree: ITree;
|
||||
private renderer: ReplExpressionsRenderer;
|
||||
private characterWidthSurveyor: HTMLElement;
|
||||
private container: HTMLElement;
|
||||
private treeContainer: HTMLElement;
|
||||
private replInput: ReplInputEditor;
|
||||
private replInputContainer: HTMLElement;
|
||||
@@ -129,30 +129,20 @@ export class Repl extends Panel implements IPrivateReplService {
|
||||
|
||||
public create(parent: Builder): TPromise<void> {
|
||||
super.create(parent);
|
||||
const container = dom.append(parent.getHTMLElement(), $('.repl'));
|
||||
this.treeContainer = dom.append(container, $('.repl-tree'));
|
||||
this.createReplInput(container);
|
||||
|
||||
this.characterWidthSurveyor = dom.append(container, $('.surveyor'));
|
||||
this.characterWidthSurveyor.textContent = Repl.HALF_WIDTH_TYPICAL;
|
||||
for (let i = 0; i < 10; i++) {
|
||||
this.characterWidthSurveyor.textContent += this.characterWidthSurveyor.textContent;
|
||||
}
|
||||
this.characterWidthSurveyor.style.fontSize = isMacintosh ? '12px' : '14px';
|
||||
this.container = dom.append(parent.getHTMLElement(), $('.repl'));
|
||||
this.treeContainer = dom.append(this.container, $('.repl-tree'));
|
||||
this.createReplInput(this.container);
|
||||
|
||||
this.renderer = this.instantiationService.createInstance(ReplExpressionsRenderer);
|
||||
const controller = this.instantiationService.createInstance(ReplExpressionsController, new ReplExpressionsActionProvider(this.instantiationService), MenuId.DebugConsoleContext);
|
||||
controller.toFocusOnClick = this.replInput;
|
||||
|
||||
this.tree = new Tree(this.treeContainer, {
|
||||
this.tree = new WorkbenchTree(this.treeContainer, {
|
||||
dataSource: new ReplExpressionsDataSource(),
|
||||
renderer: this.renderer,
|
||||
accessibilityProvider: new ReplExpressionsAccessibilityProvider(),
|
||||
controller
|
||||
}, replTreeOptions);
|
||||
|
||||
this.toUnbind.push(attachListStyler(this.tree, this.themeService));
|
||||
this.toUnbind.push(this.listService.register(this.tree));
|
||||
}, replTreeOptions, this.contextKeyService, this.listService, this.themeService);
|
||||
|
||||
if (!Repl.HISTORY) {
|
||||
Repl.HISTORY = new ReplHistory(JSON.parse(this.storageService.get(HISTORY_STORAGE_KEY, StorageScope.WORKSPACE, '[]')));
|
||||
@@ -246,7 +236,7 @@ export class Repl extends Panel implements IPrivateReplService {
|
||||
public layout(dimension: Dimension): void {
|
||||
this.dimension = dimension;
|
||||
if (this.tree) {
|
||||
this.renderer.setWidth(dimension.width - 25, this.characterWidthSurveyor.clientWidth / this.characterWidthSurveyor.textContent.length);
|
||||
this.renderer.setWidth(dimension.width - 25, this.characterWidth);
|
||||
const treeHeight = dimension.height - this.replInputHeight;
|
||||
this.treeContainer.style.height = `${treeHeight}px`;
|
||||
this.tree.layout(treeHeight);
|
||||
@@ -256,6 +246,18 @@ export class Repl extends Panel implements IPrivateReplService {
|
||||
this.replInput.layout({ width: dimension.width - 20, height: this.replInputHeight });
|
||||
}
|
||||
|
||||
@memoize
|
||||
private get characterWidth(): number {
|
||||
const characterWidthSurveyor = dom.append(this.container, $('.surveyor'));
|
||||
characterWidthSurveyor.textContent = Repl.HALF_WIDTH_TYPICAL;
|
||||
for (let i = 0; i < 10; i++) {
|
||||
characterWidthSurveyor.textContent += characterWidthSurveyor.textContent;
|
||||
}
|
||||
characterWidthSurveyor.style.fontSize = isMacintosh ? '12px' : '14px';
|
||||
|
||||
return characterWidthSurveyor.clientWidth / characterWidthSurveyor.textContent.length;
|
||||
}
|
||||
|
||||
public focus(): void {
|
||||
this.replInput.focus();
|
||||
}
|
||||
@@ -312,7 +314,6 @@ export class Repl extends Panel implements IPrivateReplService {
|
||||
}
|
||||
}
|
||||
|
||||
@editorAction
|
||||
class ReplHistoryPreviousAction extends EditorAction {
|
||||
|
||||
constructor() {
|
||||
@@ -332,12 +333,11 @@ class ReplHistoryPreviousAction extends EditorAction {
|
||||
});
|
||||
}
|
||||
|
||||
public run(accessor: ServicesAccessor, editor: ICommonCodeEditor): void | TPromise<void> {
|
||||
public run(accessor: ServicesAccessor, editor: ICodeEditor): void | TPromise<void> {
|
||||
accessor.get(IPrivateReplService).navigateHistory(true);
|
||||
}
|
||||
}
|
||||
|
||||
@editorAction
|
||||
class ReplHistoryNextAction extends EditorAction {
|
||||
|
||||
constructor() {
|
||||
@@ -357,12 +357,11 @@ class ReplHistoryNextAction extends EditorAction {
|
||||
});
|
||||
}
|
||||
|
||||
public run(accessor: ServicesAccessor, editor: ICommonCodeEditor): void | TPromise<void> {
|
||||
public run(accessor: ServicesAccessor, editor: ICodeEditor): void | TPromise<void> {
|
||||
accessor.get(IPrivateReplService).navigateHistory(false);
|
||||
}
|
||||
}
|
||||
|
||||
@editorAction
|
||||
class AcceptReplInputAction extends EditorAction {
|
||||
|
||||
constructor() {
|
||||
@@ -378,25 +377,12 @@ class AcceptReplInputAction extends EditorAction {
|
||||
});
|
||||
}
|
||||
|
||||
public run(accessor: ServicesAccessor, editor: ICommonCodeEditor): void | TPromise<void> {
|
||||
public run(accessor: ServicesAccessor, editor: ICodeEditor): void | TPromise<void> {
|
||||
SuggestController.get(editor).acceptSelectedSuggestion();
|
||||
accessor.get(IPrivateReplService).acceptReplInput();
|
||||
}
|
||||
}
|
||||
|
||||
const SuggestCommand = EditorCommand.bindToContribution<SuggestController>(SuggestController.get);
|
||||
CommonEditorRegistry.registerEditorCommand(new SuggestCommand({
|
||||
id: 'repl.action.acceptSuggestion',
|
||||
precondition: ContextKeyExpr.and(debug.CONTEXT_IN_DEBUG_REPL, SuggestContext.Visible),
|
||||
handler: x => x.acceptSelectedSuggestion(),
|
||||
kbOpts: {
|
||||
weight: 50,
|
||||
kbExpr: EditorContextKeys.textFocus,
|
||||
primary: KeyCode.RightArrow
|
||||
}
|
||||
}));
|
||||
|
||||
@editorAction
|
||||
export class ReplCopyAllAction extends EditorAction {
|
||||
|
||||
constructor() {
|
||||
@@ -408,7 +394,24 @@ export class ReplCopyAllAction extends EditorAction {
|
||||
});
|
||||
}
|
||||
|
||||
public run(accessor: ServicesAccessor, editor: ICommonCodeEditor): void | TPromise<void> {
|
||||
public run(accessor: ServicesAccessor, editor: ICodeEditor): void | TPromise<void> {
|
||||
clipboard.writeText(accessor.get(IPrivateReplService).getVisibleContent());
|
||||
}
|
||||
}
|
||||
|
||||
registerEditorAction(ReplHistoryPreviousAction);
|
||||
registerEditorAction(ReplHistoryNextAction);
|
||||
registerEditorAction(AcceptReplInputAction);
|
||||
registerEditorAction(ReplCopyAllAction);
|
||||
|
||||
const SuggestCommand = EditorCommand.bindToContribution<SuggestController>(SuggestController.get);
|
||||
registerEditorCommand(new SuggestCommand({
|
||||
id: 'repl.action.acceptSuggestion',
|
||||
precondition: ContextKeyExpr.and(debug.CONTEXT_IN_DEBUG_REPL, SuggestContext.Visible),
|
||||
handler: x => x.acceptSelectedSuggestion(),
|
||||
kbOpts: {
|
||||
weight: 50,
|
||||
kbExpr: EditorContextKeys.textFocus,
|
||||
primary: KeyCode.RightArrow
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -4,9 +4,8 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IEditorOptions } from 'vs/editor/common/config/editorOptions';
|
||||
import { EditorAction, CommonEditorRegistry } from 'vs/editor/common/editorCommonExtensions';
|
||||
import { ICodeEditorService } from 'vs/editor/common/services/codeEditorService';
|
||||
import { IEditorContributionCtor } from 'vs/editor/browser/editorBrowser';
|
||||
import { EditorAction, EditorExtensionsRegistry, IEditorContributionCtor } from 'vs/editor/browser/editorExtensions';
|
||||
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
|
||||
import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
@@ -15,9 +14,9 @@ import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
// Allowed Editor Contributions:
|
||||
import { MenuPreventer } from 'vs/workbench/parts/codeEditor/electron-browser/menuPreventer';
|
||||
import { SelectionClipboard } from 'vs/workbench/parts/codeEditor/electron-browser/selectionClipboard';
|
||||
import { ContextMenuController } from 'vs/editor/contrib/contextmenu/browser/contextmenu';
|
||||
import { SuggestController } from 'vs/editor/contrib/suggest/browser/suggestController';
|
||||
import { SnippetController2 } from 'vs/editor/contrib/snippet/browser/snippetController2';
|
||||
import { ContextMenuController } from 'vs/editor/contrib/contextmenu/contextmenu';
|
||||
import { SuggestController } from 'vs/editor/contrib/suggest/suggestController';
|
||||
import { SnippetController2 } from 'vs/editor/contrib/snippet/snippetController2';
|
||||
import { TabCompletionController } from 'vs/workbench/parts/snippets/electron-browser/tabCompletion';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
|
||||
@@ -46,6 +45,6 @@ export class ReplInputEditor extends CodeEditorWidget {
|
||||
}
|
||||
|
||||
protected _getActions(): EditorAction[] {
|
||||
return CommonEditorRegistry.getEditorActions();
|
||||
return EditorExtensionsRegistry.getEditorActions();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ import { ITree, IAccessibilityProvider, ContextMenuEvent, IDataSource, IRenderer
|
||||
import { ICancelableEvent } from 'vs/base/parts/tree/browser/treeDefaults';
|
||||
import { IExpressionContainer, IExpression, IReplElementSource } from 'vs/workbench/parts/debug/common/debug';
|
||||
import { Model, RawObjectReplElement, Expression, SimpleReplElement, Variable } from 'vs/workbench/parts/debug/common/debugModel';
|
||||
import { renderVariable, renderExpressionValue, IVariableTemplateData, BaseDebugController } from 'vs/workbench/parts/debug/electron-browser/debugViewer';
|
||||
import { renderVariable, renderExpressionValue, IVariableTemplateData, BaseDebugController } from 'vs/workbench/parts/debug/electron-browser/baseDebugView';
|
||||
import { ClearReplAction } from 'vs/workbench/parts/debug/browser/debugActions';
|
||||
import { CopyAction, CopyAllAction } from 'vs/workbench/parts/debug/electron-browser/electronDebugActions';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
@@ -41,7 +41,7 @@ export class ReplExpressionsDataSource implements IDataSource {
|
||||
return TPromise.as(element.getReplElements());
|
||||
}
|
||||
if (element instanceof RawObjectReplElement) {
|
||||
return TPromise.as(element.getChildren());
|
||||
return element.getChildren();
|
||||
}
|
||||
if (element instanceof SimpleReplElement) {
|
||||
return TPromise.as(null);
|
||||
@@ -80,12 +80,12 @@ interface IRawObjectReplTemplateData {
|
||||
|
||||
export class ReplExpressionsRenderer implements IRenderer {
|
||||
|
||||
private static VARIABLE_TEMPLATE_ID = 'variable';
|
||||
private static EXPRESSION_TEMPLATE_ID = 'expressionRepl';
|
||||
private static SIMPLE_REPL_ELEMENT_TEMPLATE_ID = 'simpleReplElement';
|
||||
private static RAW_OBJECT_REPL_ELEMENT_TEMPLATE_ID = 'rawObject';
|
||||
private static readonly VARIABLE_TEMPLATE_ID = 'variable';
|
||||
private static readonly EXPRESSION_TEMPLATE_ID = 'expressionRepl';
|
||||
private static readonly SIMPLE_REPL_ELEMENT_TEMPLATE_ID = 'simpleReplElement';
|
||||
private static readonly RAW_OBJECT_REPL_ELEMENT_TEMPLATE_ID = 'rawObject';
|
||||
|
||||
private static LINE_HEIGHT_PX = 18;
|
||||
private static readonly LINE_HEIGHT_PX = 18;
|
||||
|
||||
private width: number;
|
||||
private characterWidth: number;
|
||||
@@ -298,34 +298,46 @@ export class ReplExpressionsRenderer implements IRenderer {
|
||||
if (text.charCodeAt(i) === 27) {
|
||||
let index = i;
|
||||
let chr = (++index < len ? text.charAt(index) : null);
|
||||
let codes = [];
|
||||
if (chr && chr === '[') {
|
||||
let code: string = null;
|
||||
chr = (++index < len ? text.charAt(index) : null);
|
||||
|
||||
if (chr && chr >= '0' && chr <= '9') {
|
||||
code = chr;
|
||||
while (chr !== 'm' && codes.length <= 7) {
|
||||
chr = (++index < len ? text.charAt(index) : null);
|
||||
}
|
||||
|
||||
if (chr && chr >= '0' && chr <= '9') {
|
||||
code += chr;
|
||||
chr = (++index < len ? text.charAt(index) : null);
|
||||
}
|
||||
if (chr && chr >= '0' && chr <= '9') {
|
||||
code = chr;
|
||||
chr = (++index < len ? text.charAt(index) : null);
|
||||
}
|
||||
|
||||
if (code === null) {
|
||||
code = '0';
|
||||
if (chr && chr >= '0' && chr <= '9') {
|
||||
code += chr;
|
||||
chr = (++index < len ? text.charAt(index) : null);
|
||||
}
|
||||
|
||||
if (code === null) {
|
||||
code = '0';
|
||||
}
|
||||
|
||||
codes.push(code);
|
||||
}
|
||||
|
||||
if (chr === 'm') { // set text color/mode.
|
||||
|
||||
code = null;
|
||||
// only respect text-foreground ranges and ignore the values for "black" & "white" because those
|
||||
// only make sense in combination with text-background ranges which we currently not support
|
||||
let parsedMode = parseInt(code, 10);
|
||||
let token = document.createElement('span');
|
||||
if ((parsedMode >= 30 && parsedMode <= 37) || (parsedMode >= 90 && parsedMode <= 97)) {
|
||||
token.className = 'code' + parsedMode;
|
||||
} else if (parsedMode === 1) {
|
||||
token.className = 'code-bold';
|
||||
token.className = '';
|
||||
while (codes.length > 0) {
|
||||
code = codes.pop();
|
||||
let parsedMode = parseInt(code, 10);
|
||||
if (token.className.length > 0) {
|
||||
token.className += ' ';
|
||||
}
|
||||
if ((parsedMode >= 30 && parsedMode <= 37) || (parsedMode >= 90 && parsedMode <= 97)) {
|
||||
token.className += 'code' + parsedMode;
|
||||
} else if (parsedMode === 1) {
|
||||
token.className += 'code-bold';
|
||||
}
|
||||
}
|
||||
|
||||
// we need a tokens container now
|
||||
|
||||
@@ -34,7 +34,6 @@ export const STATUS_BAR_DEBUGGING_BORDER = registerColor('statusBar.debuggingBor
|
||||
}, localize('statusBarDebuggingBorder', "Status bar border color separating to the sidebar and editor when a program is being debugged. The status bar is shown in the bottom of the window"));
|
||||
|
||||
export class StatusBarColorProvider extends Themable implements IWorkbenchContribution {
|
||||
private static ID = 'debug.statusbarColorProvider';
|
||||
|
||||
constructor(
|
||||
@IThemeService themeService: IThemeService,
|
||||
@@ -103,10 +102,6 @@ export class StatusBarColorProvider extends Themable implements IWorkbenchContri
|
||||
|
||||
return process && process.configuration && process.configuration.noDebug;
|
||||
}
|
||||
|
||||
public getId(): string {
|
||||
return StatusBarColorProvider.ID;
|
||||
}
|
||||
}
|
||||
|
||||
registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
|
||||
|
||||
@@ -11,7 +11,7 @@ import { ITerminalService, ITerminalInstance, ITerminalConfiguration } from 'vs/
|
||||
import { ITerminalService as IExternalTerminalService } from 'vs/workbench/parts/execution/common/execution';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
|
||||
const enum ShellType { cmd, powershell, bash };
|
||||
const enum ShellType { cmd, powershell, bash }
|
||||
|
||||
export class TerminalSupport {
|
||||
|
||||
@@ -61,7 +61,7 @@ export class TerminalSupport {
|
||||
|
||||
// get the shell configuration for the current platform
|
||||
let shell: string;
|
||||
const shell_config = (<ITerminalConfiguration>configurationService.getConfiguration<any>().terminal.integrated).shell;
|
||||
const shell_config = (<ITerminalConfiguration>configurationService.getValue<any>().terminal.integrated).shell;
|
||||
if (platform.isWindows) {
|
||||
shell = shell_config.windows;
|
||||
shellType = ShellType.cmd;
|
||||
@@ -102,7 +102,12 @@ export class TerminalSupport {
|
||||
}
|
||||
if (args.env) {
|
||||
for (let key in args.env) {
|
||||
command += `$env:${key}='${args.env[key]}'; `;
|
||||
const value = args.env[key];
|
||||
if (value === null) {
|
||||
command += `Remove-Item env:${key}; `;
|
||||
} else {
|
||||
command += `\${env:${key}}='${value}'; `;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (args.args && args.args.length > 0) {
|
||||
@@ -127,7 +132,12 @@ export class TerminalSupport {
|
||||
if (args.env) {
|
||||
command += 'cmd /C "';
|
||||
for (let key in args.env) {
|
||||
command += `set "${key}=${args.env[key]}" && `;
|
||||
const value = args.env[key];
|
||||
if (value === null) {
|
||||
command += `set "${key}=" && `;
|
||||
} else {
|
||||
command += `set "${key}=${args.env[key]}" && `;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (let a of args.args) {
|
||||
@@ -151,7 +161,12 @@ export class TerminalSupport {
|
||||
if (args.env) {
|
||||
command += 'env';
|
||||
for (let key in args.env) {
|
||||
command += ` "${key}=${args.env[key]}"`;
|
||||
const value = args.env[key];
|
||||
if (value === null) {
|
||||
command += ` -u "${key}"`;
|
||||
} else {
|
||||
command += ` "${key}=${value}"`;
|
||||
}
|
||||
}
|
||||
command += ' ';
|
||||
}
|
||||
|
||||
334
src/vs/workbench/parts/debug/electron-browser/variablesView.ts
Normal file
334
src/vs/workbench/parts/debug/electron-browser/variablesView.ts
Normal file
@@ -0,0 +1,334 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { RunOnceScheduler, sequence } from 'vs/base/common/async';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import * as errors from 'vs/base/common/errors';
|
||||
import { prepareActions } from 'vs/workbench/browser/actions';
|
||||
import { IHighlightEvent, IActionProvider, ITree, IDataSource, IRenderer, IAccessibilityProvider } from 'vs/base/parts/tree/browser/tree';
|
||||
import { CollapseAction } from 'vs/workbench/browser/viewlet';
|
||||
import { TreeViewsViewletPanel, IViewletViewOptions, IViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet';
|
||||
import { IDebugService, State, CONTEXT_VARIABLES_FOCUSED, IExpression } from 'vs/workbench/parts/debug/common/debug';
|
||||
import { Variable, Scope } from 'vs/workbench/parts/debug/common/debugModel';
|
||||
import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { MenuId } from 'vs/platform/actions/common/actions';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { once } from 'vs/base/common/event';
|
||||
import { twistiePixels, renderViewTree, IVariableTemplateData, BaseDebugController, renderRenameBox, renderVariable } from 'vs/workbench/parts/debug/electron-browser/baseDebugView';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { IAction, IActionItem } from 'vs/base/common/actions';
|
||||
import { SetValueAction, AddToWatchExpressionsAction } from 'vs/workbench/parts/debug/browser/debugActions';
|
||||
import { CopyValueAction } from 'vs/workbench/parts/debug/electron-browser/electronDebugActions';
|
||||
import { Separator } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { ViewModel } from 'vs/workbench/parts/debug/common/debugViewModel';
|
||||
import { equalsIgnoreCase } from 'vs/base/common/strings';
|
||||
import { IMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||
import { WorkbenchTree, IListService } from 'vs/platform/list/browser/listService';
|
||||
|
||||
const $ = dom.$;
|
||||
|
||||
export class VariablesView extends TreeViewsViewletPanel {
|
||||
|
||||
private static readonly MEMENTO = 'variablesview.memento';
|
||||
private onFocusStackFrameScheduler: RunOnceScheduler;
|
||||
private settings: any;
|
||||
private expandedElements: any[];
|
||||
private needsRefresh: boolean;
|
||||
|
||||
constructor(
|
||||
options: IViewletViewOptions,
|
||||
@IContextMenuService contextMenuService: IContextMenuService,
|
||||
@IDebugService private debugService: IDebugService,
|
||||
@IKeybindingService keybindingService: IKeybindingService,
|
||||
@IInstantiationService private instantiationService: IInstantiationService,
|
||||
@IListService private listService: IListService,
|
||||
@IContextKeyService private contextKeyService: IContextKeyService,
|
||||
@IThemeService private themeService: IThemeService
|
||||
) {
|
||||
super({ ...(options as IViewOptions), ariaHeaderLabel: nls.localize('variablesSection', "Variables Section") }, keybindingService, contextMenuService);
|
||||
|
||||
this.settings = options.viewletSettings;
|
||||
this.expandedElements = [];
|
||||
// Use scheduler to prevent unnecessary flashing
|
||||
this.onFocusStackFrameScheduler = new RunOnceScheduler(() => {
|
||||
// Remember expanded elements when there are some (otherwise don't override/erase the previous ones)
|
||||
const expanded = this.tree.getExpandedElements();
|
||||
if (expanded.length > 0) {
|
||||
this.expandedElements = expanded;
|
||||
}
|
||||
|
||||
// Always clear tree highlight to avoid ending up in a broken state #12203
|
||||
this.tree.clearHighlight();
|
||||
this.needsRefresh = false;
|
||||
this.tree.refresh().then(() => {
|
||||
const stackFrame = this.debugService.getViewModel().focusedStackFrame;
|
||||
return sequence(this.expandedElements.map(e => () => this.tree.expand(e))).then(() => {
|
||||
// If there is no preserved expansion state simply expand the first scope
|
||||
if (stackFrame && this.tree.getExpandedElements().length === 0) {
|
||||
return stackFrame.getScopes().then(scopes => {
|
||||
if (scopes.length > 0 && !scopes[0].expensive) {
|
||||
return this.tree.expand(scopes[0]);
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
}).done(null, errors.onUnexpectedError);
|
||||
}, 400);
|
||||
}
|
||||
|
||||
public renderBody(container: HTMLElement): void {
|
||||
dom.addClass(container, 'debug-variables');
|
||||
this.treeContainer = renderViewTree(container);
|
||||
|
||||
this.tree = new WorkbenchTree(this.treeContainer, {
|
||||
dataSource: new VariablesDataSource(),
|
||||
renderer: this.instantiationService.createInstance(VariablesRenderer),
|
||||
accessibilityProvider: new VariablesAccessibilityProvider(),
|
||||
controller: this.instantiationService.createInstance(VariablesController, new VariablesActionProvider(this.debugService, this.keybindingService), MenuId.DebugVariablesContext)
|
||||
}, {
|
||||
ariaLabel: nls.localize('variablesAriaTreeLabel', "Debug Variables"),
|
||||
twistiePixels,
|
||||
keyboardSupport: false
|
||||
}, this.contextKeyService, this.listService, this.themeService);
|
||||
|
||||
CONTEXT_VARIABLES_FOCUSED.bindTo(this.tree.contextKeyService);
|
||||
|
||||
const viewModel = this.debugService.getViewModel();
|
||||
|
||||
this.tree.setInput(viewModel);
|
||||
|
||||
const collapseAction = new CollapseAction(this.tree, false, 'explorer-action collapse-explorer');
|
||||
this.toolbar.setActions(prepareActions([collapseAction]))();
|
||||
|
||||
this.disposables.push(viewModel.onDidFocusStackFrame(sf => {
|
||||
if (!this.isVisible() || !this.isExpanded()) {
|
||||
this.needsRefresh = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Refresh the tree immediately if it is not visible.
|
||||
// Otherwise postpone the refresh until user stops stepping.
|
||||
if (!this.tree.getContentHeight() || sf.explicit) {
|
||||
this.onFocusStackFrameScheduler.schedule(0);
|
||||
} else {
|
||||
this.onFocusStackFrameScheduler.schedule();
|
||||
}
|
||||
}));
|
||||
this.disposables.push(this.debugService.onDidChangeState(state => {
|
||||
collapseAction.enabled = state === State.Running || state === State.Stopped;
|
||||
}));
|
||||
|
||||
this.disposables.push(this.debugService.getViewModel().onDidSelectExpression(expression => {
|
||||
if (!expression || !(expression instanceof Variable)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.tree.refresh(expression, false).then(() => {
|
||||
this.tree.setHighlight(expression);
|
||||
once(this.tree.onDidChangeHighlight)((e: IHighlightEvent) => {
|
||||
if (!e.highlight) {
|
||||
this.debugService.getViewModel().setSelectedExpression(null);
|
||||
}
|
||||
});
|
||||
}).done(null, errors.onUnexpectedError);
|
||||
}));
|
||||
}
|
||||
|
||||
public setExpanded(expanded: boolean): void {
|
||||
super.setExpanded(expanded);
|
||||
if (expanded && this.needsRefresh) {
|
||||
this.onFocusStackFrameScheduler.schedule();
|
||||
}
|
||||
}
|
||||
|
||||
public setVisible(visible: boolean): TPromise<void> {
|
||||
return super.setVisible(visible).then(() => {
|
||||
if (visible && this.needsRefresh) {
|
||||
this.onFocusStackFrameScheduler.schedule();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public shutdown(): void {
|
||||
this.settings[VariablesView.MEMENTO] = !this.isExpanded();
|
||||
super.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
class VariablesActionProvider implements IActionProvider {
|
||||
|
||||
constructor(private debugService: IDebugService, private keybindingService: IKeybindingService) {
|
||||
// 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(new SetValueAction(SetValueAction.ID, SetValueAction.LABEL, variable, this.debugService, this.keybindingService));
|
||||
actions.push(new CopyValueAction(CopyValueAction.ID, CopyValueAction.LABEL, variable, this.debugService));
|
||||
actions.push(new Separator());
|
||||
actions.push(new AddToWatchExpressionsAction(AddToWatchExpressionsAction.ID, AddToWatchExpressionsAction.LABEL, variable, this.debugService, this.keybindingService));
|
||||
|
||||
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 class VariablesRenderer implements IRenderer {
|
||||
|
||||
private static readonly SCOPE_TEMPLATE_ID = 'scope';
|
||||
private static readonly VARIABLE_TEMPLATE_ID = 'variable';
|
||||
|
||||
constructor(
|
||||
@IDebugService private debugService: 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
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
const process = this.debugService.getViewModel().focusedProcess;
|
||||
if (element instanceof Variable && event.detail === 2 && process && process.session.capabilities.supportsSetVariable) {
|
||||
const expression = <IExpression>element;
|
||||
this.debugService.getViewModel().setSelectedExpression(expression);
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.onLeftClick(tree, element, event);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,395 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { RunOnceScheduler } from 'vs/base/common/async';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import * as errors from 'vs/base/common/errors';
|
||||
import { prepareActions } from 'vs/workbench/browser/actions';
|
||||
import { IHighlightEvent, IActionProvider, ITree, IDataSource, IRenderer, IAccessibilityProvider, IDragAndDropData, IDragOverReaction, DRAG_OVER_REJECT } from 'vs/base/parts/tree/browser/tree';
|
||||
import { CollapseAction } from 'vs/workbench/browser/viewlet';
|
||||
import { TreeViewsViewletPanel, IViewletViewOptions, IViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet';
|
||||
import { IDebugService, IExpression, CONTEXT_WATCH_EXPRESSIONS_FOCUSED } from 'vs/workbench/parts/debug/common/debug';
|
||||
import { Expression, Variable, Model } from 'vs/workbench/parts/debug/common/debugModel';
|
||||
import { AddWatchExpressionAction, RemoveAllWatchExpressionsAction, EditWatchExpressionAction, RemoveWatchExpressionAction } from 'vs/workbench/parts/debug/browser/debugActions';
|
||||
import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { MenuId } from 'vs/platform/actions/common/actions';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { once } from 'vs/base/common/event';
|
||||
import { IAction, IActionItem } from 'vs/base/common/actions';
|
||||
import { CopyValueAction } from 'vs/workbench/parts/debug/electron-browser/electronDebugActions';
|
||||
import { Separator } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { equalsIgnoreCase } from 'vs/base/common/strings';
|
||||
import { IMouseEvent, DragMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||
import { DefaultDragAndDrop } from 'vs/base/parts/tree/browser/treeDefaults';
|
||||
import { IVariableTemplateData, renderVariable, renderRenameBox, renderExpressionValue, BaseDebugController, twistiePixels, renderViewTree } from 'vs/workbench/parts/debug/electron-browser/baseDebugView';
|
||||
import { WorkbenchTree, IListService } from 'vs/platform/list/browser/listService';
|
||||
|
||||
const $ = dom.$;
|
||||
const MAX_VALUE_RENDER_LENGTH_IN_VIEWLET = 1024;
|
||||
|
||||
export class WatchExpressionsView extends TreeViewsViewletPanel {
|
||||
|
||||
private static readonly MEMENTO = 'watchexpressionsview.memento';
|
||||
private onWatchExpressionsUpdatedScheduler: RunOnceScheduler;
|
||||
private toReveal: IExpression;
|
||||
private settings: any;
|
||||
private needsRefresh: boolean;
|
||||
|
||||
constructor(
|
||||
options: IViewletViewOptions,
|
||||
@IContextMenuService contextMenuService: IContextMenuService,
|
||||
@IDebugService private debugService: IDebugService,
|
||||
@IKeybindingService keybindingService: IKeybindingService,
|
||||
@IContextKeyService private contextKeyService: IContextKeyService,
|
||||
@IListService private listService: IListService,
|
||||
@IInstantiationService private instantiationService: IInstantiationService,
|
||||
@IThemeService private themeService: IThemeService
|
||||
) {
|
||||
super({ ...(options as IViewOptions), ariaHeaderLabel: nls.localize('expressionsSection', "Expressions Section") }, keybindingService, contextMenuService);
|
||||
this.settings = options.viewletSettings;
|
||||
|
||||
this.disposables.push(this.debugService.getModel().onDidChangeWatchExpressions(we => {
|
||||
// only expand when a new watch expression is added.
|
||||
if (we instanceof Expression) {
|
||||
this.setExpanded(true);
|
||||
}
|
||||
}));
|
||||
|
||||
this.onWatchExpressionsUpdatedScheduler = new RunOnceScheduler(() => {
|
||||
this.needsRefresh = false;
|
||||
this.tree.refresh().done(() => {
|
||||
return this.toReveal instanceof Expression ? this.tree.reveal(this.toReveal) : TPromise.as(true);
|
||||
}, errors.onUnexpectedError);
|
||||
}, 50);
|
||||
}
|
||||
|
||||
public renderBody(container: HTMLElement): void {
|
||||
dom.addClass(container, 'debug-watch');
|
||||
this.treeContainer = renderViewTree(container);
|
||||
|
||||
const actionProvider = new WatchExpressionsActionProvider(this.debugService, this.keybindingService);
|
||||
this.tree = new WorkbenchTree(this.treeContainer, {
|
||||
dataSource: new WatchExpressionsDataSource(),
|
||||
renderer: this.instantiationService.createInstance(WatchExpressionsRenderer),
|
||||
accessibilityProvider: new WatchExpressionsAccessibilityProvider(),
|
||||
controller: this.instantiationService.createInstance(WatchExpressionsController, actionProvider, MenuId.DebugWatchContext),
|
||||
dnd: new WatchExpressionsDragAndDrop(this.debugService)
|
||||
}, {
|
||||
ariaLabel: nls.localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'watchAriaTreeLabel' }, "Debug Watch Expressions"),
|
||||
twistiePixels,
|
||||
keyboardSupport: false
|
||||
}, this.contextKeyService, this.listService, this.themeService);
|
||||
|
||||
CONTEXT_WATCH_EXPRESSIONS_FOCUSED.bindTo(this.tree.contextKeyService);
|
||||
|
||||
this.tree.setInput(this.debugService.getModel());
|
||||
|
||||
const addWatchExpressionAction = new AddWatchExpressionAction(AddWatchExpressionAction.ID, AddWatchExpressionAction.LABEL, this.debugService, this.keybindingService);
|
||||
const collapseAction = new CollapseAction(this.tree, true, 'explorer-action collapse-explorer');
|
||||
const removeAllWatchExpressionsAction = new RemoveAllWatchExpressionsAction(RemoveAllWatchExpressionsAction.ID, RemoveAllWatchExpressionsAction.LABEL, this.debugService, this.keybindingService);
|
||||
this.toolbar.setActions(prepareActions([addWatchExpressionAction, collapseAction, removeAllWatchExpressionsAction]))();
|
||||
|
||||
this.disposables.push(this.debugService.getModel().onDidChangeWatchExpressions(we => {
|
||||
if (!this.isExpanded() || !this.isVisible()) {
|
||||
this.needsRefresh = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.onWatchExpressionsUpdatedScheduler.isScheduled()) {
|
||||
this.onWatchExpressionsUpdatedScheduler.schedule();
|
||||
}
|
||||
this.toReveal = we;
|
||||
}));
|
||||
|
||||
this.disposables.push(this.debugService.getViewModel().onDidSelectExpression(expression => {
|
||||
if (!expression || !(expression instanceof Expression)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.tree.refresh(expression, false).then(() => {
|
||||
this.tree.setHighlight(expression);
|
||||
once(this.tree.onDidChangeHighlight)((e: IHighlightEvent) => {
|
||||
if (!e.highlight) {
|
||||
this.debugService.getViewModel().setSelectedExpression(null);
|
||||
}
|
||||
});
|
||||
}).done(null, errors.onUnexpectedError);
|
||||
}));
|
||||
}
|
||||
|
||||
public setExpanded(expanded: boolean): void {
|
||||
super.setExpanded(expanded);
|
||||
if (expanded && this.needsRefresh) {
|
||||
this.onWatchExpressionsUpdatedScheduler.schedule();
|
||||
}
|
||||
}
|
||||
|
||||
public setVisible(visible: boolean): TPromise<void> {
|
||||
return super.setVisible(visible).then(() => {
|
||||
if (visible && this.needsRefresh) {
|
||||
this.onWatchExpressionsUpdatedScheduler.schedule();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public shutdown(): void {
|
||||
this.settings[WatchExpressionsView.MEMENTO] = !this.isExpanded();
|
||||
super.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class WatchExpressionsActionProvider implements IActionProvider {
|
||||
|
||||
constructor(private debugService: IDebugService, private keybindingService: IKeybindingService) {
|
||||
// noop
|
||||
}
|
||||
|
||||
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(new AddWatchExpressionAction(AddWatchExpressionAction.ID, AddWatchExpressionAction.LABEL, this.debugService, this.keybindingService));
|
||||
actions.push(new EditWatchExpressionAction(EditWatchExpressionAction.ID, EditWatchExpressionAction.LABEL, this.debugService, this.keybindingService));
|
||||
if (!expression.hasChildren) {
|
||||
actions.push(new CopyValueAction(CopyValueAction.ID, CopyValueAction.LABEL, expression.value, this.debugService));
|
||||
}
|
||||
actions.push(new Separator());
|
||||
|
||||
actions.push(new RemoveWatchExpressionAction(RemoveWatchExpressionAction.ID, RemoveWatchExpressionAction.LABEL, this.debugService, this.keybindingService));
|
||||
actions.push(new RemoveAllWatchExpressionsAction(RemoveAllWatchExpressionsAction.ID, RemoveAllWatchExpressionsAction.LABEL, this.debugService, this.keybindingService));
|
||||
} else {
|
||||
actions.push(new AddWatchExpressionAction(AddWatchExpressionAction.ID, AddWatchExpressionAction.LABEL, this.debugService, this.keybindingService));
|
||||
if (element instanceof Variable) {
|
||||
const variable = <Variable>element;
|
||||
if (!variable.hasChildren) {
|
||||
actions.push(new CopyValueAction(CopyValueAction.ID, CopyValueAction.LABEL, variable.value, this.debugService));
|
||||
}
|
||||
actions.push(new Separator());
|
||||
}
|
||||
actions.push(new RemoveAllWatchExpressionsAction(RemoveAllWatchExpressionsAction.ID, RemoveAllWatchExpressionsAction.LABEL, this.debugService, this.keybindingService));
|
||||
}
|
||||
|
||||
return TPromise.as(actions);
|
||||
}
|
||||
|
||||
public getActionItem(tree: ITree, element: any, action: IAction): IActionItem {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
class WatchExpressionsRenderer implements IRenderer {
|
||||
|
||||
private static readonly WATCH_EXPRESSION_TEMPLATE_ID = 'watchExpression';
|
||||
private static readonly VARIABLE_TEMPLATE_ID = 'variables';
|
||||
private toDispose: IDisposable[];
|
||||
|
||||
constructor(
|
||||
@IDebugService private debugService: IDebugService,
|
||||
@IContextViewService private contextViewService: IContextViewService,
|
||||
@IThemeService private themeService: IThemeService
|
||||
) {
|
||||
this.toDispose = [];
|
||||
}
|
||||
|
||||
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: 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,
|
||||
colorize: 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 = dispose(this.toDispose);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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 = <IExpression>element;
|
||||
this.debugService.getViewModel().setSelectedExpression(expression);
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.onLeftClick(tree, element, event);
|
||||
}
|
||||
}
|
||||
|
||||
class WatchExpressionsDragAndDrop extends DefaultDragAndDrop {
|
||||
|
||||
constructor(private debugService: 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user