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:
Karl Burtram
2018-01-28 23:37:17 -08:00
committed by GitHub
parent 9a1ac20710
commit 251ae01c3e
8009 changed files with 93378 additions and 35634 deletions

View 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;
}
}

View 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);
}

View 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);
}

View File

@@ -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"),

View File

@@ -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();
}

View File

@@ -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));
});
}
}

View File

@@ -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);

View File

@@ -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;

View File

@@ -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

View File

@@ -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();
}
}

View File

@@ -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
}
}));

View File

@@ -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();
}
}

View File

@@ -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

View File

@@ -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) => {

View File

@@ -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 += ' ';
}

View 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);
}
}

View File

@@ -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);
}
}
}