Merge VS Code 1.21 source code (#1067)

* Initial VS Code 1.21 file copy with patches

* A few more merges

* Post npm install

* Fix batch of build breaks

* Fix more build breaks

* Fix more build errors

* Fix more build breaks

* Runtime fixes 1

* Get connection dialog working with some todos

* Fix a few packaging issues

* Copy several node_modules to package build to fix loader issues

* Fix breaks from master

* A few more fixes

* Make tests pass

* First pass of license header updates

* Second pass of license header updates

* Fix restore dialog issues

* Remove add additional themes menu items

* fix select box issues where the list doesn't show up

* formatting

* Fix editor dispose issue

* Copy over node modules to correct location on all platforms
This commit is contained in:
Karl Burtram
2018-04-04 15:27:51 -07:00
committed by GitHub
parent 5fba3e31b4
commit dafb780987
9412 changed files with 141255 additions and 98813 deletions

View File

@@ -1,248 +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 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

@@ -1,518 +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 * 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

@@ -15,10 +15,8 @@ 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 { BaseDebugController, twistiePixels, renderViewTree } from 'vs/workbench/parts/debug/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';
@@ -26,8 +24,9 @@ import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/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';
import { TreeResourceNavigator, WorkbenchTree } from 'vs/platform/list/browser/listService';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
const $ = dom.$;
@@ -39,18 +38,19 @@ export class CallStackView extends TreeViewsViewletPanel {
private onCallStackChangeScheduler: RunOnceScheduler;
private settings: any;
private needsRefresh: boolean;
private ignoreSelectionChangedEvent: boolean;
private treeContainer: HTMLElement;
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
@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
@IConfigurationService configurationService: IConfigurationService,
) {
super({ ...(options as IViewOptions), ariaHeaderLabel: nls.localize('callstackSection', "Call Stack Section") }, keybindingService, contextMenuService);
super({ ...(options as IViewOptions), ariaHeaderLabel: nls.localize('callstackSection', "Call Stack Section") }, keybindingService, contextMenuService, configurationService);
this.settings = options.viewletSettings;
// Create scheduler to prevent unnecessary flashing of tree when reacting to changes
@@ -95,26 +95,42 @@ export class CallStackView extends TreeViewsViewletPanel {
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);
const controller = this.instantiationService.createInstance(CallStackController, actionProvider, MenuId.DebugCallStackContext, {});
this.tree = new WorkbenchTree(this.treeContainer, {
this.tree = this.instantiationService.createInstance(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);
twistiePixels
});
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);
const callstackNavigator = new TreeResourceNavigator(this.tree);
this.disposables.push(callstackNavigator);
this.disposables.push(callstackNavigator.openResource(e => {
if (this.ignoreSelectionChangedEvent) {
return;
}
const element = e.element;
if (element instanceof StackFrame) {
this.debugService.focusStackFrame(element, element.thread, element.thread.process, true);
element.openInEditor(this.editorService, e.editorOptions.preserveFocus, e.sideBySide, e.editorOptions.pinned).done(undefined, errors.onUnexpectedError);
}
if (element instanceof Thread) {
this.debugService.focusStackFrame(undefined, element, element.process, true);
}
if (element instanceof Process) {
this.debugService.focusStackFrame(undefined, undefined, element, true);
}
if (element instanceof ThreadAndProcessIds) {
const process = this.debugService.getModel().getProcesses().filter(p => p.getId() === element.processId).pop();
const thread = process && process.getThread(element.threadId);
if (thread) {
(<Thread>thread).fetchCallStack()
.done(() => this.tree.refresh(), errors.onUnexpectedError);
}
}
}));
@@ -129,8 +145,14 @@ export class CallStackView extends TreeViewsViewletPanel {
this.onCallStackChangeScheduler.schedule();
}
}));
this.disposables.push(this.debugService.getViewModel().onDidFocusStackFrame(() =>
this.updateTreeSelection().done(undefined, errors.onUnexpectedError)));
this.disposables.push(this.debugService.getViewModel().onDidFocusStackFrame(() => {
if (!this.isVisible) {
this.needsRefresh = true;
return;
}
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) {
@@ -138,6 +160,13 @@ export class CallStackView extends TreeViewsViewletPanel {
}
}
layoutBody(size: number): void {
if (this.treeContainer) {
this.treeContainer.style.height = size + 'px';
}
super.layoutBody(size);
}
private updateTreeSelection(): TPromise<void> {
if (!this.tree.getInput()) {
// Tree not initialized yet
@@ -147,13 +176,22 @@ export class CallStackView extends TreeViewsViewletPanel {
const stackFrame = this.debugService.getViewModel().focusedStackFrame;
const thread = this.debugService.getViewModel().focusedThread;
const process = this.debugService.getViewModel().focusedProcess;
const updateSelection = (element: IStackFrame | IProcess) => {
this.ignoreSelectionChangedEvent = true;
try {
this.tree.setSelection([element]);
} finally {
this.ignoreSelectionChangedEvent = false;
}
};
if (!thread) {
if (!process) {
this.tree.clearSelection();
return TPromise.as(null);
}
this.tree.setSelection([process]);
updateSelection(process);
return this.tree.reveal(process);
}
@@ -162,7 +200,7 @@ export class CallStackView extends TreeViewsViewletPanel {
return TPromise.as(null);
}
this.tree.setSelection([stackFrame]);
updateSelection(stackFrame);
return this.tree.reveal(stackFrame);
});
}
@@ -182,20 +220,6 @@ export class CallStackView extends TreeViewsViewletPanel {
}
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) {
@@ -208,25 +232,6 @@ class CallStackController extends BaseDebugController {
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);
}
}

View File

@@ -17,7 +17,7 @@ import { ToggleViewletAction, Extensions as ViewletExtensions, ViewletRegistry,
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 } from 'vs/workbench/parts/debug/electron-browser/variablesView';
import { BreakpointsView } from 'vs/workbench/parts/debug/electron-browser/breakpointsView';
import { BreakpointsView } from 'vs/workbench/parts/debug/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';
@@ -38,10 +38,10 @@ import { DebugContentProvider } from 'vs/workbench/parts/debug/browser/debugCont
import 'vs/workbench/parts/debug/electron-browser/debugEditorContribution';
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import * as debugCommands from 'vs/workbench/parts/debug/electron-browser/debugCommands';
import { registerCommands } from 'vs/workbench/parts/debug/browser/debugCommands';
import { IQuickOpenRegistry, Extensions as QuickOpenExtensions, QuickOpenHandlerDescriptor } from 'vs/workbench/browser/quickopen';
import { StatusBarColorProvider } from 'vs/workbench/parts/debug/electron-browser/statusbarColorProvider';
import { ViewLocation, ViewsRegistry } from 'vs/workbench/browser/parts/views/viewsRegistry';
import { StatusBarColorProvider } from 'vs/workbench/parts/debug/browser/statusbarColorProvider';
import { ViewLocation, ViewsRegistry } from 'vs/workbench/common/views';
import { isMacintosh } from 'vs/base/common/platform';
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import URI from 'vs/base/common/uri';
@@ -50,6 +50,7 @@ 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';
import { launchSchemaId } from 'vs/workbench/services/configuration/common/configuration';
class OpenDebugViewletAction extends ToggleViewletAction {
public static readonly ID = VIEWLET_ID;
@@ -107,10 +108,10 @@ Registry.as<PanelRegistry>(PanelExtensions.Panels).registerPanel(new PanelDescri
Registry.as<PanelRegistry>(PanelExtensions.Panels).setDefaultPanelId(REPL_ID);
// Register default debug views
ViewsRegistry.registerViews([{ id: VARIABLES_VIEW_ID, name: nls.localize('variables', "Variables"), ctor: VariablesView, order: 10, size: 40, location: ViewLocation.Debug, canToggleVisibility: true }]);
ViewsRegistry.registerViews([{ id: WATCH_VIEW_ID, name: nls.localize('watch', "Watch"), ctor: WatchExpressionsView, order: 20, size: 10, location: ViewLocation.Debug, canToggleVisibility: true }]);
ViewsRegistry.registerViews([{ id: CALLSTACK_VIEW_ID, name: nls.localize('callStack', "Call Stack"), ctor: CallStackView, order: 30, size: 30, location: ViewLocation.Debug, canToggleVisibility: true }]);
ViewsRegistry.registerViews([{ id: BREAKPOINTS_VIEW_ID, name: nls.localize('breakpoints', "Breakpoints"), ctor: BreakpointsView, order: 40, size: 20, location: ViewLocation.Debug, canToggleVisibility: true }]);
ViewsRegistry.registerViews([{ id: VARIABLES_VIEW_ID, name: nls.localize('variables', "Variables"), ctor: VariablesView, order: 10, weight: 40, location: ViewLocation.Debug, canToggleVisibility: true }]);
ViewsRegistry.registerViews([{ id: WATCH_VIEW_ID, name: nls.localize('watch', "Watch"), ctor: WatchExpressionsView, order: 20, weight: 10, location: ViewLocation.Debug, canToggleVisibility: true }]);
ViewsRegistry.registerViews([{ id: CALLSTACK_VIEW_ID, name: nls.localize('callStack', "Call Stack"), ctor: CallStackView, order: 30, weight: 30, location: ViewLocation.Debug, canToggleVisibility: true }]);
ViewsRegistry.registerViews([{ id: BREAKPOINTS_VIEW_ID, name: nls.localize('breakpoints', "Breakpoints"), ctor: BreakpointsView, order: 40, weight: 20, location: ViewLocation.Debug, canToggleVisibility: true }]);
// register action to open viewlet
const registry = Registry.as<IWorkbenchActionRegistry>(WorkbenchActionRegistryExtensions.WorkbenchActions);
@@ -201,17 +202,18 @@ configurationRegistry.registerConfiguration({
'debug.openDebug': {
enum: ['neverOpen', 'openOnSessionStart', 'openOnFirstSessionStart'],
default: 'openOnFirstSessionStart',
description: nls.localize('openDebug', "Controls whether debug viewlet should be open on debugging session start.")
description: nls.localize('openDebug', "Controls whether debug view 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"),
default: {}
default: { configurations: [], compounds: [] },
$ref: launchSchemaId
}
}
});
debugCommands.registerCommands();
registerCommands();
// Register Debug Status
const statusBar = Registry.as<IStatusbarRegistry>(StatusExtensions.Statusbar);
@@ -223,7 +225,7 @@ if (isMacintosh) {
const registerTouchBarEntry = (id: string, title: string, order, when: ContextKeyExpr, icon: string) => {
MenuRegistry.appendMenuItem(MenuId.TouchBarContext, {
command: {
id, title, iconPath: URI.parse(require.toUrl(`vs/workbench/parts/debug/electron-browser/media/${icon}`)).fsPath
id, title, iconPath: { dark: URI.parse(require.toUrl(`vs/workbench/parts/debug/electron-browser/media/${icon}`)).fsPath }
},
when,
group: '9_debug',
@@ -239,4 +241,4 @@ if (isMacintosh) {
registerTouchBarEntry(StepOutAction.ID, StepOutAction.LABEL, 4, CONTEXT_DEBUG_STATE.isEqualTo('stopped'), 'stepout-tb.png');
registerTouchBarEntry(RestartAction.ID, RestartAction.LABEL, 5, CONTEXT_IN_DEBUG_MODE, 'restart-tb.png');
registerTouchBarEntry(StopAction.ID, StopAction.LABEL, 6, CONTEXT_IN_DEBUG_MODE, 'stop-tb.png');
}
}

View File

@@ -1,185 +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 { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
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 { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { IListService } from 'vs/platform/list/browser/listService';
import { IMessageService } from 'vs/platform/message/common/message';
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { IDebugService, IEnablement, CONTEXT_BREAKPOINTS_FOCUSED, CONTEXT_WATCH_EXPRESSIONS_FOCUSED, CONTEXT_VARIABLES_FOCUSED, EDITOR_CONTRIBUTION_ID, IDebugEditorContribution } from 'vs/workbench/parts/debug/common/debug';
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) => {
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
debugService.logToRepl(value, severity.Warning);
}
},
when: undefined,
primary: undefined
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'debug.toggleBreakpoint',
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(5),
when: CONTEXT_BREAKPOINTS_FOCUSED,
primary: KeyCode.Space,
handler: (accessor) => {
const listService = accessor.get(IListService);
const debugService = accessor.get(IDebugService);
const focused = listService.lastFocusedList;
// Tree only
if (!(focused instanceof List)) {
const tree = focused;
const element = <IEnablement>tree.getFocus();
debugService.enableOrDisableBreakpoints(!element.enabled, element).done(null, errors.onUnexpectedError);
}
}
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'debug.renameWatchExpression',
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(5),
when: CONTEXT_WATCH_EXPRESSIONS_FOCUSED,
primary: KeyCode.F2,
mac: { primary: KeyCode.Enter },
handler: (accessor) => {
const listService = accessor.get(IListService);
const debugService = accessor.get(IDebugService);
const focused = listService.lastFocusedList;
// Tree only
if (!(focused instanceof List)) {
const element = focused.getFocus();
if (element instanceof Expression) {
debugService.getViewModel().setSelectedExpression(element);
}
}
}
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'debug.setVariable',
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(5),
when: CONTEXT_VARIABLES_FOCUSED,
primary: KeyCode.F2,
mac: { primary: KeyCode.Enter },
handler: (accessor) => {
const listService = accessor.get(IListService);
const debugService = accessor.get(IDebugService);
const focused = listService.lastFocusedList;
// Tree only
if (!(focused instanceof List)) {
const element = focused.getFocus();
if (element instanceof Variable) {
debugService.getViewModel().setSelectedExpression(element);
}
}
}
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'debug.removeWatchExpression',
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
when: CONTEXT_WATCH_EXPRESSIONS_FOCUSED,
primary: KeyCode.Delete,
mac: { primary: KeyMod.CtrlCmd | KeyCode.Backspace },
handler: (accessor) => {
const listService = accessor.get(IListService);
const debugService = accessor.get(IDebugService);
const focused = listService.lastFocusedList;
// Tree only
if (!(focused instanceof List)) {
const element = focused.getFocus();
if (element instanceof Expression) {
debugService.removeWatchExpressions(element.getId());
}
}
}
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'debug.removeBreakpoint',
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
when: CONTEXT_BREAKPOINTS_FOCUSED,
primary: KeyCode.Delete,
mac: { primary: KeyMod.CtrlCmd | KeyCode.Backspace },
handler: (accessor) => {
const listService = accessor.get(IListService);
const debugService = accessor.get(IDebugService);
const focused = listService.lastFocusedList;
// Tree only
if (!(focused instanceof List)) {
const element = focused.getFocus();
if (element instanceof Breakpoint) {
debugService.removeBreakpoints(element.getId()).done(null, errors.onUnexpectedError);
} else if (element instanceof FunctionBreakpoint) {
debugService.removeFunctionBreakpoints(element.getId()).done(null, errors.onUnexpectedError);
}
}
}
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'debug.installAdditionalDebuggers',
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
when: undefined,
primary: undefined,
handler: (accessor) => {
const viewletService = accessor.get(IViewletService);
return viewletService.openViewlet(EXTENSIONS_VIEWLET_ID, true)
.then(viewlet => viewlet as IExtensionsViewlet)
.then(viewlet => {
viewlet.search('tag:debuggers @sort:installs');
viewlet.focus();
});
}
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'debug.addConfiguration',
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
when: undefined,
primary: undefined,
handler: (accessor, workspaceUri: string) => {
const manager = accessor.get(IDebugService).getConfigurationManager();
if (accessor.get(IWorkspaceContextService).getWorkbenchState() === WorkbenchState.EMPTY) {
accessor.get(IMessageService).show(severity.Info, nls.localize('noFolderDebugConfig', "Please first open a folder in order to do advanced debug configuration."));
return TPromise.as(null);
}
const launch = manager.getLaunches().filter(l => l.workspace.uri.toString() === workspaceUri).pop() || manager.selectedLaunch;
return launch.openConfigFile(false).done(editor => {
if (editor) {
const codeEditor = <ICodeEditor>editor.getControl();
if (codeEditor) {
return codeEditor.getContribution<IDebugEditorContribution>(EDITOR_CONTRIBUTION_ID).addLaunchConfiguration();
}
}
return undefined;
});
}
});
}

View File

@@ -14,25 +14,27 @@ 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 } from 'vs/editor/common/editorCommon';
import { ITextModel } from 'vs/editor/common/model';
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';
import * as extensionsRegistry from 'vs/platform/extensions/common/extensionsRegistry';
import * as extensionsRegistry from 'vs/workbench/services/extensions/common/extensionsRegistry';
import { Registry } from 'vs/platform/registry/common/platform';
import { IExtensionService } from 'vs/platform/extensions/common/extensions';
import { IExtensionService } from 'vs/workbench/services/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 { IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { IWorkspaceContextService, IWorkspaceFolder, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { IDebugConfigurationProvider, IRawAdapter, ICompound, IDebugConfiguration, IConfig, IEnvConfig, IGlobalConfig, IConfigurationManager, ILaunch } from 'vs/workbench/parts/debug/common/debug';
import { IDebugConfigurationProvider, IRawAdapter, ICompound, IDebugConfiguration, IConfig, IEnvConfig, IGlobalConfig, IConfigurationManager, ILaunch, IAdapterExecutable } from 'vs/workbench/parts/debug/common/debug';
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';
import { launchSchemaId } from 'vs/workbench/services/configuration/common/configuration';
import { IPreferencesService } from 'vs/workbench/parts/preferences/common/preferences';
// debuggers extension point
export const debuggersExtPoint = extensionsRegistry.ExtensionsRegistry.registerExtensionPoint<IRawAdapter[]>('debuggers', [], {
@@ -102,11 +104,11 @@ export const debuggersExtPoint = extensionsRegistry.ExtensionsRegistry.registerE
}
},
osx: {
description: nls.localize('vscode.extension.contributes.debuggers.osx', "OS X specific settings."),
description: nls.localize('vscode.extension.contributes.debuggers.osx', "macOS specific settings."),
type: 'object',
properties: {
runtime: {
description: nls.localize('vscode.extension.contributes.debuggers.osx.runtime', "Runtime used for OSX."),
description: nls.localize('vscode.extension.contributes.debuggers.osx.runtime', "Runtime used for macOS."),
type: 'string'
}
}
@@ -147,14 +149,12 @@ const breakpointsExtPoint = extensionsRegistry.ExtensionsRegistry.registerExtens
});
// debug general schema
export const schemaId = 'vscode://schemas/launch';
const defaultCompound: ICompound = { name: 'Compound', configurations: [] };
const schema: IJSONSchema = {
id: schemaId,
id: launchSchemaId,
type: 'object',
title: nls.localize('app.launch.json.title', "Launch"),
required: ['version', 'configurations'],
required: [],
default: { version: '0.2.0', configurations: [], compounds: [] },
properties: {
version: {
@@ -186,7 +186,23 @@ const schema: IJSONSchema = {
type: 'array',
default: [],
items: {
type: 'string'
oneOf: [{
enum: [],
description: nls.localize('useUniqueNames', "Please use unique configuration names.")
}, {
type: 'object',
required: ['name'],
properties: {
name: {
enum: [],
description: nls.localize('app.launch.json.compound.name', "Name of compound. Appears in the launch configuration drop down menu.")
},
folder: {
enum: [],
description: nls.localize('app.launch.json.compound.folder', "Name of folder in which the compound is located.")
}
}
}]
},
description: nls.localize('app.launch.json.compounds.configurations', "Names of configurations that will be started as part of this compound.")
}
@@ -201,7 +217,7 @@ const schema: IJSONSchema = {
};
const jsonRegistry = <IJSONContributionRegistry>Registry.as(JSONExtensions.JSONContribution);
jsonRegistry.registerSchema(schemaId, schema);
jsonRegistry.registerSchema(launchSchemaId, schema);
const DEBUG_SELECTED_CONFIG_NAME_KEY = 'debug.selectedconfigname';
const DEBUG_SELECTED_ROOT = 'debug.selectedroot';
@@ -209,8 +225,8 @@ export class ConfigurationManager implements IConfigurationManager {
private adapters: Adapter[];
private breakpointModeIdsSet = new Set<string>();
private launches: ILaunch[];
private _selectedName: string;
private _selectedLaunch: ILaunch;
private selectedName: string;
private selectedLaunch: ILaunch;
private toDispose: IDisposable[];
private _onDidSelectConfigurationName = new Emitter<void>();
private providers: IDebugConfigurationProvider[];
@@ -232,7 +248,7 @@ export class ConfigurationManager implements IConfigurationManager {
this.registerListeners(lifecycleService);
this.initLaunches();
const previousSelectedRoot = this.storageService.get(DEBUG_SELECTED_ROOT, StorageScope.WORKSPACE);
const filtered = this.launches.filter(l => l.workspace.uri.toString() === previousSelectedRoot);
const filtered = this.launches.filter(l => l.uri.toString() === previousSelectedRoot);
this.selectConfiguration(filtered.length ? filtered[0] : undefined, this.storageService.get(DEBUG_SELECTED_CONFIG_NAME_KEY, StorageScope.WORKSPACE));
}
@@ -276,6 +292,14 @@ export class ConfigurationManager implements IConfigurationManager {
.then(results => results.reduce((first, second) => first.concat(second), []));
}
public debugAdapterExecutable(folderUri: uri | undefined, type: string): TPromise<IAdapterExecutable | undefined> {
const providers = this.providers.filter(p => p.type === type && p.debugAdapterExecutable);
if (providers.length === 1) {
return providers[0].debugAdapterExecutable(folderUri);
}
return TPromise.as(undefined);
}
private registerListeners(lifecycleService: ILifecycleService): void {
debuggersExtPoint.setHandler((extensions) => {
extensions.forEach(extension => {
@@ -293,7 +317,7 @@ export class ConfigurationManager implements IConfigurationManager {
if (duplicate) {
duplicate.merge(rawAdapter, extension.description);
} else {
this.adapters.push(new Adapter(rawAdapter, extension.description, this.configurationService, this.commandService));
this.adapters.push(new Adapter(this, rawAdapter, extension.description, this.configurationService, this.commandService));
}
});
});
@@ -310,6 +334,8 @@ export class ConfigurationManager implements IConfigurationManager {
items.defaultSnippets.push(...configurationSnippets);
}
});
this.setCompoundSchemaValues();
});
breakpointsExtPoint.setHandler(extensions => {
@@ -323,10 +349,12 @@ export class ConfigurationManager implements IConfigurationManager {
this.toDispose.push(this.contextService.onDidChangeWorkspaceFolders(() => {
this.initLaunches();
this.selectConfiguration();
this.setCompoundSchemaValues();
}));
this.toDispose.push(this.configurationService.onDidChangeConfiguration(e => {
if (e.affectsConfiguration('launch')) {
this.selectConfiguration();
this.setCompoundSchemaValues();
}
}));
@@ -335,42 +363,75 @@ export class ConfigurationManager implements IConfigurationManager {
private initLaunches(): void {
this.launches = this.contextService.getWorkspace().folders.map(folder => this.instantiationService.createInstance(Launch, this, folder));
if (this.launches.indexOf(this._selectedLaunch) === -1) {
this._selectedLaunch = undefined;
if (this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE) {
this.launches.push(this.instantiationService.createInstance(WorkspaceLaunch, this));
}
this.launches.push(this.instantiationService.createInstance(UserLaunch, this));
if (this.launches.indexOf(this.selectedLaunch) === -1) {
this.selectedLaunch = undefined;
}
}
private setCompoundSchemaValues(): void {
const compoundConfigurationsSchema = (<IJSONSchema>schema.properties['compounds'].items).properties['configurations'];
const launchNames = this.launches.map(l =>
l.getConfigurationNames(false)).reduce((first, second) => first.concat(second), []);
(<IJSONSchema>compoundConfigurationsSchema.items).oneOf[0].enum = launchNames;
(<IJSONSchema>compoundConfigurationsSchema.items).oneOf[1].properties.name.enum = launchNames;
const folderNames = this.contextService.getWorkspace().folders.map(f => f.name);
(<IJSONSchema>compoundConfigurationsSchema.items).oneOf[1].properties.folder.enum = folderNames;
jsonRegistry.registerSchema(launchSchemaId, schema);
}
public getLaunches(): ILaunch[] {
return this.launches;
}
public get selectedLaunch(): ILaunch {
return this._selectedLaunch;
public getLaunch(workspaceUri: uri): ILaunch {
if (!uri.isUri(workspaceUri)) {
return undefined;
}
return this.launches.filter(l => l.workspace && l.workspace.uri.toString() === workspaceUri.toString()).pop();
}
public get selectedName(): string {
return this._selectedName;
public get selectedConfiguration(): { launch: ILaunch, name: string } {
return {
launch: this.selectedLaunch,
name: this.selectedName
};
}
public get onDidSelectConfiguration(): Event<void> {
return this._onDidSelectConfigurationName.event;
}
public getWorkspaceLaunch(): ILaunch {
if (this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE) {
return this.launches[this.launches.length - 1];
}
return undefined;
}
public selectConfiguration(launch?: ILaunch, name?: string, debugStarted?: boolean): void {
const previousLaunch = this._selectedLaunch;
const previousName = this._selectedName;
const previousLaunch = this.selectedLaunch;
const previousName = this.selectedName;
if (!launch) {
launch = this.selectedLaunch && this.selectedLaunch.getConfigurationNames().length ? this.selectedLaunch : first(this.launches, l => !!l.getConfigurationNames().length, this.launches.length ? this.launches[0] : undefined);
}
this._selectedLaunch = launch;
this.selectedLaunch = launch;
const names = launch ? launch.getConfigurationNames() : [];
if (name && names.indexOf(name) >= 0) {
this._selectedName = name;
this.selectedName = name;
}
if (names.indexOf(this.selectedName) === -1) {
this._selectedName = names.length ? names[0] : undefined;
this.selectedName = names.length ? names[0] : undefined;
}
if (this.selectedLaunch !== previousLaunch || this.selectedName !== previousName) {
@@ -378,10 +439,10 @@ export class ConfigurationManager implements IConfigurationManager {
}
}
public canSetBreakpointsIn(model: IModel): boolean {
public canSetBreakpointsIn(model: ITextModel): boolean {
const modeId = model ? model.getLanguageIdentifier().language : null;
if (!modeId || modeId === 'jsonc') {
// do not allow breakpoints in our settings files
if (!modeId || modeId === 'jsonc' || modeId === 'log') {
// do not allow breakpoints in our settings files and output
return false;
}
if (this.configurationService.getValue<IDebugConfiguration>('debug').allowBreakpointsEverywhere) {
@@ -403,6 +464,7 @@ export class ConfigurationManager implements IConfigurationManager {
}
const editor = this.editorService.getActiveEditor();
let candidates: Adapter[];
if (editor) {
const codeEditor = editor.getControl();
if (isCodeEditor(codeEditor)) {
@@ -412,10 +474,16 @@ export class ConfigurationManager implements IConfigurationManager {
if (adapters.length === 1) {
return TPromise.as(adapters[0]);
}
if (adapters.length > 1) {
candidates = adapters;
}
}
}
return this.quickOpenService.pick([...this.adapters.filter(a => a.hasInitialConfiguration() || a.hasConfigurationProvider), { label: 'More...', separator: { border: true } }], { placeHolder: nls.localize('selectDebug', "Select Environment") })
if (!candidates) {
candidates = this.adapters.filter(a => a.hasInitialConfiguration() || a.hasConfigurationProvider);
}
return this.quickOpenService.pick([...candidates, { label: 'More...', separator: { border: true } }], { placeHolder: nls.localize('selectDebug', "Select Environment") })
.then(picked => {
if (picked instanceof Adapter) {
return picked;
@@ -430,8 +498,8 @@ export class ConfigurationManager implements IConfigurationManager {
private store(): void {
this.storageService.store(DEBUG_SELECTED_CONFIG_NAME_KEY, this.selectedName, StorageScope.WORKSPACE);
if (this._selectedLaunch) {
this.storageService.store(DEBUG_SELECTED_ROOT, this._selectedLaunch.workspace.uri.toString(), StorageScope.WORKSPACE);
if (this.selectedLaunch) {
this.storageService.store(DEBUG_SELECTED_ROOT, this.selectedLaunch.uri.toString(), StorageScope.WORKSPACE);
}
}
@@ -446,15 +514,32 @@ class Launch implements ILaunch {
private configurationManager: ConfigurationManager,
public workspace: IWorkspaceFolder,
@IFileService private fileService: IFileService,
@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
@IConfigurationService private configurationService: IConfigurationService,
@IConfigurationResolverService private configurationResolverService: IConfigurationResolverService
@IWorkbenchEditorService protected editorService: IWorkbenchEditorService,
@IConfigurationService protected configurationService: IConfigurationService,
@IConfigurationResolverService private configurationResolverService: IConfigurationResolverService,
@IWorkspaceContextService protected contextService: IWorkspaceContextService
) {
// noop
}
public get uri(): uri {
return this.workspace.uri.with({ path: paths.join(this.workspace.uri.path, '/.vscode/launch.json') });
}
public get name(): string {
return this.workspace.name;
}
public get hidden(): boolean {
return false;
}
protected getConfig(): IGlobalConfig {
return this.configurationService.inspect<IGlobalConfig>('launch', { resource: this.workspace.uri }).workspaceFolder;
}
public getCompound(name: string): ICompound {
const config = this.configurationService.getValue<IGlobalConfig>('launch', { resource: this.workspace.uri });
const config = this.getConfig();
if (!config || !config.compounds) {
return null;
}
@@ -462,13 +547,13 @@ class Launch implements ILaunch {
return config.compounds.filter(compound => compound.name === name).pop();
}
public getConfigurationNames(): string[] {
const config = this.configurationService.getValue<IGlobalConfig>('launch', { resource: this.workspace.uri });
public getConfigurationNames(includeCompounds = true): string[] {
const config = this.getConfig();
if (!config || !config.configurations || !Array.isArray(config.configurations)) {
return [];
} else {
const names = config.configurations.filter(cfg => cfg && typeof cfg.name === 'string').map(cfg => cfg.name);
if (names.length > 0 && config.compounds) {
if (includeCompounds && config.compounds) {
if (config.compounds) {
names.push(...config.compounds.filter(compound => typeof compound.name === 'string' && compound.configurations && compound.configurations.length)
.map(compound => compound.name));
@@ -480,7 +565,8 @@ class Launch implements ILaunch {
}
public getConfiguration(name: string): IConfig {
const config = objects.deepClone(this.configurationService.getValue<IGlobalConfig>('launch', { resource: this.workspace.uri }));
// We need to clone the configuration in order to be able to make changes to it #42198
const config = objects.deepClone(this.getConfig());
if (!config || !config.configurations) {
return null;
}
@@ -488,6 +574,18 @@ class Launch implements ILaunch {
return config.configurations.filter(config => config && config.name === name).shift();
}
protected getWorkspaceForResolving(): IWorkspaceFolder {
if (this.workspace) {
return this.workspace;
}
if (this.contextService.getWorkspace().folders.length === 1) {
return this.contextService.getWorkspace().folders[0];
}
return undefined;
}
public resolveConfiguration(config: IConfig): TPromise<IConfig> {
const result = objects.deepClone(config) as IConfig;
// Set operating system specific properties #1873
@@ -504,22 +602,18 @@ class Launch implements ILaunch {
// massage configuration attributes - append workspace path to relatvie paths, substitute variables in paths.
Object.keys(result).forEach(key => {
result[key] = this.configurationResolverService.resolveAny(this.workspace, result[key]);
result[key] = this.configurationResolverService.resolveAny(this.getWorkspaceForResolving(), result[key]);
});
const adapter = this.configurationManager.getAdapter(result.type);
return this.configurationResolverService.resolveInteractiveVariables(result, adapter ? adapter.variables : null);
}
public get uri(): uri {
return this.workspace.uri.with({ path: paths.join(this.workspace.uri.path, '/.vscode/launch.json') });
}
public openConfigFile(sideBySide: boolean, type?: string): TPromise<IEditor> {
const resource = this.uri;
let configFileCreated = false;
let pinned = false;
return this.fileService.resolveContent(resource).then(content => content, err => {
return this.fileService.resolveContent(resource).then(content => content.value, err => {
// launch.json not found: create one by collecting launch configs from debugConfigProviders
@@ -537,20 +631,20 @@ class Launch implements ILaunch {
return undefined;
}
configFileCreated = true;
pinned = true; // pin only if config file is created #8727
return this.fileService.updateContent(resource, content).then(() => {
// convert string into IContent; see #32135
return { value: content };
return content;
});
});
}).then(content => {
if (!content) {
return undefined;
}
const index = content.value.indexOf(`"${this.configurationManager.selectedName}"`);
const index = content.indexOf(`"${this.configurationManager.selectedConfiguration.name}"`);
let startLineNumber = 1;
for (let i = 0; i < index; i++) {
if (content.value.charAt(i) === '\n') {
if (content.charAt(i) === '\n') {
startLineNumber++;
}
}
@@ -561,7 +655,7 @@ class Launch implements ILaunch {
options: {
forceOpen: true,
selection,
pinned: configFileCreated, // pin only if config file is created #8727
pinned,
revealIfVisible: true
},
}, sideBySide);
@@ -570,3 +664,68 @@ class Launch implements ILaunch {
});
}
}
class WorkspaceLaunch extends Launch implements ILaunch {
constructor(
configurationManager: ConfigurationManager,
@IFileService fileService: IFileService,
@IWorkbenchEditorService editorService: IWorkbenchEditorService,
@IConfigurationService configurationService: IConfigurationService,
@IConfigurationResolverService configurationResolverService: IConfigurationResolverService,
@IWorkspaceContextService contextService: IWorkspaceContextService
) {
super(configurationManager, undefined, fileService, editorService, configurationService, configurationResolverService, contextService);
}
get uri(): uri {
return this.contextService.getWorkspace().configuration;
}
get name(): string {
return nls.localize('workspace', "workspace");
}
protected getConfig(): IGlobalConfig {
return this.configurationService.inspect<IGlobalConfig>('launch').workspace;
}
openConfigFile(sideBySide: boolean, type?: string): TPromise<IEditor> {
return this.editorService.openEditor({ resource: this.contextService.getWorkspace().configuration });
}
}
class UserLaunch extends Launch implements ILaunch {
constructor(
configurationManager: ConfigurationManager,
@IFileService fileService: IFileService,
@IWorkbenchEditorService editorService: IWorkbenchEditorService,
@IConfigurationService configurationService: IConfigurationService,
@IConfigurationResolverService configurationResolverService: IConfigurationResolverService,
@IPreferencesService private preferencesService: IPreferencesService,
@IWorkspaceContextService contextService: IWorkspaceContextService
) {
super(configurationManager, undefined, fileService, editorService, configurationService, configurationResolverService, contextService);
}
get uri(): uri {
return this.preferencesService.userSettingsResource;
}
get name(): string {
return nls.localize('user settings', "user settings");
}
public get hidden(): boolean {
return true;
}
protected getConfig(): IGlobalConfig {
return this.configurationService.inspect<IGlobalConfig>('launch').user;
}
openConfigFile(sideBySide: boolean, type?: string): TPromise<IEditor> {
return this.preferencesService.openGlobalSettings();
}
}

View File

@@ -19,7 +19,8 @@ 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 { registerEditorContribution } from 'vs/editor/browser/editorExtensions';
import { IDecorationOptions, IModelDecorationOptions, IModelDeltaDecoration, TrackedRangeStickiness } from 'vs/editor/common/editorCommon';
import { IDecorationOptions } from 'vs/editor/common/editorCommon';
import { IModelDecorationOptions, IModelDeltaDecoration, TrackedRangeStickiness } from 'vs/editor/common/model';
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
import { Range } from 'vs/editor/common/core/range';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
@@ -27,7 +28,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { IContextMenuService, ContextSubMenu } from 'vs/platform/contextview/browser/contextView';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { DebugHoverWidget } from 'vs/workbench/parts/debug/electron-browser/debugHover';
import { RemoveBreakpointAction, EditConditionalBreakpointAction, EnableBreakpointAction, DisableBreakpointAction, AddConditionalBreakpointAction } from 'vs/workbench/parts/debug/browser/debugActions';
import { IDebugEditorContribution, IDebugService, State, IBreakpoint, EDITOR_CONTRIBUTION_ID, CONTEXT_BREAKPOINT_WIDGET_VISIBLE, IStackFrame, IDebugConfiguration, IExpression, IExceptionInfo } from 'vs/workbench/parts/debug/common/debug';
@@ -40,7 +41,7 @@ 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';
import { ContextSubMenu } from 'vs/base/browser/contextmenu';
const HOVER_DELAY = 300;
const LAUNCH_JSON_REGEX = /launch\.json$/;
@@ -77,13 +78,12 @@ export class DebugEditorContribution implements IDebugEditorContribution {
@ICommandService private commandService: ICommandService,
@ICodeEditorService private codeEditorService: ICodeEditorService,
@ITelemetryService private telemetryService: ITelemetryService,
@IListService listService: IListService,
@IConfigurationService private configurationService: IConfigurationService,
@IThemeService themeService: IThemeService,
@IKeybindingService private keybindingService: IKeybindingService
) {
this.breakpointHintDecoration = [];
this.hoverWidget = new DebugHoverWidget(this.editor, this.debugService, this.instantiationService, themeService, contextKeyService, listService);
this.hoverWidget = new DebugHoverWidget(this.editor, this.debugService, this.instantiationService, themeService);
this.toDispose = [];
this.showHoverScheduler = new RunOnceScheduler(() => this.showHover(this.hoverRange, false), HOVER_DELAY);
this.hideHoverScheduler = new RunOnceScheduler(() => this.hoverWidget.hide(), HOVER_DELAY);
@@ -409,12 +409,13 @@ export class DebugEditorContribution implements IDebugEditorContribution {
// configuration widget
private updateConfigurationWidgetVisibility(): void {
const model = this.editor.getModel();
if (this.configurationWidget) {
this.configurationWidget.dispose();
}
if (model && LAUNCH_JSON_REGEX.test(model.uri.toString())) {
this.configurationWidget = this.instantiationService.createInstance(FloatingClickWidget, this.editor, nls.localize('addConfiguration', "Add Configuration..."), null);
this.configurationWidget.render();
this.toDispose.push(this.configurationWidget.onClick(() => this.addLaunchConfiguration().done(undefined, errors.onUnexpectedError)));
} else if (this.configurationWidget) {
this.configurationWidget.dispose();
}
}
@@ -468,7 +469,7 @@ export class DebugEditorContribution implements IDebugEditorContribution {
}
private static BREAKPOINT_HELPER_DECORATION: IModelDecorationOptions = {
glyphMarginClassName: 'debug-breakpoint-hint-glyph',
glyphMarginClassName: 'debug-breakpoint-hint',
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges
};
@@ -581,6 +582,10 @@ export class DebugEditorContribution implements IDebugEditorContribution {
if (!this.wordToLineNumbersMap) {
this.wordToLineNumbersMap = new Map<string, Position[]>();
const model = this.editor.getModel();
if (!model) {
return this.wordToLineNumbersMap;
}
// For every word in every line, map its ranges for fast lookup
for (let lineNumber = 1, len = model.getLineCount(); lineNumber <= len; ++lineNumber) {
const lineContent = model.getLineContent(lineNumber);
@@ -592,11 +597,14 @@ export class DebugEditorContribution implements IDebugEditorContribution {
model.forceTokenization(lineNumber);
const lineTokens = model.getLineTokens(lineNumber);
for (let token = lineTokens.firstToken(); !!token; token = token.next()) {
const tokenStr = lineContent.substring(token.startOffset, token.endOffset);
for (let tokenIndex = 0, tokenCount = lineTokens.getCount(); tokenIndex < tokenCount; tokenIndex++) {
const tokenStartOffset = lineTokens.getStartOffset(tokenIndex);
const tokenEndOffset = lineTokens.getEndOffset(tokenIndex);
const tokenType = lineTokens.getStandardTokenType(tokenIndex);
const tokenStr = lineContent.substring(tokenStartOffset, tokenEndOffset);
// Token is a word and not a comment
if (token.tokenType === StandardTokenType.Other) {
if (tokenType === StandardTokenType.Other) {
DEFAULT_WORD_REGEXP.lastIndex = 0; // We assume tokens will usually map 1:1 to words if they match
const wordMatch = DEFAULT_WORD_REGEXP.exec(tokenStr);
@@ -606,7 +614,7 @@ export class DebugEditorContribution implements IDebugEditorContribution {
this.wordToLineNumbersMap.set(word, []);
}
this.wordToLineNumbersMap.get(word).push(new Position(lineNumber, token.startOffset));
this.wordToLineNumbersMap.get(word).push(new Position(lineNumber, tokenStartOffset));
}
}
}

View File

@@ -11,7 +11,7 @@ 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 { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { DefaultController, ICancelableEvent, ClickBehavior } from 'vs/base/parts/tree/browser/treeDefaults';
import { ICancelableEvent, OpenMode } from 'vs/base/parts/tree/browser/treeDefaults';
import { IConfigurationChangedEvent } from 'vs/editor/common/config/editorOptions';
import { Position } from 'vs/editor/common/core/position';
import { Range } from 'vs/editor/common/core/range';
@@ -19,14 +19,14 @@ 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 { renderExpressionValue } from 'vs/workbench/parts/debug/electron-browser/baseDebugView';
import { renderExpressionValue } from 'vs/workbench/parts/debug/browser/baseDebugView';
import { VariablesDataSource, VariablesRenderer } from 'vs/workbench/parts/debug/electron-browser/variablesView';
import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
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';
import { WorkbenchTree, WorkbenchTreeController } from 'vs/platform/list/browser/listService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
const $ = dom.$;
const MAX_ELEMENTS_SHOWN = 18;
@@ -54,9 +54,7 @@ export class DebugHoverWidget implements IContentWidget {
private editor: ICodeEditor,
private debugService: IDebugService,
private instantiationService: IInstantiationService,
private themeService: IThemeService,
private contextKeyService: IContextKeyService,
private listService: IListService
private themeService: IThemeService
) {
this.toDispose = [];
@@ -71,16 +69,15 @@ export class DebugHoverWidget implements IContentWidget {
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, {
this.tree = this.instantiationService.createInstance(WorkbenchTree, this.treeContainer, {
dataSource: new VariablesDataSource(),
renderer: this.instantiationService.createInstance(VariablesHoverRenderer),
controller: new DebugHoverController(this.editor)
controller: this.instantiationService.createInstance(DebugHoverController, this.editor)
}, {
indentPixels: 6,
twistiePixels: 15,
ariaLabel: nls.localize('treeAriaLabel', "Debug Hover"),
keyboardSupport: false
}, this.contextKeyService, this.listService, this.themeService);
ariaLabel: nls.localize('treeAriaLabel', "Debug Hover")
});
this.valueContainer = $('.value');
this.valueContainer.tabIndex = 0;
@@ -337,10 +334,13 @@ export class DebugHoverWidget implements IContentWidget {
}
}
class DebugHoverController extends DefaultController {
class DebugHoverController extends WorkbenchTreeController {
constructor(private editor: ICodeEditor) {
super({ clickBehavior: ClickBehavior.ON_MOUSE_UP, keyboardSupport: false });
constructor(
private editor: ICodeEditor,
@IConfigurationService configurationService: IConfigurationService
) {
super({ openMode: OpenMode.SINGLE_CLICK }, configurationService);
}
protected onLeftClick(tree: ITree, element: any, eventish: ICancelableEvent, origin = 'mouse'): boolean {

View File

@@ -11,7 +11,6 @@ import * as strings from 'vs/base/common/strings';
import { generateUuid } from 'vs/base/common/uuid';
import uri from 'vs/base/common/uri';
import * as platform from 'vs/base/common/platform';
import { Action } from 'vs/base/common/actions';
import { first, distinct } from 'vs/base/common/arrays';
import { isObject, isUndefinedOrNull } from 'vs/base/common/types';
import * as errors from 'vs/base/common/errors';
@@ -22,10 +21,9 @@ import { Client as TelemetryClient } from 'vs/base/parts/ipc/node/ipc.cp';
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { IMarkerService } from 'vs/platform/markers/common/markers';
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
import { IExtensionService } from 'vs/platform/extensions/common/extensions';
import { IExtensionService } from 'vs/workbench/services/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 { IWindowService } from 'vs/platform/windows/common/windows';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { TelemetryService } from 'vs/platform/telemetry/common/telemetryService';
@@ -37,7 +35,7 @@ import { Model, ExceptionBreakpoint, FunctionBreakpoint, Breakpoint, Expression,
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 Constants from 'vs/workbench/parts/markers/common/constants';
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';
@@ -53,6 +51,10 @@ import { IBroadcastService, IBroadcast } from 'vs/platform/broadcast/electron-br
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';
import { IChoiceService } from 'vs/platform/dialogs/common/dialogs';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { IAction, Action } from 'vs/base/common/actions';
import { normalizeDriveLetter } from 'vs/base/common/labels';
const DEBUG_BREAKPOINTS_KEY = 'debug.breakpoint';
const DEBUG_BREAKPOINTS_ACTIVATED_KEY = 'debug.breakpointactivated';
@@ -88,7 +90,8 @@ export class DebugService implements debug.IDebugService {
@ITextFileService private textFileService: ITextFileService,
@IViewletService private viewletService: IViewletService,
@IPanelService private panelService: IPanelService,
@IMessageService private messageService: IMessageService,
@INotificationService private notificationService: INotificationService,
@IChoiceService private choiceService: IChoiceService,
@IPartService private partService: IPartService,
@IWindowService private windowService: IWindowService,
@IBroadcastService private broadcastService: IBroadcastService,
@@ -122,7 +125,7 @@ export class DebugService implements debug.IDebugService {
this.model = new Model(this.loadBreakpoints(), this.storageService.getBoolean(DEBUG_BREAKPOINTS_ACTIVATED_KEY, StorageScope.WORKSPACE, true), this.loadFunctionBreakpoints(),
this.loadExceptionBreakpoints(), this.loadWatchExpressions());
this.toDispose.push(this.model);
this.viewModel = new ViewModel();
this.viewModel = new ViewModel(contextKeyService);
this.firstSessionStart = true;
this.registerListeners();
@@ -252,7 +255,7 @@ export class DebugService implements debug.IDebugService {
return TPromise.as(null);
}
this.focusStackFrameAndEvaluate(stackFrameToFocus).done(null, errors.onUnexpectedError);
this.focusStackFrame(stackFrameToFocus);
if (thread.stoppedDetails) {
this.windowService.focusWindow();
aria.alert(nls.localize('debuggingPaused', "Debugging paused, reason {0}, {1} {2}", thread.stoppedDetails.reason, stackFrameToFocus.source ? stackFrameToFocus.source.name : '', stackFrameToFocus.range.startLineNumber));
@@ -274,7 +277,7 @@ export class DebugService implements debug.IDebugService {
if (session) {
session.disconnect().done(null, errors.onUnexpectedError);
}
this.messageService.show(severity.Error, e.message);
this.notificationService.error(e.message);
});
}
};
@@ -309,7 +312,7 @@ export class DebugService implements debug.IDebugService {
aria.status(nls.localize('debuggingStopped', "Debugging stopped."));
if (session && session.getId() === event.sessionId) {
if (event.body && event.body.restart && process) {
this.restartProcess(process, event.body.restart).done(null, err => this.messageService.show(severity.Error, err.message));
this.restartProcess(process, event.body.restart).done(null, err => this.notificationService.error(err.message));
} else {
session.disconnect().done(null, errors.onUnexpectedError);
}
@@ -320,7 +323,7 @@ export class DebugService implements debug.IDebugService {
const threadId = event.body.allThreadsContinued !== false ? undefined : event.body.threadId;
this.model.clearThreads(session.getId(), false, threadId);
if (this.viewModel.focusedProcess.getId() === session.getId()) {
this.focusStackFrameAndEvaluate(null, this.viewModel.focusedProcess).done(null, errors.onUnexpectedError);
this.focusStackFrame(undefined, this.viewModel.focusedThread, this.viewModel.focusedProcess);
}
this.updateStateAndEmit(session.getId(), debug.State.Running);
}));
@@ -534,21 +537,34 @@ export class DebugService implements debug.IDebugService {
}
}
public focusStackFrameAndEvaluate(stackFrame: debug.IStackFrame, process?: debug.IProcess, explicit?: boolean): TPromise<void> {
public focusStackFrame(stackFrame: debug.IStackFrame, thread?: debug.IThread, process?: debug.IProcess, explicit?: boolean): void {
if (!process) {
const processes = this.model.getProcesses();
process = stackFrame ? stackFrame.thread.process : processes.length ? processes[0] : null;
if (stackFrame || thread) {
process = stackFrame ? stackFrame.thread.process : thread.process;
} else {
const processes = this.model.getProcesses();
process = processes.length ? processes[0] : undefined;
}
}
if (!thread) {
if (stackFrame) {
thread = stackFrame.thread;
} else {
const threads = process ? process.getAllThreads() : undefined;
thread = threads && threads.length ? threads[0] : undefined;
}
}
if (!stackFrame) {
const threads = process ? process.getAllThreads() : null;
const callStack = threads && threads.length === 1 ? threads[0].getCallStack() : null;
stackFrame = callStack && callStack.length ? callStack[0] : null;
if (thread) {
const callStack = thread.getCallStack();
stackFrame = callStack && callStack.length ? callStack[0] : null;
}
}
this.viewModel.setFocusedStackFrame(stackFrame, process, explicit);
this.viewModel.setFocus(stackFrame, thread, process, explicit);
this.updateStateAndEmit();
return this.model.evaluateWatchExpressions(process, stackFrame);
}
public enableOrDisableBreakpoints(enable: boolean, breakpoint?: debug.IEnablement): TPromise<void> {
@@ -567,16 +583,20 @@ export class DebugService implements debug.IDebugService {
return this.sendAllBreakpoints();
}
public addBreakpoints(uri: uri, rawBreakpoints: debug.IRawBreakpoint[]): TPromise<void> {
public addBreakpoints(uri: uri, rawBreakpoints: debug.IBreakpointData[]): TPromise<void> {
this.model.addBreakpoints(uri, rawBreakpoints);
rawBreakpoints.forEach(rbp => aria.status(nls.localize('breakpointAdded', "Added breakpoint, line {0}, file {1}", rbp.lineNumber, uri.fsPath)));
return this.sendBreakpoints(uri);
}
public updateBreakpoints(uri: uri, data: { [id: string]: DebugProtocol.Breakpoint }): TPromise<void> {
public updateBreakpoints(uri: uri, data: { [id: string]: DebugProtocol.Breakpoint }, sendOnResourceSaved: boolean): void {
this.model.updateBreakpoints(data);
return this.sendBreakpoints(uri);
if (sendOnResourceSaved) {
this.breakpointsToSendOnResourceSaved.add(uri.toString());
} else {
this.sendBreakpoints(uri);
}
}
public removeBreakpoints(id?: string): TPromise<any> {
@@ -594,8 +614,8 @@ export class DebugService implements debug.IDebugService {
return this.sendAllBreakpoints();
}
public addFunctionBreakpoint(): void {
const newFunctionBreakpoint = this.model.addFunctionBreakpoint('');
public addFunctionBreakpoint(name?: string, id?: string): void {
const newFunctionBreakpoint = this.model.addFunctionBreakpoint(name || '', id);
this.viewModel.setSelectedFunctionBreakpoint(newFunctionBreakpoint);
}
@@ -612,7 +632,7 @@ export class DebugService implements debug.IDebugService {
public addReplExpression(name: string): TPromise<void> {
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));
.then(() => this.focusStackFrame(this.viewModel.focusedStackFrame, this.viewModel.focusedThread, this.viewModel.focusedProcess));
}
public removeReplExpressions(): void {
@@ -628,11 +648,12 @@ export class DebugService implements debug.IDebugService {
}
}
public addWatchExpression(name: string): TPromise<void> {
return this.model.addWatchExpression(this.viewModel.focusedProcess, this.viewModel.focusedStackFrame, name);
public addWatchExpression(name: string): void {
const we = this.model.addWatchExpression(this.viewModel.focusedProcess, this.viewModel.focusedStackFrame, name);
this.viewModel.setSelectedExpression(we);
}
public renameWatchExpression(id: string, newName: string): TPromise<void> {
public renameWatchExpression(id: string, newName: string): void {
return this.model.renameWatchExpression(this.viewModel.focusedProcess, this.viewModel.focusedStackFrame, id, newName);
}
@@ -644,14 +665,10 @@ export class DebugService implements debug.IDebugService {
this.model.removeWatchExpressions(id);
}
public evaluateWatchExpressions(): TPromise<void> {
return this.model.evaluateWatchExpressions(this.viewModel.focusedProcess, this.viewModel.focusedStackFrame);
}
public startDebugging(root: IWorkspaceFolder, configOrName?: debug.IConfig | string, noDebug = false, topCompoundName?: string): TPromise<any> {
public startDebugging(launch: debug.ILaunch, configOrName?: debug.IConfig | string, noDebug = false): TPromise<any> {
// 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(() =>
return this.extensionService.activateByEvent('onDebug').then(() => this.textFileService.saveAll().then(() => this.configurationService.reloadConfiguration(launch ? launch.workspace : undefined).then(() =>
this.extensionService.whenInstalledExtensionsRegistered().then(() => {
if (this.model.getProcesses().length === 0) {
this.removeReplExpressions();
@@ -659,12 +676,10 @@ export class DebugService implements debug.IDebugService {
this.model.getBreakpoints().forEach(bp => bp.verified = false);
}
this.launchJsonChanged = false;
const manager = this.getConfigurationManager();
const launch = root ? manager.getLaunches().filter(l => l.workspace.uri.toString() === root.uri.toString()).pop() : undefined;
let config: debug.IConfig, compound: debug.ICompound;
if (!configOrName) {
configOrName = this.configurationManager.selectedName;
configOrName = this.configurationManager.selectedConfiguration.name;
}
if (typeof configOrName === 'string' && launch) {
config = launch.getConfiguration(configOrName);
@@ -672,10 +687,6 @@ export class DebugService implements debug.IDebugService {
} else if (typeof configOrName !== 'string') {
config = configOrName;
}
if (launch) {
// in the drop down the name of the top most compound takes precedence over the launch config name
manager.selectConfiguration(launch, topCompoundName || (typeof configOrName === 'string' ? configOrName : undefined), true);
}
if (compound) {
if (!compound.configurations) {
@@ -683,7 +694,35 @@ export class DebugService implements debug.IDebugService {
"Compound must have \"configurations\" attribute set in order to start multiple configurations.")));
}
return TPromise.join(compound.configurations.map(name => name !== compound.name ? this.startDebugging(root, name, noDebug, topCompoundName || compound.name) : TPromise.as(null)));
return TPromise.join(compound.configurations.map(configData => {
const name = typeof configData === 'string' ? configData : configData.name;
if (name === compound.name) {
return TPromise.as(null);
}
let launchForName: debug.ILaunch;
if (typeof configData === 'string') {
const launchesContainingName = this.configurationManager.getLaunches().filter(l => !!l.getConfiguration(name));
if (launchesContainingName.length === 1) {
launchForName = launchesContainingName[0];
} else if (launchesContainingName.length > 1 && launchesContainingName.indexOf(launch) >= 0) {
// If there are multiple launches containing the configuration give priority to the configuration in the current launch
launchForName = launch;
} else {
return TPromise.wrapError(new Error(launchesContainingName.length === 0 ? nls.localize('noConfigurationNameInWorkspace', "Could not find launch configuration '{0}' in the workspace.", name)
: nls.localize('multipleConfigurationNamesInWorkspace', "There are multiple launch configurations '{0}' in the workspace. Use folder name to qualify the configuration.", name)));
}
} else if (configData.folder) {
const launchesMatchingConfigData = this.configurationManager.getLaunches().filter(l => l.workspace && l.workspace.name === configData.folder && !!l.getConfiguration(configData.name));
if (launchesMatchingConfigData.length === 1) {
launchForName = launchesMatchingConfigData[0];
} else {
return TPromise.wrapError(new Error(nls.localize('noFolderWithName', "Can not find folder with name '{0}' for configuration '{1}' in compound '{2}'.", configData.folder, configData.name, compound.name)));
}
}
return this.startDebugging(launchForName, name, noDebug);
}));
}
if (configOrName && !config) {
const message = !!launch ? nls.localize('configMissing', "Configuration '{0}' is missing in 'launch.json'.", configOrName) :
@@ -714,16 +753,15 @@ export class DebugService implements debug.IDebugService {
return (type ? TPromise.as(null) : this.configurationManager.guessAdapter().then(a => type = a && a.type)).then(() =>
(type ? this.extensionService.activateByEvent(`onDebugResolve:${type}`) : TPromise.as(null)).then(() =>
this.configurationManager.resolveConfigurationByProviders(launch ? launch.workspace.uri : undefined, type, config).then(config => {
this.configurationManager.resolveConfigurationByProviders(launch && launch.workspace ? 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 this.createProcess(launch, config, sessionId);
}
return undefined;
if (launch) {
return launch.openConfigFile(false, type).done(undefined, errors.onUnexpectedError);
}
})
).then(() => wrapUpState(), err => {
wrapUpState();
@@ -733,9 +771,9 @@ export class DebugService implements debug.IDebugService {
)));
}
private createProcess(root: IWorkspaceFolder, config: debug.IConfig, sessionId: string): TPromise<void> {
private createProcess(launch: debug.ILaunch, 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 => {
(launch ? launch.resolveConfiguration(config) : TPromise.as(config)).then(resolvedConfig => {
if (!resolvedConfig) {
// User canceled resolving of interactive variables, silently return
return undefined;
@@ -744,65 +782,50 @@ export class DebugService implements debug.IDebugService {
if (!this.configurationManager.getAdapter(resolvedConfig.type) || (config.request !== 'attach' && config.request !== 'launch')) {
let message: string;
if (config.request !== 'attach' && config.request !== 'launch') {
message = config.request ? nls.localize('debugRequestNotSupported', "Attribute `{0}` has an unsupported value '{1}' in the chosen debug configuration.", 'request', config.request)
message = config.request ? nls.localize('debugRequestNotSupported', "Attribute '{0}' has an unsupported value '{1}' in the chosen debug configuration.", 'request', config.request)
: nls.localize('debugRequesMissing', "Attribute '{0}' is missing from the chosen debug configuration.", 'request');
} else {
message = resolvedConfig.type ? nls.localize('debugTypeNotSupported', "Configured debug type '{0}' is not supported.", resolvedConfig.type) :
nls.localize('debugTypeMissing', "Missing property `type` for the chosen launch configuration.");
nls.localize('debugTypeMissing', "Missing property 'type' for the chosen launch configuration.");
}
return TPromise.wrapError(errors.create(message, { actions: [this.instantiationService.createInstance(debugactions.ConfigureAction, debugactions.ConfigureAction.ID, debugactions.ConfigureAction.LABEL), CloseAction] }));
return this.showError(message);
}
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);
const workspace = launch ? launch.workspace : undefined;
const debugAnywayAction = new Action('debug.debugAnyway', nls.localize('debugAnyway', "Debug Anyway"), undefined, true, () => {
return this.doCreateProcess(workspace, resolvedConfig, sessionId);
});
return this.runPreLaunchTask(sessionId, root, resolvedConfig.preLaunchTask).then((taskSummary: ITaskSummary) => {
return this.runPreLaunchTask(sessionId, workspace, 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;
if (successExitCode || (errorCount === 0 && !failureExitCode)) {
return this.doCreateProcess(root, resolvedConfig, sessionId);
return this.doCreateProcess(workspace, resolvedConfig, sessionId);
}
this.messageService.show(severity.Error, {
message: errorCount > 1 ? nls.localize('preLaunchTaskErrors', "Build errors have been detected during preLaunchTask '{0}'.", resolvedConfig.preLaunchTask) :
errorCount === 1 ? nls.localize('preLaunchTaskError', "Build error has been detected during preLaunchTask '{0}'.", resolvedConfig.preLaunchTask) :
nls.localize('preLaunchTaskExitCode', "The preLaunchTask '{0}' terminated with exit code {1}.", resolvedConfig.preLaunchTask, taskSummary.exitCode),
actions: [
debugAnywayAction,
this.instantiationService.createInstance(ToggleMarkersPanelAction, ToggleMarkersPanelAction.ID, ToggleMarkersPanelAction.LABEL),
CloseAction
]
const message = errorCount > 1 ? nls.localize('preLaunchTaskErrors', "Build errors have been detected during preLaunchTask '{0}'.", resolvedConfig.preLaunchTask) :
errorCount === 1 ? nls.localize('preLaunchTaskError', "Build error has been detected during preLaunchTask '{0}'.", resolvedConfig.preLaunchTask) :
nls.localize('preLaunchTaskExitCode', "The preLaunchTask '{0}' terminated with exit code {1}.", resolvedConfig.preLaunchTask, taskSummary.exitCode);
const showErrorsAction = new Action('debug.showErrors', nls.localize('showErrors', "Show Errors"), undefined, true, () => {
return this.panelService.openPanel(Constants.MARKERS_PANEL_ID).then(() => undefined);
});
return undefined;
return this.showError(message, [debugAnywayAction, showErrorsAction]);
}, (err: TaskError) => {
this.messageService.show(err.severity, {
message: err.message,
actions: [
debugAnywayAction,
this.instantiationService.createInstance(debugactions.ConfigureAction, debugactions.ConfigureAction.ID, debugactions.ConfigureAction.LABEL),
this.taskService.configureAction(),
CloseAction
]
});
return this.showError(err.message, [debugAnywayAction, this.taskService.configureAction()]);
});
}, err => {
if (this.contextService.getWorkbenchState() === WorkbenchState.EMPTY) {
this.messageService.show(severity.Error, nls.localize('noFolderWorkspaceDebugError', "The active file can not be debugged. Make sure it is saved on disk and that you have a debug extension installed for that file type."));
return undefined;
return this.showError(nls.localize('noFolderWorkspaceDebugError', "The active file can not be debugged. Make sure it is saved on disk and that you have a debug extension installed for that file type."));
}
return this.configurationManager.selectedLaunch.openConfigFile(false).then(openend => {
if (openend) {
this.messageService.show(severity.Info, nls.localize('NewLaunchConfig', "Please set up the launch configuration file for your application. {0}", err.message));
}
return undefined;
});
return launch && launch.openConfigFile(false).then(editor => void 0);
})
);
}
@@ -870,8 +893,8 @@ export class DebugService implements debug.IDebugService {
if (session.disconnected) {
return TPromise.as(null);
}
this.focusStackFrame(undefined, undefined, process);
this._onDidNewProcess.fire(process);
this.focusStackFrameAndEvaluate(null, process);
const internalConsoleOptions = configuration.internalConsoleOptions || this.configurationService.getValue<debug.IDebugConfiguration>('debug').internalConsoleOptions;
if (internalConsoleOptions === 'openOnSessionStart' || (this.firstSessionStart && internalConsoleOptions === 'openOnFirstSessionStart')) {
@@ -911,8 +934,8 @@ export class DebugService implements debug.IDebugService {
isBuiltin: adapter.extensionDescription.isBuiltin,
launchJsonExists: root && !!this.configurationService.getValue<debug.IGlobalConfig>('launch', { resource: root.uri })
});
}).then(() => process, (error: any) => {
if (error instanceof Error && error.message === 'Canceled') {
}).then(() => process, (error: Error | string) => {
if (errors.isPromiseCanceledError(error)) {
// Do not show 'canceled' error messages to the user #7906
return TPromise.as(null);
}
@@ -921,7 +944,7 @@ export class DebugService implements debug.IDebugService {
/* __GDPR__
"debugMisconfiguration" : {
"type" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"error": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
"error": { "classification": "CallstackOrException", "purpose": "FeatureInsight" }
}
*/
this.telemetryService.publicLog('debugMisconfiguration', { type: configuration ? configuration.type : undefined, error: errorMessage });
@@ -940,14 +963,24 @@ export class DebugService implements debug.IDebugService {
this.inDebugMode.reset();
}
const configureAction = this.instantiationService.createInstance(debugactions.ConfigureAction, debugactions.ConfigureAction.ID, debugactions.ConfigureAction.LABEL);
const actions = (error.actions && error.actions.length) ? error.actions.concat([configureAction]) : [CloseAction, configureAction];
this.messageService.show(severity.Error, { message: errorMessage, actions });
this.showError(errorMessage, errors.isErrorWithActions(error) ? error.actions : []);
return undefined;
});
});
}
private showError(message: string, actions: IAction[] = []): TPromise<any> {
const configureAction = this.instantiationService.createInstance(debugactions.ConfigureAction, debugactions.ConfigureAction.ID, debugactions.ConfigureAction.LABEL);
actions.push(configureAction);
return this.choiceService.choose(severity.Error, message, actions.map(a => a.label).concat(nls.localize('cancel', "Cancel")), actions.length, true).then(choice => {
if (choice < actions.length) {
return actions[choice].run();
}
return TPromise.as(null);
});
}
private runPreLaunchTask(sessionId: string, root: IWorkspaceFolder, taskName: string): TPromise<ITaskSummary> {
if (!taskName) {
return TPromise.as(null);
@@ -1020,7 +1053,7 @@ export class DebugService implements debug.IDebugService {
const preserveFocus = focusedProcess && process.getId() === focusedProcess.getId();
return process.session.disconnect(true).then(() => {
if (strings.equalsIgnoreCase(process.configuration.type, 'extensionHost')) {
if (strings.equalsIgnoreCase(process.configuration.type, 'extensionHost') && process.session.root) {
return this.broadcastService.broadcast({
channel: EXTENSION_RELOAD_BROADCAST_CHANNEL,
payload: [process.session.root.uri.fsPath]
@@ -1031,15 +1064,17 @@ export class DebugService implements debug.IDebugService {
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) {
const launch = process.session.root ? this.configurationManager.getLaunch(process.session.root.uri) : undefined;
if (this.launchJsonChanged && launch) {
this.launchJsonChanged = false;
config = this.configurationManager.selectedLaunch.getConfiguration(process.configuration.name) || config;
config = launch.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));
this.startDebugging(launch, config).then(() => c(null), err => e(err));
}, 300);
});
}).then(() => {
@@ -1047,7 +1082,7 @@ export class DebugService implements debug.IDebugService {
// 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);
this.focusStackFrame(undefined, undefined, restartedProcess);
}
}
});
@@ -1090,7 +1125,7 @@ export class DebugService implements debug.IDebugService {
});
this.model.removeProcess(session.getId());
if (process && process.state !== debug.ProcessState.INACTIVE) {
if (process) {
process.inactive = true;
this._onDidEndProcess.fire(process);
}
@@ -1098,7 +1133,7 @@ export class DebugService implements debug.IDebugService {
this.toDisposeOnSessionEnd.set(session.getId(), lifecycle.dispose(this.toDisposeOnSessionEnd.get(session.getId())));
const focusedProcess = this.viewModel.focusedProcess;
if (focusedProcess && focusedProcess.getId() === session.getId()) {
this.focusStackFrameAndEvaluate(null).done(null, errors.onUnexpectedError);
this.focusStackFrame(null);
}
this.updateStateAndEmit(session.getId(), debug.State.Inactive);
@@ -1146,11 +1181,6 @@ export class DebugService implements debug.IDebugService {
if (!session.readyForBreakpoints) {
return TPromise.as(null);
}
if (this.textFileService.isDirty(modelUri)) {
// Only send breakpoints for a file once it is not dirty #8077
this.breakpointsToSendOnResourceSaved.add(modelUri.toString());
return TPromise.as(null);
}
const breakpointsToSend = this.model.getBreakpoints().filter(bp => this.model.areBreakpointsActivated() && bp.enabled && bp.uri.toString() === modelUri.toString());
@@ -1166,6 +1196,8 @@ export class DebugService implements debug.IDebugService {
if (breakpointsToSend.length && !rawSource.adapterData) {
rawSource.adapterData = breakpointsToSend[0].adapterData;
}
// Normalize all drive letters going out from vscode to debug adapters so we are consistent with our resolving #43959
rawSource.path = normalizeDriveLetter(rawSource.path);
return session.setBreakpoints({
source: rawSource,

View File

@@ -13,7 +13,7 @@ import { IDebugService, IStackFrame } from 'vs/workbench/parts/debug/common/debu
import { clipboard } from 'electron';
export class CopyValueAction extends Action {
static ID = 'workbench.debug.viewlet.action.copyValue';
static readonly ID = 'workbench.debug.viewlet.action.copyValue';
static LABEL = nls.localize('copyValue', "Copy Value");
constructor(id: string, label: string, private value: any, @IDebugService private debugService: IDebugService) {
@@ -34,8 +34,25 @@ export class CopyValueAction extends Action {
}
}
export class CopyEvaluatePathAction extends Action {
static readonly ID = 'workbench.debug.viewlet.action.copyEvaluatePath';
static LABEL = nls.localize('copyAsExpression', "Copy as Expression");
constructor(id: string, label: string, private value: any) {
super(id, label);
}
public run(): TPromise<any> {
if (this.value instanceof Variable) {
clipboard.writeText(this.value.evaluateName);
}
return TPromise.as(null);
}
}
export class CopyAction extends Action {
static ID = 'workbench.debug.action.copy';
static readonly ID = 'workbench.debug.action.copy';
static LABEL = nls.localize('copy', "Copy");
public run(): TPromise<any> {
@@ -45,7 +62,7 @@ export class CopyAction extends Action {
}
export class CopyAllAction extends Action {
static ID = 'workbench.debug.action.copyAll';
static readonly ID = 'workbench.debug.action.copyAll';
static LABEL = nls.localize('copyAll', "Copy All");
constructor(id: string, label: string, private tree: ITree) {
@@ -69,7 +86,7 @@ export class CopyAllAction extends Action {
}
export class CopyStackTraceAction extends Action {
static ID = 'workbench.action.debug.copyStackTrace';
static readonly ID = 'workbench.action.debug.copyStackTrace';
static LABEL = nls.localize('copyStackTrace', "Copy Call Stack");
public run(frame: IStackFrame): TPromise<any> {

View File

@@ -12,9 +12,7 @@ import objects = require('vs/base/common/objects');
import { Action } from 'vs/base/common/actions';
import errors = require('vs/base/common/errors');
import { TPromise } from 'vs/base/common/winjs.base';
import severity from 'vs/base/common/severity';
import stdfork = require('vs/base/node/stdFork');
import { IMessageService, CloseAction } from 'vs/platform/message/common/message';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { ITerminalService } from 'vs/workbench/parts/terminal/common/terminal';
import { ITerminalService as IExternalTerminalService } from 'vs/workbench/parts/execution/common/execution';
@@ -26,6 +24,7 @@ import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { ExtensionsChannelId } from 'vs/platform/extensionManagement/common/extensionManagement';
import { TerminalSupport } from 'vs/workbench/parts/debug/electron-browser/terminalSupport';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { INotificationService } from 'vs/platform/notification/common/notification';
export interface SessionExitedEvent extends debug.DebugEvent {
body: {
@@ -72,7 +71,7 @@ export class RawDebugSession extends V8Protocol implements debug.ISession {
private adapter: Adapter,
public customTelemetryService: ITelemetryService,
public root: IWorkspaceFolder,
@IMessageService private messageService: IMessageService,
@INotificationService private notificationService: INotificationService,
@ITelemetryService private telemetryService: ITelemetryService,
@IOutputService private outputService: IOutputService,
@ITerminalService private terminalService: ITerminalService,
@@ -166,7 +165,7 @@ export class RawDebugSession extends V8Protocol implements debug.ISession {
if (error && error.sendTelemetry) {
/* __GDPR__
"debugProtocolErrorResponse" : {
"error" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
"error" : { "classification": "CallstackOrException", "purpose": "FeatureInsight" }
}
*/
this.telemetryService.publicLog('debugProtocolErrorResponse', { error: telemetryMessage });
@@ -183,7 +182,7 @@ export class RawDebugSession extends V8Protocol implements debug.ISession {
if (error && error.url) {
const label = error.urlLabel ? error.urlLabel : nls.localize('moreInfo', "More Info");
return TPromise.wrapError<R>(errors.create(userMessage, {
actions: [CloseAction, new Action('debug.moreInfo', label, null, true, () => {
actions: [new Action('debug.moreInfo', label, null, true, () => {
window.open(error.url);
return TPromise.as(null);
})]
@@ -206,6 +205,9 @@ export class RawDebugSession extends V8Protocol implements debug.ISession {
if (event.event === 'initialized') {
this.readyForBreakpoints = true;
this._onDidInitialize.fire(event);
} else if (event.event === 'capabilities' && event.body) {
const capabilites = (<DebugProtocol.CapabilitiesEvent>event).body.capabilities;
this._capabilities = objects.mixin(this._capabilities, capabilites);
} else if (event.event === 'stopped') {
this.emittedStopped = true;
this._onDidStop.fire(<DebugProtocol.StoppedEvent>event);
@@ -524,7 +526,7 @@ export class RawDebugSession extends V8Protocol implements debug.ISession {
}
protected onServerError(err: Error): void {
this.messageService.show(severity.Error, nls.localize('stoppingDebugAdapter', "{0}. Stopping the debug adapter.", err.message));
this.notificationService.error(nls.localize('stoppingDebugAdapter', "{0}. Stopping the debug adapter.", err.message));
this.stopServer().done(null, errors.onUnexpectedError);
}
@@ -532,7 +534,7 @@ export class RawDebugSession extends V8Protocol implements debug.ISession {
this.serverProcess = null;
this.cachedInitServer = null;
if (!this.disconnected) {
this.messageService.show(severity.Error, nls.localize('debugAdapterCrash', "Debug adapter process has terminated unexpectedly"));
this.notificationService.error(nls.localize('debugAdapterCrash', "Debug adapter process has terminated unexpectedly"));
}
this.onEvent({ event: 'exit', type: 'event', seq: 0 });
}

View File

@@ -18,7 +18,7 @@ import { KeyCode } from 'vs/base/common/keyCodes';
import { ITree, ITreeOptions } from 'vs/base/parts/tree/browser/tree';
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 { ITextModel } from 'vs/editor/common/model';
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
import { Position } from 'vs/editor/common/core/position';
import * as modes from 'vs/editor/common/modes';
@@ -41,15 +41,16 @@ 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 { WorkbenchTree } from 'vs/platform/list/browser/listService';
import { memoize } from 'vs/base/common/decorators';
import { dispose } from 'vs/base/common/lifecycle';
import { OpenMode, ClickBehavior } from 'vs/base/parts/tree/browser/treeDefaults';
const $ = dom.$;
const replTreeOptions: ITreeOptions = {
twistiePixels: 20,
ariaLabel: nls.localize('replAriaLabel', "Read Eval Print Loop Panel"),
keyboardSupport: false
ariaLabel: nls.localize('replAriaLabel', "Read Eval Print Loop Panel")
};
const HISTORY_STORAGE_KEY = 'debug.repl.history';
@@ -82,6 +83,7 @@ export class Repl extends Panel implements IPrivateReplService {
private actions: IAction[];
private dimension: Dimension;
private replInputHeight: number;
private model: ITextModel;
constructor(
@debug.IDebugService private debugService: debug.IDebugService,
@@ -91,8 +93,7 @@ export class Repl extends Panel implements IPrivateReplService {
@IPanelService private panelService: IPanelService,
@IThemeService protected themeService: IThemeService,
@IModelService private modelService: IModelService,
@IContextKeyService private contextKeyService: IContextKeyService,
@IListService private listService: IListService
@IContextKeyService private contextKeyService: IContextKeyService
) {
super(debug.REPL_ID, telemetryService, themeService);
@@ -118,7 +119,7 @@ export class Repl extends Panel implements IPrivateReplService {
this.refreshTimeoutHandle = null;
const previousScrollPosition = this.tree.getScrollPosition();
this.tree.refresh().then(() => {
if (previousScrollPosition === 1 || previousScrollPosition === 0) {
if (previousScrollPosition === 1) {
// Only scroll if we were scrolled all the way down before tree refreshed #10486
this.tree.setScrollPosition(1);
}
@@ -134,15 +135,15 @@ export class Repl extends Panel implements IPrivateReplService {
this.createReplInput(this.container);
this.renderer = this.instantiationService.createInstance(ReplExpressionsRenderer);
const controller = this.instantiationService.createInstance(ReplExpressionsController, new ReplExpressionsActionProvider(this.instantiationService), MenuId.DebugConsoleContext);
const controller = this.instantiationService.createInstance(ReplExpressionsController, new ReplExpressionsActionProvider(this.instantiationService, this.replInput), MenuId.DebugConsoleContext, { openMode: OpenMode.SINGLE_CLICK, clickBehavior: ClickBehavior.ON_MOUSE_UP /* do not change, to preserve focus behaviour in input field */ });
controller.toFocusOnClick = this.replInput;
this.tree = new WorkbenchTree(this.treeContainer, {
this.tree = this.instantiationService.createInstance(WorkbenchTree, this.treeContainer, {
dataSource: new ReplExpressionsDataSource(),
renderer: this.renderer,
accessibilityProvider: new ReplExpressionsAccessibilityProvider(),
controller
}, replTreeOptions, this.contextKeyService, this.listService, this.themeService);
}, replTreeOptions);
if (!Repl.HISTORY) {
Repl.HISTORY = new ReplHistory(JSON.parse(this.storageService.get(HISTORY_STORAGE_KEY, StorageScope.WORKSPACE, '[]')));
@@ -151,6 +152,17 @@ export class Repl extends Panel implements IPrivateReplService {
return this.tree.setInput(this.debugService.getModel());
}
public setVisible(visible: boolean): TPromise<void> {
if (!visible) {
dispose(this.model);
} else {
this.model = this.modelService.createModel('', null, uri.parse(`${debug.DEBUG_SCHEME}:input`));
this.replInput.setModel(this.model);
}
return super.setVisible(visible);
}
private createReplInput(container: HTMLElement): void {
this.replInputContainer = dom.append(container, $('.repl-input-wrapper'));
@@ -165,12 +177,10 @@ export class Repl extends Panel implements IPrivateReplService {
const scopedInstantiationService = this.instantiationService.createChild(new ServiceCollection(
[IContextKeyService, scopedContextKeyService], [IPrivateReplService, this]));
this.replInput = scopedInstantiationService.createInstance(ReplInputEditor, this.replInputContainer, this.getReplInputOptions());
const model = this.modelService.createModel('', null, uri.parse(`${debug.DEBUG_SCHEME}:input`));
this.replInput.setModel(model);
modes.SuggestRegistry.register({ scheme: debug.DEBUG_SCHEME }, {
triggerCharacters: ['.'],
provideCompletionItems: (model: IReadOnlyModel, position: Position, _context: modes.SuggestContext, token: CancellationToken): Thenable<modes.ISuggestResult> => {
provideCompletionItems: (model: ITextModel, position: Position, _context: modes.SuggestContext, token: CancellationToken): Thenable<modes.ISuggestResult> => {
const word = this.replInput.getModel().getWordAtPosition(position);
const overwriteBefore = word ? word.word.length : 0;
const text = this.replInput.getModel().getLineContent(position.lineNumber);

View File

@@ -9,7 +9,7 @@ import { IAction } from 'vs/base/common/actions';
import * as lifecycle from 'vs/base/common/lifecycle';
import * as errors from 'vs/base/common/errors';
import { isFullWidthCharacter, removeAnsiEscapeCodes, endsWith } from 'vs/base/common/strings';
import { IActionItem } from 'vs/base/browser/ui/actionbar/actionbar';
import { IActionItem, Separator } from 'vs/base/browser/ui/actionbar/actionbar';
import * as dom from 'vs/base/browser/dom';
import severity from 'vs/base/common/severity';
import { IMouseEvent } from 'vs/base/browser/mouseEvent';
@@ -17,8 +17,8 @@ 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/baseDebugView';
import { ClearReplAction } from 'vs/workbench/parts/debug/browser/debugActions';
import { renderVariable, renderExpressionValue, IVariableTemplateData, BaseDebugController } from 'vs/workbench/parts/debug/browser/baseDebugView';
import { ClearReplAction, ReplCollapseAllAction } 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';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
@@ -417,7 +417,7 @@ export class ReplExpressionsAccessibilityProvider implements IAccessibilityProvi
export class ReplExpressionsActionProvider implements IActionProvider {
constructor(private instantiationService: IInstantiationService) {
constructor(private instantiationService: IInstantiationService, private toFocus: { focus(): void }) {
// noop
}
@@ -437,6 +437,8 @@ export class ReplExpressionsActionProvider implements IActionProvider {
const actions: IAction[] = [];
actions.push(new CopyAction(CopyAction.ID, CopyAction.LABEL));
actions.push(new CopyAllAction(CopyAllAction.ID, CopyAllAction.LABEL, tree));
actions.push(new ReplCollapseAllAction(tree, this.toFocus));
actions.push(new Separator());
actions.push(this.instantiationService.createInstance(ClearReplAction, ClearReplAction.ID, ClearReplAction.LABEL));
return TPromise.as(actions);

View File

@@ -1,112 +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 { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService';
import { localize } from 'vs/nls';
import { registerColor, contrastBorder } from 'vs/platform/theme/common/colorRegistry';
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { IPartService, Parts } from 'vs/workbench/services/part/common/partService';
import { IDebugService, State } from 'vs/workbench/parts/debug/common/debug';
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { STATUS_BAR_NO_FOLDER_BACKGROUND, STATUS_BAR_NO_FOLDER_FOREGROUND, STATUS_BAR_BACKGROUND, Themable, STATUS_BAR_FOREGROUND, STATUS_BAR_NO_FOLDER_BORDER, STATUS_BAR_BORDER } from 'vs/workbench/common/theme';
import { addClass, removeClass } from 'vs/base/browser/dom';
// colors for theming
export const STATUS_BAR_DEBUGGING_BACKGROUND = registerColor('statusBar.debuggingBackground', {
dark: '#CC6633',
light: '#CC6633',
hc: '#CC6633'
}, localize('statusBarDebuggingBackground', "Status bar background color when a program is being debugged. The status bar is shown in the bottom of the window"));
export const STATUS_BAR_DEBUGGING_FOREGROUND = registerColor('statusBar.debuggingForeground', {
dark: STATUS_BAR_FOREGROUND,
light: STATUS_BAR_FOREGROUND,
hc: STATUS_BAR_FOREGROUND
}, localize('statusBarDebuggingForeground', "Status bar foreground color when a program is being debugged. The status bar is shown in the bottom of the window"));
export const STATUS_BAR_DEBUGGING_BORDER = registerColor('statusBar.debuggingBorder', {
dark: STATUS_BAR_BORDER,
light: STATUS_BAR_BORDER,
hc: STATUS_BAR_BORDER
}, 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 {
constructor(
@IThemeService themeService: IThemeService,
@IDebugService private debugService: IDebugService,
@IWorkspaceContextService private contextService: IWorkspaceContextService,
@IPartService private partService: IPartService
) {
super(themeService);
this.registerListeners();
}
private registerListeners(): void {
this.toUnbind.push(this.debugService.onDidChangeState(state => this.updateStyles()));
this.toUnbind.push(this.contextService.onDidChangeWorkbenchState(state => this.updateStyles()));
}
protected updateStyles(): void {
super.updateStyles();
const container = this.partService.getContainer(Parts.STATUSBAR_PART);
if (this.isDebugging()) {
addClass(container, 'debugging');
} else {
removeClass(container, 'debugging');
}
container.style.backgroundColor = this.getColor(this.getColorKey(STATUS_BAR_NO_FOLDER_BACKGROUND, STATUS_BAR_DEBUGGING_BACKGROUND, STATUS_BAR_BACKGROUND));
container.style.color = this.getColor(this.getColorKey(STATUS_BAR_NO_FOLDER_FOREGROUND, STATUS_BAR_DEBUGGING_FOREGROUND, STATUS_BAR_FOREGROUND));
const borderColor = this.getColor(this.getColorKey(STATUS_BAR_NO_FOLDER_BORDER, STATUS_BAR_DEBUGGING_BORDER, STATUS_BAR_BORDER)) || this.getColor(contrastBorder);
container.style.borderTopWidth = borderColor ? '1px' : null;
container.style.borderTopStyle = borderColor ? 'solid' : null;
container.style.borderTopColor = borderColor;
}
private getColorKey(noFolderColor: string, debuggingColor: string, normalColor: string): string {
// Not debugging
if (!this.isDebugging()) {
if (this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY) {
return normalColor;
}
return noFolderColor;
}
// Debugging
return debuggingColor;
}
private isDebugging(): boolean {
if (this.debugService.state === State.Inactive || this.debugService.state === State.Initializing) {
return false;
}
if (this.isRunningWithoutDebug()) {
return false;
}
return true;
}
private isRunningWithoutDebug(): boolean {
const process = this.debugService.getViewModel().focusedProcess;
return process && process.configuration && process.configuration.noDebug;
}
}
registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
const statusBarItemDebuggingForeground = theme.getColor(STATUS_BAR_DEBUGGING_FOREGROUND);
if (statusBarItemDebuggingForeground) {
collector.addRule(`.monaco-workbench > .part.statusbar.debugging > .statusbar-item .mask-icon { background-color: ${statusBarItemDebuggingForeground} !important; }`);
}
});

View File

@@ -5,6 +5,7 @@
import * as nls from 'vs/nls';
import * as platform from 'vs/base/common/platform';
import cp = require('child_process');
import { IDisposable } from 'vs/base/common/lifecycle';
import { TPromise } from 'vs/base/common/winjs.base';
import { ITerminalService, ITerminalInstance, ITerminalConfiguration } from 'vs/workbench/parts/terminal/common/terminal';
@@ -24,11 +25,6 @@ export class TerminalSupport {
return nativeTerminalService.runInTerminal(args.title, args.cwd, args.args, args.env || {});
}
let delay = 0;
if (!TerminalSupport.integratedTerminalInstance) {
TerminalSupport.integratedTerminalInstance = terminalService.createInstance({ name: args.title || nls.localize('debug.terminal.title', "debuggee") });
delay = 2000; // delay the first sendText so that the newly created terminal is ready.
}
if (!TerminalSupport.terminalDisposedListener) {
// React on terminal disposed and check if that is the debug terminal #12956
TerminalSupport.terminalDisposedListener = terminalService.onInstanceDisposed(terminal => {
@@ -37,22 +33,49 @@ export class TerminalSupport {
}
});
}
terminalService.setActiveInstance(TerminalSupport.integratedTerminalInstance);
let t = TerminalSupport.integratedTerminalInstance;
if ((t && this.isBusy(t)) || !t) {
t = terminalService.createInstance({ name: args.title || nls.localize('debug.terminal.title', "debuggee") });
TerminalSupport.integratedTerminalInstance = t;
}
terminalService.setActiveInstance(t);
terminalService.showPanel(true);
return new TPromise<void>((c, e) => {
const command = this.prepareCommand(args, configurationService);
t.sendText(command, true);
setTimeout(() => {
if (TerminalSupport.integratedTerminalInstance) {
const command = this.prepareCommand(args, configurationService);
TerminalSupport.integratedTerminalInstance.sendText(command, true);
c(void 0);
return TPromise.as(void 0);
}
private static isBusy(t: ITerminalInstance): boolean {
if (t.processId) {
try {
// if shell has at least one child process, assume that shell is busy
if (platform.isWindows) {
const result = cp.spawnSync('wmic', ['process', 'get', 'ParentProcessId']);
if (result.stdout) {
const pids = result.stdout.toString().split('\r\n');
if (!pids.some(p => parseInt(p) === t.processId)) {
return false;
}
}
} else {
e(new Error(nls.localize('debug.terminal.not.available.error', "Integrated terminal not available")));
const result = cp.spawnSync('/usr/bin/pgrep', ['-lP', String(t.processId)]);
if (result.stdout) {
const r = result.stdout.toString().trim();
if (r.length === 0 || r.indexOf(' tmux') >= 0) { // ignore 'tmux'; see #43683
return false;
}
}
}
}, delay);
});
}
catch (e) {
// silently ignore
}
}
// fall back to safe side
return true;
}
private static prepareCommand(args: DebugProtocol.RunInTerminalRequestArguments, configurationService: IConfigurationService): string {
@@ -94,7 +117,8 @@ export class TerminalSupport {
quote = (s: string) => {
s = s.replace(/\'/g, '\'\'');
return s.indexOf(' ') >= 0 || s.indexOf('\'') >= 0 || s.indexOf('"') >= 0 ? `'${s}'` : s;
return `'${s}'`;
//return s.indexOf(' ') >= 0 || s.indexOf('\'') >= 0 || s.indexOf('"') >= 0 ? `'${s}'` : s;
};
if (args.cwd) {

View File

@@ -7,8 +7,7 @@ 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 { 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';
@@ -17,19 +16,19 @@ import { IContextMenuService, IContextViewService } from 'vs/platform/contextvie
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 { twistiePixels, renderViewTree, IVariableTemplateData, BaseDebugController, renderRenameBox, renderVariable } from 'vs/workbench/parts/debug/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 { CopyValueAction, CopyEvaluatePathAction } 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';
import { WorkbenchTree } from 'vs/platform/list/browser/listService';
import { OpenMode } from 'vs/base/parts/tree/browser/treeDefaults';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
const $ = dom.$;
@@ -40,6 +39,7 @@ export class VariablesView extends TreeViewsViewletPanel {
private settings: any;
private expandedElements: any[];
private needsRefresh: boolean;
private treeContainer: HTMLElement;
constructor(
options: IViewletViewOptions,
@@ -47,11 +47,9 @@ export class VariablesView extends TreeViewsViewletPanel {
@IDebugService private debugService: IDebugService,
@IKeybindingService keybindingService: IKeybindingService,
@IInstantiationService private instantiationService: IInstantiationService,
@IListService private listService: IListService,
@IContextKeyService private contextKeyService: IContextKeyService,
@IThemeService private themeService: IThemeService
@IConfigurationService configurationService: IConfigurationService
) {
super({ ...(options as IViewOptions), ariaHeaderLabel: nls.localize('variablesSection', "Variables Section") }, keybindingService, contextMenuService);
super({ ...(options as IViewOptions), ariaHeaderLabel: nls.localize('variablesSection', "Variables Section") }, keybindingService, contextMenuService, configurationService);
this.settings = options.viewletSettings;
this.expandedElements = [];
@@ -63,8 +61,6 @@ export class VariablesView extends TreeViewsViewletPanel {
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;
@@ -88,16 +84,15 @@ export class VariablesView extends TreeViewsViewletPanel {
dom.addClass(container, 'debug-variables');
this.treeContainer = renderViewTree(container);
this.tree = new WorkbenchTree(this.treeContainer, {
this.tree = this.instantiationService.createInstance(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)
controller: this.instantiationService.createInstance(VariablesController, new VariablesActionProvider(this.debugService, this.keybindingService), MenuId.DebugVariablesContext, { openMode: OpenMode.SINGLE_CLICK })
}, {
ariaLabel: nls.localize('variablesAriaTreeLabel', "Debug Variables"),
twistiePixels,
keyboardSupport: false
}, this.contextKeyService, this.listService, this.themeService);
twistiePixels
});
CONTEXT_VARIABLES_FOCUSED.bindTo(this.tree.contextKeyService);
@@ -106,7 +101,7 @@ export class VariablesView extends TreeViewsViewletPanel {
this.tree.setInput(viewModel);
const collapseAction = new CollapseAction(this.tree, false, 'explorer-action collapse-explorer');
this.toolbar.setActions(prepareActions([collapseAction]))();
this.toolbar.setActions([collapseAction])();
this.disposables.push(viewModel.onDidFocusStackFrame(sf => {
if (!this.isVisible() || !this.isExpanded()) {
@@ -127,21 +122,19 @@ export class VariablesView extends TreeViewsViewletPanel {
}));
this.disposables.push(this.debugService.getViewModel().onDidSelectExpression(expression => {
if (!expression || !(expression instanceof Variable)) {
return;
if (expression instanceof Variable) {
this.tree.refresh(expression, false).done(null, errors.onUnexpectedError);
}
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);
}));
}
layoutBody(size: number): void {
if (this.treeContainer) {
this.treeContainer.style.height = size + 'px';
}
super.layoutBody(size);
}
public setExpanded(expanded: boolean): void {
super.setExpanded(expanded);
if (expanded && this.needsRefresh) {
@@ -187,6 +180,7 @@ class VariablesActionProvider implements IActionProvider {
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 CopyEvaluatePathAction(CopyEvaluatePathAction.ID, CopyEvaluatePathAction.LABEL, variable));
actions.push(new Separator());
actions.push(new AddToWatchExpressionsAction(AddToWatchExpressionsAction.ID, AddToWatchExpressionsAction.LABEL, variable, this.debugService, this.keybindingService));

View File

@@ -9,8 +9,7 @@ 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 { 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';
@@ -20,17 +19,16 @@ import { IContextMenuService, IContextViewService } from 'vs/platform/contextvie
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';
import { DefaultDragAndDrop, OpenMode, ClickBehavior } from 'vs/base/parts/tree/browser/treeDefaults';
import { IVariableTemplateData, renderVariable, renderRenameBox, renderExpressionValue, BaseDebugController, twistiePixels, renderViewTree } from 'vs/workbench/parts/debug/browser/baseDebugView';
import { WorkbenchTree } from 'vs/platform/list/browser/listService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
const $ = dom.$;
const MAX_VALUE_RENDER_LENGTH_IN_VIEWLET = 1024;
@@ -39,7 +37,7 @@ export class WatchExpressionsView extends TreeViewsViewletPanel {
private static readonly MEMENTO = 'watchexpressionsview.memento';
private onWatchExpressionsUpdatedScheduler: RunOnceScheduler;
private toReveal: IExpression;
private treeContainer: HTMLElement;
private settings: any;
private needsRefresh: boolean;
@@ -48,26 +46,15 @@ export class WatchExpressionsView extends TreeViewsViewletPanel {
@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
@IConfigurationService configurationService: IConfigurationService
) {
super({ ...(options as IViewOptions), ariaHeaderLabel: nls.localize('expressionsSection', "Expressions Section") }, keybindingService, contextMenuService);
super({ ...(options as IViewOptions), ariaHeaderLabel: nls.localize('expressionsSection', "Expressions Section") }, keybindingService, contextMenuService, configurationService);
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);
this.tree.refresh().done(undefined, errors.onUnexpectedError);
}, 50);
}
@@ -76,17 +63,16 @@ export class WatchExpressionsView extends TreeViewsViewletPanel {
this.treeContainer = renderViewTree(container);
const actionProvider = new WatchExpressionsActionProvider(this.debugService, this.keybindingService);
this.tree = new WorkbenchTree(this.treeContainer, {
dataSource: new WatchExpressionsDataSource(),
this.tree = this.instantiationService.createInstance(WorkbenchTree, this.treeContainer, {
dataSource: new WatchExpressionsDataSource(this.debugService),
renderer: this.instantiationService.createInstance(WatchExpressionsRenderer),
accessibilityProvider: new WatchExpressionsAccessibilityProvider(),
controller: this.instantiationService.createInstance(WatchExpressionsController, actionProvider, MenuId.DebugWatchContext),
controller: this.instantiationService.createInstance(WatchExpressionsController, actionProvider, MenuId.DebugWatchContext, { clickBehavior: ClickBehavior.ON_MOUSE_UP /* do not change to not break DND */, openMode: OpenMode.SINGLE_CLICK }),
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);
twistiePixels
});
CONTEXT_WATCH_EXPRESSIONS_FOCUSED.bindTo(this.tree.contextKeyService);
@@ -95,7 +81,7 @@ export class WatchExpressionsView extends TreeViewsViewletPanel {
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.toolbar.setActions([addWatchExpressionAction, collapseAction, removeAllWatchExpressionsAction])();
this.disposables.push(this.debugService.getModel().onDidChangeWatchExpressions(we => {
if (!this.isExpanded() || !this.isVisible()) {
@@ -103,28 +89,35 @@ export class WatchExpressionsView extends TreeViewsViewletPanel {
return;
}
this.tree.refresh().done(() => {
return we instanceof Expression ? this.tree.reveal(we) : TPromise.as(true);
}, errors.onUnexpectedError);
}));
this.disposables.push(this.debugService.getViewModel().onDidFocusStackFrame(() => {
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;
if (expression instanceof Expression) {
this.tree.refresh(expression, false).done(null, errors.onUnexpectedError);
}
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);
}));
}
layoutBody(size: number): void {
if (this.treeContainer) {
this.treeContainer.style.height = size + 'px';
}
super.layoutBody(size);
}
public setExpanded(expanded: boolean): void {
super.setExpanded(expanded);
if (expanded && this.needsRefresh) {
@@ -200,6 +193,10 @@ class WatchExpressionsActionProvider implements IActionProvider {
class WatchExpressionsDataSource implements IDataSource {
constructor(private debugService: IDebugService) {
// noop
}
public getId(tree: ITree, element: any): string {
return element.getId();
}
@@ -215,7 +212,9 @@ class WatchExpressionsDataSource implements IDataSource {
public getChildren(tree: ITree, element: any): TPromise<any> {
if (element instanceof Model) {
return TPromise.as((<Model>element).getWatchExpressions());
const viewModel = this.debugService.getViewModel();
return TPromise.join(element.getWatchExpressions().map(we =>
we.name ? we.evaluate(viewModel.focusedProcess, viewModel.focusedStackFrame, 'watch').then(() => we) : TPromise.as(we)));
}
let expression = <Expression>element;
@@ -291,7 +290,7 @@ class WatchExpressionsRenderer implements IRenderer {
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)) {
if ((selectedExpression instanceof Expression && selectedExpression.getId() === watchExpression.getId())) {
renderRenameBox(this.debugService, this.contextViewService, this.themeService, tree, watchExpression, data.expression, {
initialValue: watchExpression.name,
placeholder: nls.localize('watchExpressionPlaceholder', "Expression to watch"),
@@ -344,6 +343,10 @@ class WatchExpressionsController extends BaseDebugController {
const expression = <IExpression>element;
this.debugService.getViewModel().setSelectedExpression(expression);
return true;
} else if (element instanceof Model && event.detail === 2) {
// Double click in watch panel triggers to add a new watch expression
this.debugService.addWatchExpression();
return true;
}
return super.onLeftClick(tree, element, event);