Merge from vscode c58aaab8a1cc22a7139b761166a0d4f37d41e998 (#7880)

* Merge from vscode c58aaab8a1cc22a7139b761166a0d4f37d41e998

* fix pipelines

* fix strict-null-checks

* add missing files
This commit is contained in:
Anthony Dresser
2019-10-21 22:12:22 -07:00
committed by GitHub
parent 7c9be74970
commit 1e22f47304
913 changed files with 18898 additions and 16536 deletions

View File

@@ -12,12 +12,12 @@ import { IAction, Action } from 'vs/base/common/actions';
import { Range } from 'vs/editor/common/core/range';
import { ICodeEditor, IEditorMouseEvent, MouseTargetType, IContentWidget, IActiveCodeEditor, IContentWidgetPosition, ContentWidgetPositionPreference } from 'vs/editor/browser/editorBrowser';
import { registerEditorContribution } from 'vs/editor/browser/editorExtensions';
import { IModelDecorationOptions, IModelDeltaDecoration, TrackedRangeStickiness, ITextModel } from 'vs/editor/common/model';
import { IModelDecorationOptions, IModelDeltaDecoration, TrackedRangeStickiness, ITextModel, OverviewRulerLane, IModelDecorationOverviewRulerOptions } from 'vs/editor/common/model';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { RemoveBreakpointAction } from 'vs/workbench/contrib/debug/browser/debugActions';
import { IDebugService, IBreakpoint, CONTEXT_BREAKPOINT_WIDGET_VISIBLE, BreakpointWidgetContext, BREAKPOINT_EDITOR_CONTRIBUTION_ID, IBreakpointEditorContribution, IBreakpointUpdateData } from 'vs/workbench/contrib/debug/common/debug';
import { IDebugService, IBreakpoint, CONTEXT_BREAKPOINT_WIDGET_VISIBLE, BreakpointWidgetContext, BREAKPOINT_EDITOR_CONTRIBUTION_ID, IBreakpointEditorContribution, IBreakpointUpdateData, IDebugConfiguration } from 'vs/workbench/contrib/debug/common/debug';
import { IMarginData } from 'vs/editor/browser/controller/mouseTarget';
import { ContextSubMenu } from 'vs/base/browser/contextmenu';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
@@ -30,6 +30,8 @@ import { memoize } from 'vs/base/common/decorators';
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
import { distinct } from 'vs/base/common/arrays';
import { RunOnceScheduler } from 'vs/base/common/async';
import { EditorOption } from 'vs/editor/common/config/editorOptions';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
const $ = dom.$;
@@ -45,7 +47,7 @@ const breakpointHelperDecoration: IModelDecorationOptions = {
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges
};
function createBreakpointDecorations(model: ITextModel, breakpoints: ReadonlyArray<IBreakpoint>, debugService: IDebugService): { range: Range; options: IModelDecorationOptions; }[] {
function createBreakpointDecorations(model: ITextModel, breakpoints: ReadonlyArray<IBreakpoint>, debugService: IDebugService, debugSettings: IDebugConfiguration): { range: Range; options: IModelDecorationOptions; }[] {
const result: { range: Range; options: IModelDecorationOptions; }[] = [];
breakpoints.forEach((breakpoint) => {
if (breakpoint.lineNumber <= model.getLineCount()) {
@@ -56,7 +58,7 @@ function createBreakpointDecorations(model: ITextModel, breakpoints: ReadonlyArr
);
result.push({
options: getBreakpointDecorationOptions(model, breakpoint, debugService),
options: getBreakpointDecorationOptions(model, breakpoint, debugService, debugSettings),
range
});
}
@@ -65,7 +67,7 @@ function createBreakpointDecorations(model: ITextModel, breakpoints: ReadonlyArr
return result;
}
function getBreakpointDecorationOptions(model: ITextModel, breakpoint: IBreakpoint, debugService: IDebugService): IModelDecorationOptions {
function getBreakpointDecorationOptions(model: ITextModel, breakpoint: IBreakpoint, debugService: IDebugService, debugSettings: IDebugConfiguration): IModelDecorationOptions {
const { className, message } = getBreakpointMessageAndClassName(debugService, breakpoint);
let glyphMarginHoverMessage: MarkdownString | undefined;
@@ -78,11 +80,22 @@ function getBreakpointDecorationOptions(model: ITextModel, breakpoint: IBreakpoi
}
}
let overviewRulerDecoration: IModelDecorationOverviewRulerOptions | null;
if (debugSettings.showBreakpointsInOverviewRuler) {
overviewRulerDecoration = {
color: 'rgb(124, 40, 49)',
position: OverviewRulerLane.Left
};
} else {
overviewRulerDecoration = null;
}
return {
glyphMarginClassName: className,
glyphMarginHoverMessage,
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
beforeContentClassName: breakpoint.column ? `debug-breakpoint-placeholder` : undefined
beforeContentClassName: breakpoint.column ? `debug-breakpoint-placeholder` : undefined,
overviewRuler: overviewRulerDecoration
};
}
@@ -138,16 +151,13 @@ class BreakpointEditorContribution implements IBreakpointEditorContribution {
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IContextKeyService contextKeyService: IContextKeyService,
@IDialogService private readonly dialogService: IDialogService,
@IConfigurationService private readonly configurationService: IConfigurationService
) {
this.breakpointWidgetVisible = CONTEXT_BREAKPOINT_WIDGET_VISIBLE.bindTo(contextKeyService);
this.registerListeners();
this.setDecorationsScheduler = new RunOnceScheduler(() => this.setDecorations(), 30);
}
getId(): string {
return BREAKPOINT_EDITOR_CONTRIBUTION_ID;
}
private registerListeners(): void {
this.toDispose.push(this.editor.onMouseDown(async (e: IEditorMouseEvent) => {
const data = e.target.detail as IMarginData;
@@ -220,7 +230,7 @@ class BreakpointEditorContribution implements IBreakpointEditorContribution {
this.toDispose.push(this.editor.onMouseMove((e: IEditorMouseEvent) => {
let showBreakpointHintAtLineNumber = -1;
const model = this.editor.getModel();
if (model && e.target.position && e.target.type === MouseTargetType.GUTTER_GLYPH_MARGIN && this.debugService.getConfigurationManager().canSetBreakpointsIn(model) &&
if (model && e.target.position && (e.target.type === MouseTargetType.GUTTER_GLYPH_MARGIN || e.target.type === MouseTargetType.GUTTER_LINE_NUMBERS) && this.debugService.getConfigurationManager().canSetBreakpointsIn(model) &&
this.marginFreeFromNonDebugDecorations(e.target.position.lineNumber)) {
const data = e.target.detail as IMarginData;
if (!data.isAfterLines) {
@@ -243,6 +253,11 @@ class BreakpointEditorContribution implements IBreakpointEditorContribution {
}
}));
this.toDispose.push(this.editor.onDidChangeModelDecorations(() => this.onModelDecorationsChanged()));
this.toDispose.push(this.configurationService.onDidChangeConfiguration(async (e) => {
if (e.affectsConfiguration('debug.showBreakpointsInOverviewRuler')) {
await this.setDecorations();
}
}));
}
private getContextMenuActions(breakpoints: ReadonlyArray<IBreakpoint>, uri: URI, lineNumber: number, column?: number): Array<IAction | ContextSubMenu> {
@@ -305,7 +320,7 @@ class BreakpointEditorContribution implements IBreakpointEditorContribution {
nls.localize('addConditionalBreakpoint', "Add Conditional Breakpoint..."),
undefined,
true,
() => Promise.resolve(this.showBreakpointWidget(lineNumber, column))
() => Promise.resolve(this.showBreakpointWidget(lineNumber, column, BreakpointWidgetContext.CONDITION))
));
actions.push(new Action(
'addLogPoint',
@@ -357,7 +372,8 @@ class BreakpointEditorContribution implements IBreakpointEditorContribution {
const activeCodeEditor = this.editor;
const model = activeCodeEditor.getModel();
const breakpoints = this.debugService.getModel().getBreakpoints({ uri: model.uri });
const desiredBreakpointDecorations = createBreakpointDecorations(model, breakpoints, this.debugService);
const debugSettings = this.configurationService.getValue<IDebugConfiguration>('debug');
const desiredBreakpointDecorations = createBreakpointDecorations(model, breakpoints, this.debugService, debugSettings);
try {
this.ignoreDecorationsChangedEvent = true;
@@ -542,6 +558,20 @@ class InlineBreakpointWidget implements IContentWidget, IDisposable {
onHide: () => dispose(actions)
});
}));
const updateSize = () => {
const lineHeight = this.editor.getOption(EditorOption.lineHeight);
this.domNode.style.height = `${lineHeight}px`;
this.domNode.style.width = `${Math.ceil(0.8 * lineHeight)}px`;
this.domNode.style.marginLeft = `${Math.ceil(0.35 * lineHeight)}px`;
};
updateSize();
this.toDispose.push(this.editor.onDidChangeConfiguration(c => {
if (c.hasChanged(EditorOption.fontSize) || c.hasChanged(EditorOption.lineHeight)) {
updateSize();
}
}));
}
@memoize
@@ -572,4 +602,4 @@ class InlineBreakpointWidget implements IContentWidget, IDisposable {
}
}
registerEditorContribution(BreakpointEditorContribution);
registerEditorContribution(BREAKPOINT_EDITOR_CONTRIBUTION_ID, BreakpointEditorContribution);

View File

@@ -54,8 +54,9 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi
private hitCountInput = '';
private logMessageInput = '';
private breakpoint: IBreakpoint | undefined;
private context: Context;
constructor(editor: ICodeEditor, private lineNumber: number, private column: number | undefined, private context: Context,
constructor(editor: ICodeEditor, private lineNumber: number, private column: number | undefined, context: Context | undefined,
@IContextViewService private readonly contextViewService: IContextViewService,
@IDebugService private readonly debugService: IDebugService,
@IThemeService private readonly themeService: IThemeService,
@@ -74,7 +75,7 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi
this.breakpoint = breakpoints.length ? breakpoints[0] : undefined;
}
if (this.context === undefined) {
if (context === undefined) {
if (this.breakpoint && !this.breakpoint.condition && !this.breakpoint.hitCondition && this.breakpoint.logMessage) {
this.context = Context.LOG_MESSAGE;
} else if (this.breakpoint && !this.breakpoint.condition && this.breakpoint.hitCondition) {
@@ -82,6 +83,8 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi
} else {
this.context = Context.CONDITION;
}
} else {
this.context = context;
}
this.toDispose.push(this.debugService.getModel().onDidChangeBreakpoints(e => {

View File

@@ -14,7 +14,7 @@ import { IContextMenuService, IContextViewService } from 'vs/platform/contextvie
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 { Constants } from 'vs/base/common/uint';
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
import { Separator } from 'vs/base/browser/ui/actionbar/actionbar';
import { IListVirtualDelegate, IListContextMenuEvent, IListRenderer } from 'vs/base/browser/ui/list/list';
@@ -161,21 +161,19 @@ export class BreakpointsView extends ViewletPanel {
const breakpointType = element instanceof Breakpoint && element.logMessage ? nls.localize('Logpoint', "Logpoint") : nls.localize('Breakpoint', "Breakpoint");
if (element instanceof Breakpoint || element instanceof FunctionBreakpoint) {
actions.push(new Action('workbench.action.debug.openEditorAndEditBreakpoint', nls.localize('editBreakpoint', "Edit {0}...", breakpointType), '', true, () => {
actions.push(new Action('workbench.action.debug.openEditorAndEditBreakpoint', nls.localize('editBreakpoint', "Edit {0}...", breakpointType), '', true, async () => {
if (element instanceof Breakpoint) {
return openBreakpointSource(element, false, false, this.debugService, this.editorService).then(editor => {
if (editor) {
const codeEditor = editor.getControl();
if (isCodeEditor(codeEditor)) {
codeEditor.getContribution<IBreakpointEditorContribution>(BREAKPOINT_EDITOR_CONTRIBUTION_ID).showBreakpointWidget(element.lineNumber, element.column);
}
const editor = await openBreakpointSource(element, false, false, this.debugService, this.editorService);
if (editor) {
const codeEditor = editor.getControl();
if (isCodeEditor(codeEditor)) {
codeEditor.getContribution<IBreakpointEditorContribution>(BREAKPOINT_EDITOR_CONTRIBUTION_ID).showBreakpointWidget(element.lineNumber, element.column);
}
});
}
} else {
this.debugService.getViewModel().setSelectedFunctionBreakpoint(element);
this.onBreakpointsChange();
}
this.debugService.getViewModel().setSelectedFunctionBreakpoint(element);
this.onBreakpointsChange();
return Promise.resolve(undefined);
}));
actions.push(new Separator());
}

View File

@@ -34,6 +34,7 @@ import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
import { isSessionAttach } from 'vs/workbench/contrib/debug/common/debugUtils';
import { STOP_ID, STOP_LABEL, DISCONNECT_ID, DISCONNECT_LABEL, RESTART_SESSION_ID, RESTART_LABEL, STEP_OVER_ID, STEP_OVER_LABEL, STEP_INTO_LABEL, STEP_INTO_ID, STEP_OUT_LABEL, STEP_OUT_ID, PAUSE_ID, PAUSE_LABEL, CONTINUE_ID, CONTINUE_LABEL } from 'vs/workbench/contrib/debug/browser/debugCommands';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { CollapseAction } from 'vs/workbench/browser/viewlet';
const $ = dom.$;
@@ -82,8 +83,16 @@ export class CallStackView extends ViewletPanel {
this.pauseMessageLabel.title = thread.stoppedDetails.text || '';
dom.toggleClass(this.pauseMessageLabel, 'exception', thread.stoppedDetails.reason === 'exception');
this.pauseMessage.hidden = false;
if (this.toolbar) {
this.toolbar.setActions([])();
}
} else {
this.pauseMessage.hidden = true;
if (this.toolbar) {
const collapseAction = new CollapseAction(this.tree, true, 'explorer-action collapse-explorer');
this.toolbar.setActions([collapseAction])();
}
}
this.needsRefresh = false;

View File

@@ -262,8 +262,14 @@ configurationRegistry.registerConfiguration({
},
'debug.onTaskErrors': {
enum: ['debugAnyway', 'showErrors', 'prompt'],
enumDescriptions: [nls.localize('debugAnyway', "Ignore task errors and start debugging."), nls.localize('showErrors', "Show the Problems view and do not start debugging."), nls.localize('prompt', "Prompt user.")],
description: nls.localize('debug.onTaskErrors', "Controls what to do when errors are encountered after running a preLaunchTask."),
default: 'prompt'
},
'debug.showBreakpointsInOverviewRuler': {
type: 'boolean',
description: nls.localize({ comment: ['This is the description for a setting'], key: 'showBreakpointsInOverviewRuler' }, "Controls whether breakpoints should be shown in the overview ruler."),
default: false
}
}
});

View File

@@ -36,7 +36,7 @@ export class StartDebugActionViewItem implements IActionViewItem {
private selected = 0;
constructor(
private context: any,
private context: unknown,
private action: IAction,
@IDebugService private readonly debugService: IDebugService,
@IThemeService private readonly themeService: IThemeService,
@@ -122,8 +122,8 @@ export class StartDebugActionViewItem implements IActionViewItem {
}
}));
this.toDispose.push(attachStylerCallback(this.themeService, { selectBorder }, colors => {
this.container.style.border = colors.selectBorder ? `1px solid ${colors.selectBorder}` : null;
selectBoxContainer.style.borderLeft = colors.selectBorder ? `1px solid ${colors.selectBorder}` : null;
this.container.style.border = colors.selectBorder ? `1px solid ${colors.selectBorder}` : '';
selectBoxContainer.style.borderLeft = colors.selectBorder ? `1px solid ${colors.selectBorder}` : '';
}));
this.updateOptions();

View File

@@ -12,23 +12,17 @@ import { Variable, Breakpoint, FunctionBreakpoint } from 'vs/workbench/contrib/d
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { IHistoryService } from 'vs/workbench/services/history/common/history';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { startDebugging } from 'vs/workbench/contrib/debug/common/debugUtils';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
export abstract class AbstractDebugAction extends Action {
protected toDispose: IDisposable[];
constructor(
id: string, label: string, cssClass: string,
@IDebugService protected debugService: IDebugService,
@IKeybindingService protected keybindingService: IKeybindingService,
) {
super(id, label, cssClass, false);
this.toDispose = [];
this.toDispose.push(this.debugService.onDidChangeState(state => this.updateEnablement(state)));
this._register(this.debugService.onDidChangeState(state => this.updateEnablement(state)));
this.updateLabel(label);
this.updateEnablement();
@@ -56,16 +50,11 @@ export abstract class AbstractDebugAction extends Action {
protected isEnabled(_: State): boolean {
return true;
}
dispose(): void {
super.dispose();
this.toDispose = dispose(this.toDispose);
}
}
export class ConfigureAction extends AbstractDebugAction {
static readonly ID = 'workbench.action.debug.configure';
static LABEL = nls.localize('openLaunchJson', "Open {0}", 'launch.json');
static readonly LABEL = nls.localize('openLaunchJson', "Open {0}", 'launch.json');
constructor(id: string, label: string,
@IDebugService debugService: IDebugService,
@@ -74,7 +63,7 @@ export class ConfigureAction extends AbstractDebugAction {
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService
) {
super(id, label, 'debug-action configure', debugService, keybindingService);
this.toDispose.push(debugService.getConfigurationManager().onDidSelectConfiguration(() => this.updateClass()));
this._register(debugService.getConfigurationManager().onDidSelectConfiguration(() => this.updateClass()));
this.updateClass();
}
@@ -92,19 +81,17 @@ export class ConfigureAction extends AbstractDebugAction {
this.class = configurationCount > 0 ? 'debug-action configure' : 'debug-action configure notification';
}
run(event?: any): Promise<any> {
async run(event?: any): Promise<any> {
if (this.contextService.getWorkbenchState() === WorkbenchState.EMPTY) {
this.notificationService.info(nls.localize('noFolderDebugConfig', "Please first open a folder in order to do advanced debug configuration."));
return Promise.resolve();
return;
}
const sideBySide = !!(event && (event.ctrlKey || event.metaKey));
const configurationManager = this.debugService.getConfigurationManager();
if (!configurationManager.selectedConfiguration.launch) {
configurationManager.selectConfiguration(configurationManager.getLaunches()[0]);
if (configurationManager.selectedConfiguration.launch) {
return configurationManager.selectedConfiguration.launch.openConfigFile(sideBySide, false);
}
return configurationManager.selectedConfiguration.launch!.openConfigFile(sideBySide, false);
}
}
@@ -116,18 +103,18 @@ export class StartAction extends AbstractDebugAction {
@IDebugService debugService: IDebugService,
@IKeybindingService keybindingService: IKeybindingService,
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
@IHistoryService private readonly historyService: IHistoryService
) {
super(id, label, 'debug-action start', debugService, keybindingService);
this.toDispose.push(this.debugService.getConfigurationManager().onDidSelectConfiguration(() => this.updateEnablement()));
this.toDispose.push(this.debugService.onDidNewSession(() => this.updateEnablement()));
this.toDispose.push(this.debugService.onDidEndSession(() => this.updateEnablement()));
this.toDispose.push(this.contextService.onDidChangeWorkbenchState(() => this.updateEnablement()));
this._register(this.debugService.getConfigurationManager().onDidSelectConfiguration(() => this.updateEnablement()));
this._register(this.debugService.onDidNewSession(() => this.updateEnablement()));
this._register(this.debugService.onDidEndSession(() => this.updateEnablement()));
this._register(this.contextService.onDidChangeWorkbenchState(() => this.updateEnablement()));
}
run(): Promise<boolean> {
return startDebugging(this.debugService, this.historyService, this.isNoDebug());
const { launch, name } = this.debugService.getConfigurationManager().selectedConfiguration;
return this.debugService.startDebugging(launch, name, { noDebug: this.isNoDebug() });
}
protected isNoDebug(): boolean {
@@ -165,7 +152,7 @@ export class RunAction extends StartAction {
export class SelectAndStartAction extends AbstractDebugAction {
static readonly ID = 'workbench.action.debug.selectandstart';
static LABEL = nls.localize('selectAndStartDebugging', "Select and Start Debugging");
static readonly LABEL = nls.localize('selectAndStartDebugging', "Select and Start Debugging");
constructor(id: string, label: string,
@IDebugService debugService: IDebugService,
@@ -182,7 +169,7 @@ export class SelectAndStartAction extends AbstractDebugAction {
export class RemoveBreakpointAction extends Action {
static readonly ID = 'workbench.debug.viewlet.action.removeBreakpoint';
static LABEL = nls.localize('removeBreakpoint', "Remove Breakpoint");
static readonly LABEL = nls.localize('removeBreakpoint', "Remove Breakpoint");
constructor(id: string, label: string, @IDebugService private readonly debugService: IDebugService) {
super(id, label, 'debug-action remove');
@@ -196,11 +183,11 @@ export class RemoveBreakpointAction extends Action {
export class RemoveAllBreakpointsAction extends AbstractDebugAction {
static readonly ID = 'workbench.debug.viewlet.action.removeAllBreakpoints';
static LABEL = nls.localize('removeAllBreakpoints', "Remove All Breakpoints");
static readonly LABEL = nls.localize('removeAllBreakpoints', "Remove All Breakpoints");
constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) {
super(id, label, 'debug-action remove-all', debugService, keybindingService);
this.toDispose.push(this.debugService.getModel().onDidChangeBreakpoints(() => this.updateEnablement()));
this._register(this.debugService.getModel().onDidChangeBreakpoints(() => this.updateEnablement()));
}
run(): Promise<any> {
@@ -215,11 +202,11 @@ export class RemoveAllBreakpointsAction extends AbstractDebugAction {
export class EnableAllBreakpointsAction extends AbstractDebugAction {
static readonly ID = 'workbench.debug.viewlet.action.enableAllBreakpoints';
static LABEL = nls.localize('enableAllBreakpoints', "Enable All Breakpoints");
static readonly LABEL = nls.localize('enableAllBreakpoints', "Enable All Breakpoints");
constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) {
super(id, label, 'debug-action enable-all-breakpoints', debugService, keybindingService);
this.toDispose.push(this.debugService.getModel().onDidChangeBreakpoints(() => this.updateEnablement()));
this._register(this.debugService.getModel().onDidChangeBreakpoints(() => this.updateEnablement()));
}
run(): Promise<any> {
@@ -234,11 +221,11 @@ export class EnableAllBreakpointsAction extends AbstractDebugAction {
export class DisableAllBreakpointsAction extends AbstractDebugAction {
static readonly ID = 'workbench.debug.viewlet.action.disableAllBreakpoints';
static LABEL = nls.localize('disableAllBreakpoints', "Disable All Breakpoints");
static readonly LABEL = nls.localize('disableAllBreakpoints', "Disable All Breakpoints");
constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) {
super(id, label, 'debug-action disable-all-breakpoints', debugService, keybindingService);
this.toDispose.push(this.debugService.getModel().onDidChangeBreakpoints(() => this.updateEnablement()));
this._register(this.debugService.getModel().onDidChangeBreakpoints(() => this.updateEnablement()));
}
run(): Promise<any> {
@@ -253,14 +240,14 @@ export class DisableAllBreakpointsAction extends AbstractDebugAction {
export class ToggleBreakpointsActivatedAction extends AbstractDebugAction {
static readonly ID = 'workbench.debug.viewlet.action.toggleBreakpointsActivatedAction';
static ACTIVATE_LABEL = nls.localize('activateBreakpoints', "Activate Breakpoints");
static DEACTIVATE_LABEL = nls.localize('deactivateBreakpoints', "Deactivate Breakpoints");
static readonly ACTIVATE_LABEL = nls.localize('activateBreakpoints', "Activate Breakpoints");
static readonly DEACTIVATE_LABEL = nls.localize('deactivateBreakpoints', "Deactivate Breakpoints");
constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) {
super(id, label, 'debug-action breakpoints-activate', debugService, keybindingService);
this.updateLabel(this.debugService.getModel().areBreakpointsActivated() ? ToggleBreakpointsActivatedAction.DEACTIVATE_LABEL : ToggleBreakpointsActivatedAction.ACTIVATE_LABEL);
this.toDispose.push(this.debugService.getModel().onDidChangeBreakpoints(() => {
this._register(this.debugService.getModel().onDidChangeBreakpoints(() => {
this.updateLabel(this.debugService.getModel().areBreakpointsActivated() ? ToggleBreakpointsActivatedAction.DEACTIVATE_LABEL : ToggleBreakpointsActivatedAction.ACTIVATE_LABEL);
this.updateEnablement();
}));
@@ -277,11 +264,11 @@ export class ToggleBreakpointsActivatedAction extends AbstractDebugAction {
export class ReapplyBreakpointsAction extends AbstractDebugAction {
static readonly ID = 'workbench.debug.viewlet.action.reapplyBreakpointsAction';
static LABEL = nls.localize('reapplyAllBreakpoints', "Reapply All Breakpoints");
static readonly LABEL = nls.localize('reapplyAllBreakpoints', "Reapply All Breakpoints");
constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) {
super(id, label, '', debugService, keybindingService);
this.toDispose.push(this.debugService.getModel().onDidChangeBreakpoints(() => this.updateEnablement()));
this._register(this.debugService.getModel().onDidChangeBreakpoints(() => this.updateEnablement()));
}
run(): Promise<any> {
@@ -297,16 +284,15 @@ export class ReapplyBreakpointsAction extends AbstractDebugAction {
export class AddFunctionBreakpointAction extends AbstractDebugAction {
static readonly ID = 'workbench.debug.viewlet.action.addFunctionBreakpointAction';
static LABEL = nls.localize('addFunctionBreakpoint', "Add Function Breakpoint");
static readonly LABEL = nls.localize('addFunctionBreakpoint', "Add Function Breakpoint");
constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) {
super(id, label, 'debug-action add-function-breakpoint', debugService, keybindingService);
this.toDispose.push(this.debugService.getModel().onDidChangeBreakpoints(() => this.updateEnablement()));
this._register(this.debugService.getModel().onDidChangeBreakpoints(() => this.updateEnablement()));
}
run(): Promise<any> {
async run(): Promise<any> {
this.debugService.addFunctionBreakpoint();
return Promise.resolve();
}
protected isEnabled(_: State): boolean {
@@ -317,17 +303,16 @@ export class AddFunctionBreakpointAction extends AbstractDebugAction {
export class AddWatchExpressionAction extends AbstractDebugAction {
static readonly ID = 'workbench.debug.viewlet.action.addWatchExpression';
static LABEL = nls.localize('addWatchExpression', "Add Expression");
static readonly LABEL = nls.localize('addWatchExpression', "Add Expression");
constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) {
super(id, label, 'debug-action add-watch-expression', debugService, keybindingService);
this.toDispose.push(this.debugService.getModel().onDidChangeWatchExpressions(() => this.updateEnablement()));
this.toDispose.push(this.debugService.getViewModel().onDidSelectExpression(() => this.updateEnablement()));
this._register(this.debugService.getModel().onDidChangeWatchExpressions(() => this.updateEnablement()));
this._register(this.debugService.getViewModel().onDidSelectExpression(() => this.updateEnablement()));
}
run(): Promise<any> {
async run(): Promise<any> {
this.debugService.addWatchExpression();
return Promise.resolve(undefined);
}
protected isEnabled(_: State): boolean {
@@ -338,16 +323,15 @@ export class AddWatchExpressionAction extends AbstractDebugAction {
export class RemoveAllWatchExpressionsAction extends AbstractDebugAction {
static readonly ID = 'workbench.debug.viewlet.action.removeAllWatchExpressions';
static LABEL = nls.localize('removeAllWatchExpressions', "Remove All Expressions");
static readonly LABEL = nls.localize('removeAllWatchExpressions', "Remove All Expressions");
constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) {
super(id, label, 'debug-action remove-all', debugService, keybindingService);
this.toDispose.push(this.debugService.getModel().onDidChangeWatchExpressions(() => this.updateEnablement()));
this._register(this.debugService.getModel().onDidChangeWatchExpressions(() => this.updateEnablement()));
}
run(): Promise<any> {
async run(): Promise<any> {
this.debugService.removeWatchExpressions();
return Promise.resolve();
}
protected isEnabled(_: State): boolean {
@@ -357,7 +341,7 @@ export class RemoveAllWatchExpressionsAction extends AbstractDebugAction {
export class FocusSessionAction extends AbstractDebugAction {
static readonly ID = 'workbench.action.debug.focusProcess';
static LABEL = nls.localize('focusSession', "Focus Session");
static readonly LABEL = nls.localize('focusSession', "Focus Session");
constructor(id: string, label: string,
@IDebugService debugService: IDebugService,
@@ -367,23 +351,21 @@ export class FocusSessionAction extends AbstractDebugAction {
super(id, label, '', debugService, keybindingService);
}
run(session: IDebugSession): Promise<any> {
this.debugService.focusStackFrame(undefined, undefined, session, true);
async run(session: IDebugSession): Promise<any> {
await this.debugService.focusStackFrame(undefined, undefined, session, true);
const stackFrame = this.debugService.getViewModel().focusedStackFrame;
if (stackFrame) {
return stackFrame.openInEditor(this.editorService, true);
await stackFrame.openInEditor(this.editorService, true);
}
return Promise.resolve(undefined);
}
}
export class CopyValueAction extends Action {
static readonly ID = 'workbench.debug.viewlet.action.copyValue';
static LABEL = nls.localize('copyValue', "Copy Value");
static readonly LABEL = nls.localize('copyValue', "Copy Value");
constructor(
id: string, label: string, private value: any, private context: string,
id: string, label: string, private value: Variable | string, private context: string,
@IDebugService private readonly debugService: IDebugService,
@IClipboardService private readonly clipboardService: IClipboardService
) {
@@ -395,15 +377,19 @@ export class CopyValueAction extends Action {
const stackFrame = this.debugService.getViewModel().focusedStackFrame;
const session = this.debugService.getViewModel().focusedSession;
if (this.value instanceof Variable && stackFrame && session && this.value.evaluateName) {
if (typeof this.value === 'string') {
return this.clipboardService.writeText(this.value);
}
if (stackFrame && session && this.value.evaluateName) {
try {
const evaluation = await session.evaluate(this.value.evaluateName, stackFrame.frameId, this.context);
this.clipboardService.writeText(evaluation.body.result);
} catch (e) {
this.clipboardService.writeText(this.value.value);
}
} else {
this.clipboardService.writeText(this.value.value);
}
return this.clipboardService.writeText(this.value);
}
}

View File

@@ -3,7 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Constants } from 'vs/editor/common/core/uint';
import { Constants } from 'vs/base/common/uint';
import { Range } from 'vs/editor/common/core/range';
import { ITextModel, TrackedRangeStickiness, IModelDeltaDecoration, IModelDecorationOptions } from 'vs/editor/common/model';
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';

View File

@@ -27,8 +27,6 @@ import { CommandsRegistry } from 'vs/platform/commands/common/commands';
import { onUnexpectedError } from 'vs/base/common/errors';
import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { IHistoryService } from 'vs/workbench/services/history/common/history';
import { startDebugging } from 'vs/workbench/contrib/debug/common/debugUtils';
import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
@@ -60,10 +58,10 @@ export const DISCONNECT_LABEL = nls.localize('disconnect', "Disconnect");
export const STOP_LABEL = nls.localize('stop', "Stop");
export const CONTINUE_LABEL = nls.localize('continueDebug', "Continue");
function getThreadAndRun(accessor: ServicesAccessor, threadId: number | undefined, run: (thread: IThread) => Promise<void>): void {
async function getThreadAndRun(accessor: ServicesAccessor, threadId: number | any, run: (thread: IThread) => Promise<void>): Promise<void> {
const debugService = accessor.get(IDebugService);
let thread: IThread | undefined;
if (threadId) {
if (typeof threadId === 'number') {
debugService.getModel().getSessions().forEach(s => {
if (!thread) {
thread = s.getThread(threadId);
@@ -79,7 +77,7 @@ function getThreadAndRun(accessor: ServicesAccessor, threadId: number | undefine
}
if (thread) {
run(thread).then(undefined, onUnexpectedError);
await run(thread);
}
}
@@ -104,6 +102,10 @@ function getFrame(debugService: IDebugService, frameId: string | undefined): ISt
export function registerCommands(): void {
// These commands are used in call stack context menu, call stack inline actions, command pallete, debug toolbar, mac native touch bar
// When the command is exectued in the context of a thread(context menu on a thread, inline call stack action) we pass the thread id
// Otherwise when it is executed "globaly"(using the touch bar, debug toolbar, command pallete) we do not pass any id and just take whatever is the focussed thread
// Same for stackFrame commands and session commands.
CommandsRegistry.registerCommand({
id: COPY_STACK_TRACE_ID,
handler: async (accessor: ServicesAccessor, _: string, frameId: string | undefined) => {
@@ -119,21 +121,21 @@ export function registerCommands(): void {
CommandsRegistry.registerCommand({
id: REVERSE_CONTINUE_ID,
handler: (accessor: ServicesAccessor, threadId: number | undefined) => {
handler: (accessor: ServicesAccessor, threadId: number | any) => {
getThreadAndRun(accessor, threadId, thread => thread.reverseContinue());
}
});
CommandsRegistry.registerCommand({
id: STEP_BACK_ID,
handler: (accessor: ServicesAccessor, threadId: number | undefined) => {
handler: (accessor: ServicesAccessor, threadId: number | any) => {
getThreadAndRun(accessor, threadId, thread => thread.stepBack());
}
});
CommandsRegistry.registerCommand({
id: TERMINATE_THREAD_ID,
handler: (accessor: ServicesAccessor, threadId: number | undefined) => {
handler: (accessor: ServicesAccessor, threadId: number | any) => {
getThreadAndRun(accessor, threadId, thread => thread.terminate());
}
});
@@ -199,8 +201,8 @@ export function registerCommands(): void {
}
if (!session) {
const historyService = accessor.get(IHistoryService);
startDebugging(debugService, historyService, false);
const { launch, name } = debugService.getConfigurationManager().selectedConfiguration;
debugService.startDebugging(launch, name, { noDebug: false });
} else {
session.removeReplExpressions();
debugService.restartSession(session).then(undefined, onUnexpectedError);
@@ -213,7 +215,7 @@ export function registerCommands(): void {
weight: KeybindingWeight.WorkbenchContrib,
primary: KeyCode.F10,
when: CONTEXT_DEBUG_STATE.isEqualTo('stopped'),
handler: (accessor: ServicesAccessor, threadId: number) => {
handler: (accessor: ServicesAccessor, threadId: number | any) => {
getThreadAndRun(accessor, threadId, (thread: IThread) => thread.next());
}
});
@@ -223,7 +225,7 @@ export function registerCommands(): void {
weight: KeybindingWeight.WorkbenchContrib + 10, // Have a stronger weight to have priority over full screen when debugging
primary: KeyCode.F11,
when: CONTEXT_IN_DEBUG_MODE,
handler: (accessor: ServicesAccessor, threadId: number) => {
handler: (accessor: ServicesAccessor, threadId: number | any) => {
getThreadAndRun(accessor, threadId, (thread: IThread) => thread.stepIn());
}
});
@@ -233,7 +235,7 @@ export function registerCommands(): void {
weight: KeybindingWeight.WorkbenchContrib,
primary: KeyMod.Shift | KeyCode.F11,
when: CONTEXT_DEBUG_STATE.isEqualTo('stopped'),
handler: (accessor: ServicesAccessor, threadId: number) => {
handler: (accessor: ServicesAccessor, threadId: number | any) => {
getThreadAndRun(accessor, threadId, (thread: IThread) => thread.stepOut());
}
});
@@ -243,7 +245,7 @@ export function registerCommands(): void {
weight: KeybindingWeight.WorkbenchContrib,
primary: KeyCode.F6,
when: CONTEXT_DEBUG_STATE.isEqualTo('running'),
handler: (accessor: ServicesAccessor, threadId: number) => {
handler: (accessor: ServicesAccessor, threadId: number | any) => {
getThreadAndRun(accessor, threadId, thread => thread.pause());
}
});
@@ -292,7 +294,7 @@ export function registerCommands(): void {
weight: KeybindingWeight.WorkbenchContrib,
primary: KeyCode.F5,
when: CONTEXT_IN_DEBUG_MODE,
handler: (accessor: ServicesAccessor, threadId: number | undefined) => {
handler: (accessor: ServicesAccessor, threadId: number | any) => {
getThreadAndRun(accessor, threadId, thread => thread.continue());
}
});
@@ -445,14 +447,11 @@ export function registerCommands(): void {
weight: KeybindingWeight.WorkbenchContrib,
when: undefined,
primary: undefined,
handler: (accessor) => {
handler: async (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();
});
const viewlet = await viewletService.openViewlet(EXTENSIONS_VIEWLET_ID, true) as IExtensionsViewlet;
viewlet.search('tag:debuggers @sort:installs');
viewlet.focus();
}
});
@@ -461,24 +460,23 @@ export function registerCommands(): void {
weight: KeybindingWeight.WorkbenchContrib,
when: undefined,
primary: undefined,
handler: (accessor, launchUri: string) => {
handler: async (accessor, launchUri: string) => {
const manager = accessor.get(IDebugService).getConfigurationManager();
if (accessor.get(IWorkspaceContextService).getWorkbenchState() === WorkbenchState.EMPTY) {
accessor.get(INotificationService).info(nls.localize('noFolderDebugConfig', "Please first open a folder in order to do advanced debug configuration."));
return undefined;
return;
}
const launch = manager.getLaunches().filter(l => l.uri.toString() === launchUri).pop() || manager.selectedConfiguration.launch;
return launch!.openConfigFile(false, false).then(({ editor, created }) => {
const launch = manager.getLaunches().filter(l => l.uri.toString() === launchUri).pop() || manager.selectedConfiguration.launch;
if (launch) {
const { editor, created } = await launch.openConfigFile(false, false);
if (editor && !created) {
const codeEditor = <ICodeEditor>editor.getControl();
if (codeEditor) {
return codeEditor.getContribution<IDebugEditorContribution>(EDITOR_CONTRIBUTION_ID).addLaunchConfiguration();
await codeEditor.getContribution<IDebugEditorContribution>(EDITOR_CONTRIBUTION_ID).addLaunchConfiguration();
}
}
return undefined;
});
}
}
});

View File

@@ -13,7 +13,6 @@ import * as resources from 'vs/base/common/resources';
import { IJSONSchema } from 'vs/base/common/jsonSchema';
import { ITextModel } from 'vs/editor/common/model';
import { IEditor } from 'vs/workbench/common/editor';
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
@@ -21,7 +20,7 @@ import { IFileService } from 'vs/platform/files/common/files';
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, ICompound, IDebugConfiguration, IConfig, IGlobalConfig, IConfigurationManager, ILaunch, IDebugAdapterDescriptorFactory, IDebugAdapter, IDebugSession, IAdapterDescriptor, CONTEXT_DEBUG_CONFIGURATION_TYPE, IDebugAdapterFactory, IDebugService } from 'vs/workbench/contrib/debug/common/debug';
import { IDebugConfigurationProvider, ICompound, IDebugConfiguration, IConfig, IGlobalConfig, IConfigurationManager, ILaunch, IDebugAdapterDescriptorFactory, IDebugAdapter, IDebugSession, IAdapterDescriptor, CONTEXT_DEBUG_CONFIGURATION_TYPE, IDebugAdapterFactory } from 'vs/workbench/contrib/debug/common/debug';
import { Debugger } from 'vs/workbench/contrib/debug/common/debugger';
import { IEditorService, ACTIVE_GROUP, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService';
import { isCodeEditor } from 'vs/editor/browser/editorBrowser';
@@ -32,10 +31,12 @@ import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/plat
import { launchSchema, debuggersExtPoint, breakpointsExtPoint } from 'vs/workbench/contrib/debug/common/debugSchemas';
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { onUnexpectedError } from 'vs/base/common/errors';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { CancellationToken } from 'vs/base/common/cancellation';
import { withUndefinedAsNull } from 'vs/base/common/types';
import { sequence } from 'vs/base/common/async';
import { IHistoryService } from 'vs/workbench/services/history/common/history';
import { first } from 'vs/base/common/arrays';
const jsonRegistry = Registry.as<IJSONContributionRegistry>(JSONExtensions.JSONContribution);
jsonRegistry.registerSchema(launchSchemaId, launchSchema);
@@ -57,7 +58,6 @@ export class ConfigurationManager implements IConfigurationManager {
private debugConfigurationTypeContext: IContextKey<string>;
constructor(
private debugService: IDebugService,
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
@IEditorService private readonly editorService: IEditorService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@@ -65,21 +65,29 @@ export class ConfigurationManager implements IConfigurationManager {
@IInstantiationService private readonly instantiationService: IInstantiationService,
@ICommandService private readonly commandService: ICommandService,
@IStorageService private readonly storageService: IStorageService,
@ILifecycleService lifecycleService: ILifecycleService,
@IExtensionService private readonly extensionService: IExtensionService,
@IContextKeyService contextKeyService: IContextKeyService
@IContextKeyService contextKeyService: IContextKeyService,
@IHistoryService historyService: IHistoryService
) {
this.configProviders = [];
this.adapterDescriptorFactories = [];
this.debuggers = [];
this.toDispose = [];
this.initLaunches();
this.registerListeners(lifecycleService);
this.registerListeners();
const previousSelectedRoot = this.storageService.get(DEBUG_SELECTED_ROOT, StorageScope.WORKSPACE);
const previousSelectedLaunch = this.launches.filter(l => l.uri.toString() === previousSelectedRoot).pop();
this.debugConfigurationTypeContext = CONTEXT_DEBUG_CONFIGURATION_TYPE.bindTo(contextKeyService);
if (previousSelectedLaunch) {
this.selectConfiguration(previousSelectedLaunch, this.storageService.get(DEBUG_SELECTED_CONFIG_NAME_KEY, StorageScope.WORKSPACE));
} else if (this.launches.length > 0) {
const rootUri = historyService.getLastActiveWorkspaceRoot();
let launch = this.getLaunch(rootUri);
if (!launch || launch.getConfigurationNames().length === 0) {
launch = first(this.launches, l => !!(l && l.getConfigurationNames().length), launch) || this.launches[0];
}
this.selectConfiguration(launch);
}
}
@@ -182,31 +190,27 @@ export class ConfigurationManager implements IConfigurationManager {
return providers.length > 0;
}
resolveConfigurationByProviders(folderUri: uri | undefined, type: string | undefined, debugConfiguration: IConfig, token: CancellationToken): Promise<IConfig | null | undefined> {
return this.activateDebuggers('onDebugResolve', type).then(() => {
// pipe the config through the promises sequentially. Append at the end the '*' types
const providers = this.configProviders.filter(p => p.type === type && p.resolveDebugConfiguration)
.concat(this.configProviders.filter(p => p.type === '*' && p.resolveDebugConfiguration));
async resolveConfigurationByProviders(folderUri: uri | undefined, type: string | undefined, config: IConfig, token: CancellationToken): Promise<IConfig | null | undefined> {
await this.activateDebuggers('onDebugResolve', type);
// pipe the config through the promises sequentially. Append at the end the '*' types
const providers = this.configProviders.filter(p => p.type === type && p.resolveDebugConfiguration)
.concat(this.configProviders.filter(p => p.type === '*' && p.resolveDebugConfiguration));
return providers.reduce((promise, provider) => {
return promise.then(config => {
if (config) {
return provider.resolveDebugConfiguration!(folderUri, config, token);
} else {
return Promise.resolve(config);
}
});
}, Promise.resolve(debugConfiguration));
});
await sequence(providers.map(provider => async () => {
config = (await provider.resolveDebugConfiguration!(folderUri, config, token)) || config;
}));
return config;
}
provideDebugConfigurations(folderUri: uri | undefined, type: string, token: CancellationToken): Promise<any[]> {
return this.activateDebuggers('onDebugInitialConfigurations')
.then(() => Promise.all(this.configProviders.filter(p => p.type === type && p.provideDebugConfigurations).map(p => p.provideDebugConfigurations!(folderUri, token)))
.then(results => results.reduce((first, second) => first.concat(second), [])));
async provideDebugConfigurations(folderUri: uri | undefined, type: string, token: CancellationToken): Promise<any[]> {
await this.activateDebuggers('onDebugInitialConfigurations');
const results = await Promise.all(this.configProviders.filter(p => p.type === type && p.provideDebugConfigurations).map(p => p.provideDebugConfigurations!(folderUri, token)));
return results.reduce((first, second) => first.concat(second), []);
}
private registerListeners(lifecycleService: ILifecycleService): void {
private registerListeners(): void {
debuggersExtPoint.setHandler((extensions, delta) => {
delta.added.forEach(added => {
added.value.forEach(rawAdapter => {
@@ -242,12 +246,6 @@ export class ConfigurationManager implements IConfigurationManager {
delta.removed.forEach(removed => {
const removedTypes = removed.value.map(rawAdapter => rawAdapter.type);
this.debuggers = this.debuggers.filter(d => removedTypes.indexOf(d.type) === -1);
this.debugService.getModel().getSessions().forEach(s => {
// Stop sessions if their debugger has been removed
if (removedTypes.indexOf(s.configuration.type) >= 0) {
this.debugService.stopSession(s).then(undefined, onUnexpectedError);
}
});
});
// update the schema to include all attributes, snippets and types from extensions.
@@ -386,57 +384,54 @@ export class ConfigurationManager implements IConfigurationManager {
return this.debuggers.filter(dbg => strings.equalsIgnoreCase(dbg.type, type)).pop();
}
guessDebugger(type?: string): Promise<Debugger | undefined> {
async guessDebugger(type?: string): Promise<Debugger | undefined> {
if (type) {
const adapter = this.getDebugger(type);
return Promise.resolve(adapter);
}
const activeTextEditorWidget = this.editorService.activeTextEditorWidget;
let candidates: Promise<Debugger[]> | undefined;
let candidates: Debugger[] | undefined;
if (isCodeEditor(activeTextEditorWidget)) {
const model = activeTextEditorWidget.getModel();
const language = model ? model.getLanguageIdentifier().language : undefined;
const adapters = this.debuggers.filter(a => language && a.languages && a.languages.indexOf(language) >= 0);
if (adapters.length === 1) {
return Promise.resolve(adapters[0]);
return adapters[0];
}
if (adapters.length > 1) {
candidates = Promise.resolve(adapters);
candidates = adapters;
}
}
if (!candidates) {
candidates = this.activateDebuggers('onDebugInitialConfigurations').then(() => this.debuggers.filter(dbg => dbg.hasInitialConfiguration() || dbg.hasConfigurationProvider()));
await this.activateDebuggers('onDebugInitialConfigurations');
candidates = this.debuggers.filter(dbg => dbg.hasInitialConfiguration() || dbg.hasConfigurationProvider());
}
return candidates.then(debuggers => {
debuggers.sort((first, second) => first.label.localeCompare(second.label));
const picks = debuggers.map(c => ({ label: c.label, debugger: c }));
return this.quickInputService.pick<{ label: string, debugger: Debugger | undefined }>([...picks, { type: 'separator' }, { label: 'More...', debugger: undefined }], { placeHolder: nls.localize('selectDebug', "Select Environment") })
.then(picked => {
if (picked && picked.debugger) {
return picked.debugger;
}
if (picked) {
this.commandService.executeCommand('debug.installAdditionalDebuggers');
}
return undefined;
});
});
candidates.sort((first, second) => first.label.localeCompare(second.label));
const picks = candidates.map(c => ({ label: c.label, debugger: c }));
const picked = await this.quickInputService.pick<{ label: string, debugger: Debugger | undefined }>([...picks, { type: 'separator' }, { label: 'More...', debugger: undefined }], { placeHolder: nls.localize('selectDebug', "Select Environment") });
if (picked && picked.debugger) {
return picked.debugger;
}
if (picked) {
this.commandService.executeCommand('debug.installAdditionalDebuggers');
}
return undefined;
}
activateDebuggers(activationEvent: string, debugType?: string): Promise<void> {
const thenables: Promise<any>[] = [
async activateDebuggers(activationEvent: string, debugType?: string): Promise<void> {
const promises: Promise<any>[] = [
this.extensionService.activateByEvent(activationEvent),
this.extensionService.activateByEvent('onDebug')
];
if (debugType) {
thenables.push(this.extensionService.activateByEvent(`${activationEvent}:${debugType}`));
promises.push(this.extensionService.activateByEvent(`${activationEvent}:${debugType}`));
}
return Promise.all(thenables).then(_ => {
return undefined;
});
await Promise.all(promises);
}
private setSelectedLaunchName(selectedName: string | undefined): void {
@@ -533,54 +528,57 @@ class Launch extends AbstractLaunch implements ILaunch {
return this.configurationService.inspect<IGlobalConfig>('launch', { resource: this.workspace.uri }).workspaceFolder;
}
openConfigFile(sideBySide: boolean, preserveFocus: boolean, type?: string, token?: CancellationToken): Promise<{ editor: IEditor | null, created: boolean }> {
async openConfigFile(sideBySide: boolean, preserveFocus: boolean, type?: string, token?: CancellationToken): Promise<{ editor: IEditor | null, created: boolean }> {
const resource = this.uri;
let created = false;
return this.fileService.readFile(resource).then(content => content.value, err => {
let content = '';
try {
const fileContent = await this.fileService.readFile(resource);
content = fileContent.value.toString();
} catch {
// launch.json not found: create one by collecting launch configs from debugConfigProviders
return this.configurationManager.guessDebugger(type).then(adapter => {
if (adapter) {
return this.configurationManager.provideDebugConfigurations(this.workspace.uri, adapter.type, token || CancellationToken.None).then(initialConfigs => {
return adapter.getInitialConfigurationContent(initialConfigs);
});
} else {
return '';
}
}).then(content => {
if (!content) {
return '';
}
const adapter = await this.configurationManager.guessDebugger(type);
if (adapter) {
const initialConfigs = await this.configurationManager.provideDebugConfigurations(this.workspace.uri, adapter.type, token || CancellationToken.None);
content = await adapter.getInitialConfigurationContent(initialConfigs);
}
if (content) {
created = true; // pin only if config file is created #8727
return this.textFileService.write(resource, content).then(() => content);
});
}).then(content => {
if (!content) {
return { editor: null, created: false };
}
const contentValue = content.toString();
const index = contentValue.indexOf(`"${this.configurationManager.selectedConfiguration.name}"`);
let startLineNumber = 1;
for (let i = 0; i < index; i++) {
if (contentValue.charAt(i) === '\n') {
startLineNumber++;
try {
await this.textFileService.write(resource, content);
} catch (error) {
throw new Error(nls.localize('DebugConfig.failed', "Unable to create 'launch.json' file inside the '.vscode' folder ({0}).", error.message));
}
}
const selection = startLineNumber > 1 ? { startLineNumber, startColumn: 4 } : undefined;
}
return Promise.resolve(this.editorService.openEditor({
resource,
options: {
selection,
preserveFocus,
pinned: created,
revealIfVisible: true
},
}, sideBySide ? SIDE_GROUP : ACTIVE_GROUP).then(editor => ({ editor: withUndefinedAsNull(editor), created })));
}, (error: Error) => {
throw new Error(nls.localize('DebugConfig.failed', "Unable to create 'launch.json' file inside the '.vscode' folder ({0}).", error.message));
if (content === '') {
return { editor: null, created: false };
}
const index = content.indexOf(`"${this.configurationManager.selectedConfiguration.name}"`);
let startLineNumber = 1;
for (let i = 0; i < index; i++) {
if (content.charAt(i) === '\n') {
startLineNumber++;
}
}
const selection = startLineNumber > 1 ? { startLineNumber, startColumn: 4 } : undefined;
const editor = await this.editorService.openEditor({
resource,
options: {
selection,
preserveFocus,
pinned: created,
revealIfVisible: true
},
}, sideBySide ? SIDE_GROUP : ACTIVE_GROUP);
return ({
editor: withUndefinedAsNull(editor),
created
});
}
}
@@ -610,11 +608,17 @@ class WorkspaceLaunch extends AbstractLaunch implements ILaunch {
return this.configurationService.inspect<IGlobalConfig>('launch').workspace;
}
openConfigFile(sideBySide: boolean, preserveFocus: boolean, type?: string): Promise<{ editor: IEditor | null, created: boolean }> {
return this.editorService.openEditor({
async openConfigFile(sideBySide: boolean, preserveFocus: boolean): Promise<{ editor: IEditor | null, created: boolean }> {
const editor = await this.editorService.openEditor({
resource: this.contextService.getWorkspace().configuration!,
options: { preserveFocus }
}, sideBySide ? SIDE_GROUP : ACTIVE_GROUP).then(editor => ({ editor: withUndefinedAsNull(editor), created: false }));
}, sideBySide ? SIDE_GROUP : ACTIVE_GROUP);
return ({
editor: withUndefinedAsNull(editor),
created: false
});
}
}
@@ -647,7 +651,11 @@ class UserLaunch extends AbstractLaunch implements ILaunch {
return this.configurationService.inspect<IGlobalConfig>('launch').user;
}
openConfigFile(sideBySide: boolean, preserveFocus: boolean, type?: string): Promise<{ editor: IEditor | null, created: boolean }> {
return this.preferencesService.openGlobalSettings(false, { preserveFocus }).then(editor => ({ editor: withUndefinedAsNull(editor), created: false }));
async openConfigFile(_: boolean, preserveFocus: boolean): Promise<{ editor: IEditor | null, created: boolean }> {
const editor = await this.preferencesService.openGlobalSettings(false, { preserveFocus });
return ({
editor: withUndefinedAsNull(editor),
created: false
});
}
}

View File

@@ -34,7 +34,7 @@ class ToggleBreakpointAction extends EditorAction {
});
}
public run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<any> {
async run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<any> {
if (editor.hasModel()) {
const debugService = accessor.get(IDebugService);
const modelUri = editor.getModel().uri;
@@ -49,12 +49,10 @@ class ToggleBreakpointAction extends EditorAction {
} else if (canSet) {
return (debugService.addBreakpoints(modelUri, [{ lineNumber: line }], 'debugEditorActions.toggleBreakpointAction'));
} else {
return Promise.resolve([]);
return [];
}
}));
}
return Promise.resolve();
}
}
@@ -75,7 +73,7 @@ class ConditionalBreakpointAction extends EditorAction {
const position = editor.getPosition();
if (position && editor.hasModel() && debugService.getConfigurationManager().canSetBreakpointsIn(editor.getModel())) {
editor.getContribution<IBreakpointEditorContribution>(BREAKPOINT_EDITOR_CONTRIBUTION_ID).showBreakpointWidget(position.lineNumber, undefined);
editor.getContribution<IBreakpointEditorContribution>(BREAKPOINT_EDITOR_CONTRIBUTION_ID).showBreakpointWidget(position.lineNumber, undefined, BreakpointWidgetContext.CONDITION);
}
}
}
@@ -97,15 +95,15 @@ class LogPointAction extends EditorAction {
const position = editor.getPosition();
if (position && editor.hasModel() && debugService.getConfigurationManager().canSetBreakpointsIn(editor.getModel())) {
editor.getContribution<IBreakpointEditorContribution>(BREAKPOINT_EDITOR_CONTRIBUTION_ID).showBreakpointWidget(position.lineNumber, BreakpointWidgetContext.LOG_MESSAGE);
editor.getContribution<IBreakpointEditorContribution>(BREAKPOINT_EDITOR_CONTRIBUTION_ID).showBreakpointWidget(position.lineNumber, position.column, BreakpointWidgetContext.LOG_MESSAGE);
}
}
}
export class RunToCursorAction extends EditorAction {
public static ID = 'editor.debug.action.runToCursor';
public static LABEL = nls.localize('runToCursor', "Run to Cursor");
public static readonly ID = 'editor.debug.action.runToCursor';
public static readonly LABEL = nls.localize('runToCursor', "Run to Cursor");
constructor() {
super({
@@ -120,11 +118,11 @@ export class RunToCursorAction extends EditorAction {
});
}
public run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> {
async run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> {
const debugService = accessor.get(IDebugService);
const focusedSession = debugService.getViewModel().focusedSession;
if (debugService.state !== State.Stopped || !focusedSession) {
return Promise.resolve(undefined);
return;
}
let breakpointToRemove: IBreakpoint;
@@ -139,18 +137,18 @@ export class RunToCursorAction extends EditorAction {
});
const position = editor.getPosition();
if (!editor.hasModel() || !position) {
return Promise.resolve();
}
const uri = editor.getModel().uri;
const bpExists = !!(debugService.getModel().getBreakpoints({ column: position.column, lineNumber: position.lineNumber, uri }).length);
return (bpExists ? Promise.resolve(null) : <Promise<any>>debugService.addBreakpoints(uri, [{ lineNumber: position.lineNumber, column: position.column }], 'debugEditorActions.runToCursorAction')).then((breakpoints) => {
if (breakpoints && breakpoints.length) {
breakpointToRemove = breakpoints[0];
if (editor.hasModel() && position) {
const uri = editor.getModel().uri;
const bpExists = !!(debugService.getModel().getBreakpoints({ column: position.column, lineNumber: position.lineNumber, uri }).length);
if (!bpExists) {
const breakpoints = await debugService.addBreakpoints(uri, [{ lineNumber: position.lineNumber, column: position.column }], 'debugEditorActions.runToCursorAction');
if (breakpoints && breakpoints.length) {
breakpointToRemove = breakpoints[0];
}
}
debugService.getViewModel().focusedThread!.continue();
});
await debugService.getViewModel().focusedThread!.continue();
}
}
}
@@ -169,13 +167,13 @@ class SelectionToReplAction extends EditorAction {
});
}
public async run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> {
async run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> {
const debugService = accessor.get(IDebugService);
const panelService = accessor.get(IPanelService);
const viewModel = debugService.getViewModel();
const session = viewModel.focusedSession;
if (!editor.hasModel() || !session) {
return Promise.resolve();
return;
}
const text = editor.getModel().getValueInRange(editor.getSelection());
@@ -199,15 +197,16 @@ class SelectionToWatchExpressionsAction extends EditorAction {
});
}
public run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> {
async run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> {
const debugService = accessor.get(IDebugService);
const viewletService = accessor.get(IViewletService);
if (!editor.hasModel()) {
return Promise.resolve();
return;
}
const text = editor.getModel().getValueInRange(editor.getSelection());
return viewletService.openViewlet(VIEWLET_ID).then(() => debugService.addWatchExpression(text));
await viewletService.openViewlet(VIEWLET_ID);
debugService.addWatchExpression(text);
}
}
@@ -227,14 +226,14 @@ class ShowDebugHoverAction extends EditorAction {
});
}
public run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> {
async run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> {
const position = editor.getPosition();
if (!position || !editor.hasModel()) {
return Promise.resolve();
return;
}
const word = editor.getModel().getWordAtPosition(position);
if (!word) {
return Promise.resolve();
return;
}
const range = new Range(position.lineNumber, position.column, position.lineNumber, word.endColumn);
@@ -247,7 +246,7 @@ class GoToBreakpointAction extends EditorAction {
super(opts);
}
public run(accessor: ServicesAccessor, editor: ICodeEditor, args: any): Promise<any> {
async run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<any> {
const debugService = accessor.get(IDebugService);
const editorService = accessor.get(IEditorService);
if (editor.hasModel()) {
@@ -279,8 +278,6 @@ class GoToBreakpointAction extends EditorAction {
return openBreakpointSource(moveBreakpoint, false, true, debugService, editorService);
}
}
return Promise.resolve(null);
}
}

View File

@@ -7,7 +7,7 @@ import * as nls from 'vs/nls';
import { RunOnceScheduler } from 'vs/base/common/async';
import * as env from 'vs/base/common/platform';
import { visit } from 'vs/base/common/json';
import { Constants } from 'vs/editor/common/core/uint';
import { Constants } from 'vs/base/common/uint';
import { KeyCode } from 'vs/base/common/keyCodes';
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { StandardTokenType } from 'vs/editor/common/modes';
@@ -98,7 +98,7 @@ class DebugEditorContribution implements IDebugEditorContribution {
this.wordToLineNumbersMap = undefined;
this.updateInlineValuesScheduler.schedule();
}));
this.toDispose.push(this.editor.onDidChangeModel(() => {
this.toDispose.push(this.editor.onDidChangeModel(async () => {
const stackFrame = this.debugService.getViewModel().focusedStackFrame;
const model = this.editor.getModel();
if (model) {
@@ -108,7 +108,7 @@ class DebugEditorContribution implements IDebugEditorContribution {
this.hideHoverWidget();
this.updateConfigurationWidgetVisibility();
this.wordToLineNumbersMap = undefined;
this.updateInlineValueDecorations(stackFrame);
await this.updateInlineValueDecorations(stackFrame);
}));
this.toDispose.push(this.editor.onDidScrollChange(() => this.hideHoverWidget));
this.toDispose.push(this.debugService.onDidChangeState((state: State) => {
@@ -141,32 +141,26 @@ class DebugEditorContribution implements IDebugEditorContribution {
}
}
getId(): string {
return EDITOR_CONTRIBUTION_ID;
}
showHover(range: Range, focus: boolean): Promise<void> {
async showHover(range: Range, focus: boolean): Promise<void> {
const sf = this.debugService.getViewModel().focusedStackFrame;
const model = this.editor.getModel();
if (sf && model && sf.source.uri.toString() === model.uri.toString()) {
return this.hoverWidget.showAt(range, focus);
}
return Promise.resolve();
}
private onFocusStackFrame(sf: IStackFrame | undefined): void {
private async onFocusStackFrame(sf: IStackFrame | undefined): Promise<void> {
const model = this.editor.getModel();
if (model) {
this._applyHoverConfiguration(model, sf);
if (sf && sf.source.uri.toString() === model.uri.toString()) {
this.toggleExceptionWidget();
await this.toggleExceptionWidget();
} else {
this.hideHoverWidget();
}
}
this.updateInlineValueDecorations(sf);
await this.updateInlineValueDecorations(sf);
}
@memoize
@@ -261,7 +255,7 @@ class DebugEditorContribution implements IDebugEditorContribution {
// end hover business
// exception widget
private toggleExceptionWidget(): void {
private async toggleExceptionWidget(): Promise<void> {
// Toggles exception widget based on the state of the current editor model and debug stack frame
const model = this.editor.getModel();
const focusedSf = this.debugService.getViewModel().focusedStackFrame;
@@ -282,11 +276,10 @@ class DebugEditorContribution implements IDebugEditorContribution {
if (this.exceptionWidget && !sameUri) {
this.closeExceptionWidget();
} else if (sameUri) {
focusedSf.thread.exceptionInfo.then(exceptionInfo => {
if (exceptionInfo && exceptionSf.range.startLineNumber && exceptionSf.range.startColumn) {
this.showExceptionWidget(exceptionInfo, this.debugService.getViewModel().focusedSession, exceptionSf.range.startLineNumber, exceptionSf.range.startColumn);
}
});
const exceptionInfo = await focusedSf.thread.exceptionInfo;
if (exceptionInfo && exceptionSf.range.startLineNumber && exceptionSf.range.startColumn) {
this.showExceptionWidget(exceptionInfo, this.debugService.getViewModel().focusedSession, exceptionSf.range.startLineNumber, exceptionSf.range.startColumn);
}
}
}
@@ -320,7 +313,7 @@ class DebugEditorContribution implements IDebugEditorContribution {
}
}
addLaunchConfiguration(): Promise<any> {
async addLaunchConfiguration(): Promise<any> {
/* __GDPR__
"debug/addLaunchConfiguration" : {}
*/
@@ -328,7 +321,7 @@ class DebugEditorContribution implements IDebugEditorContribution {
let configurationsArrayPosition: Position | undefined;
const model = this.editor.getModel();
if (!model) {
return Promise.resolve();
return;
}
let depthInArray = 0;
@@ -351,7 +344,7 @@ class DebugEditorContribution implements IDebugEditorContribution {
this.editor.focus();
if (!configurationsArrayPosition) {
return Promise.resolve();
return;
}
const insertLine = (position: Position): Promise<any> => {
@@ -364,7 +357,8 @@ class DebugEditorContribution implements IDebugEditorContribution {
return this.commandService.executeCommand('editor.action.insertLineAfter');
};
return insertLine(configurationsArrayPosition).then(() => this.commandService.executeCommand('editor.action.triggerSuggest'));
await insertLine(configurationsArrayPosition);
await this.commandService.executeCommand('editor.action.triggerSuggest');
}
// Inline Decorations
@@ -380,12 +374,12 @@ class DebugEditorContribution implements IDebugEditorContribution {
@memoize
private get updateInlineValuesScheduler(): RunOnceScheduler {
return new RunOnceScheduler(
() => this.updateInlineValueDecorations(this.debugService.getViewModel().focusedStackFrame),
async () => await this.updateInlineValueDecorations(this.debugService.getViewModel().focusedStackFrame),
200
);
}
private updateInlineValueDecorations(stackFrame: IStackFrame | undefined): void {
private async updateInlineValueDecorations(stackFrame: IStackFrame | undefined): Promise<void> {
const model = this.editor.getModel();
if (!this.configurationService.getValue<IDebugConfiguration>('debug').inlineValues ||
!model || !stackFrame || model.uri.toString() !== stackFrame.source.uri.toString()) {
@@ -397,20 +391,20 @@ class DebugEditorContribution implements IDebugEditorContribution {
this.removeInlineValuesScheduler.cancel();
stackFrame.getMostSpecificScopes(stackFrame.range)
// Get all top level children in the scope chain
.then(scopes => Promise.all(scopes.map(scope => scope.getChildren()
.then(children => {
let range = new Range(0, 0, stackFrame.range.startLineNumber, stackFrame.range.startColumn);
if (scope.range) {
range = range.setStartPosition(scope.range.startLineNumber, scope.range.startColumn);
}
const scopes = await stackFrame.getMostSpecificScopes(stackFrame.range);
// Get all top level children in the scope chain
const decorationsPerScope = await Promise.all(scopes.map(async scope => {
const children = await scope.getChildren();
let range = new Range(0, 0, stackFrame.range.startLineNumber, stackFrame.range.startColumn);
if (scope.range) {
range = range.setStartPosition(scope.range.startLineNumber, scope.range.startColumn);
}
return this.createInlineValueDecorationsInsideRange(children, range, model);
}))).then(decorationsPerScope => {
const allDecorations = decorationsPerScope.reduce((previous, current) => previous.concat(current), []);
this.editor.setDecorations(INLINE_VALUE_DECORATION_KEY, allDecorations);
}));
return this.createInlineValueDecorationsInsideRange(children, range, model);
}));
const allDecorations = decorationsPerScope.reduce((previous, current) => previous.concat(current), []);
this.editor.setDecorations(INLINE_VALUE_DECORATION_KEY, allDecorations);
}
private createInlineValueDecorationsInsideRange(expressions: ReadonlyArray<IExpression>, range: Range, model: ITextModel): IDecorationOptions[] {
@@ -547,4 +541,4 @@ class DebugEditorContribution implements IDebugEditorContribution {
}
}
registerEditorContribution(DebugEditorContribution);
registerEditorContribution(EDITOR_CONTRIBUTION_ID, DebugEditorContribution);

View File

@@ -94,12 +94,12 @@ export class DebugHoverWidget implements IContentWidget {
if (colors.editorHoverBackground) {
this.domNode.style.backgroundColor = colors.editorHoverBackground.toString();
} else {
this.domNode.style.backgroundColor = null;
this.domNode.style.backgroundColor = '';
}
if (colors.editorHoverBorder) {
this.domNode.style.border = `1px solid ${colors.editorHoverBorder}`;
} else {
this.domNode.style.border = null;
this.domNode.style.border = '';
}
}));
this.toDispose.push(this.tree.onDidChangeContentHeight(() => this.layoutTreeAndContainer()));
@@ -174,7 +174,7 @@ export class DebugHoverWidget implements IContentWidget {
return this.doShow(pos, expression, focus);
}
private static _HOVER_HIGHLIGHT_DECORATION_OPTIONS = ModelDecorationOptions.register({
private static readonly _HOVER_HIGHLIGHT_DECORATION_OPTIONS = ModelDecorationOptions.register({
className: 'hoverHighlight'
});

View File

@@ -24,14 +24,13 @@ import * as debugactions from 'vs/workbench/contrib/debug/browser/debugActions';
import { ConfigurationManager } from 'vs/workbench/contrib/debug/browser/debugConfigurationManager';
import Constants from 'vs/workbench/contrib/markers/browser/constants';
import { ITaskService, ITaskSummary } from 'vs/workbench/contrib/tasks/common/taskService';
import { TaskError } from 'vs/workbench/contrib/tasks/common/taskSystem';
import { VIEWLET_ID as EXPLORER_VIEWLET_ID } from 'vs/workbench/contrib/files/common/files';
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IWorkspaceContextService, WorkbenchState, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { IWorkspaceContextService, WorkbenchState, IWorkspaceFolder, IWorkspace } from 'vs/platform/workspace/common/workspace';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { parse, getFirstFrame } from 'vs/base/common/console';
import { TaskEvent, TaskEventKind, TaskIdentifier } from 'vs/workbench/contrib/tasks/common/tasks';
@@ -121,7 +120,7 @@ export class DebugService implements IDebugService {
this._onWillNewSession = new Emitter<IDebugSession>();
this._onDidEndSession = new Emitter<IDebugSession>();
this.configurationManager = this.instantiationService.createInstance(ConfigurationManager, this);
this.configurationManager = this.instantiationService.createInstance(ConfigurationManager);
this.toDispose.push(this.configurationManager);
this.debugType = CONTEXT_DEBUG_TYPE.bindTo(contextKeyService);
@@ -144,13 +143,13 @@ export class DebugService implements IDebugService {
session.configuration.request = 'attach';
session.configuration.port = event.port;
session.setSubId(event.subId);
this.launchOrAttachToSession(session).then(undefined, errors.onUnexpectedError);
this.launchOrAttachToSession(session);
}
}));
this.toDispose.push(this.extensionHostDebugService.onTerminateSession(event => {
const session = this.model.getSession(event.sessionId);
if (session && session.subId === event.subId) {
session.disconnect().then(undefined, errors.onUnexpectedError);
session.disconnect();
}
}));
this.toDispose.push(this.extensionHostDebugService.onLogToSession(event => {
@@ -253,96 +252,99 @@ export class DebugService implements IDebugService {
* main entry point
* properly manages compounds, checks for errors and handles the initializing state.
*/
startDebugging(launch: ILaunch | undefined, configOrName?: IConfig | string, options?: IDebugSessionOptions): Promise<boolean> {
async startDebugging(launch: ILaunch | undefined, configOrName?: IConfig | string, options?: IDebugSessionOptions): Promise<boolean> {
this.startInitializingState();
// make sure to save all files and that the configuration is up to date
return this.extensionService.activateByEvent('onDebug').then(() => {
return this.textFileService.saveAll().then(() => this.configurationService.reloadConfiguration(launch ? launch.workspace : undefined).then(() => {
return this.extensionService.whenInstalledExtensionsRegistered().then(() => {
try {
// make sure to save all files and that the configuration is up to date
await this.extensionService.activateByEvent('onDebug');
await this.textFileService.saveAll();
await this.configurationService.reloadConfiguration(launch ? launch.workspace : undefined);
await this.extensionService.whenInstalledExtensionsRegistered();
let config: IConfig | undefined;
let compound: ICompound | undefined;
if (!configOrName) {
configOrName = this.configurationManager.selectedConfiguration.name;
let config: IConfig | undefined;
let compound: ICompound | undefined;
if (!configOrName) {
configOrName = this.configurationManager.selectedConfiguration.name;
}
if (typeof configOrName === 'string' && launch) {
config = launch.getConfiguration(configOrName);
compound = launch.getCompound(configOrName);
const sessions = this.model.getSessions();
const alreadyRunningMessage = nls.localize('configurationAlreadyRunning', "There is already a debug configuration \"{0}\" running.", configOrName);
if (sessions.some(s => s.configuration.name === configOrName && (!launch || !launch.workspace || !s.root || s.root.uri.toString() === launch.workspace.uri.toString()))) {
throw new Error(alreadyRunningMessage);
}
if (compound && compound.configurations && sessions.some(p => compound!.configurations.indexOf(p.configuration.name) !== -1)) {
throw new Error(alreadyRunningMessage);
}
} else if (typeof configOrName !== 'string') {
config = configOrName;
}
if (compound) {
// we are starting a compound debug, first do some error checking and than start each configuration in the compound
if (!compound.configurations) {
throw new Error(nls.localize({ key: 'compoundMustHaveConfigurations', comment: ['compound indicates a "compounds" configuration item', '"configurations" is an attribute and should not be localized'] },
"Compound must have \"configurations\" attribute set in order to start multiple configurations."));
}
const values = await Promise.all(compound.configurations.map(configData => {
const name = typeof configData === 'string' ? configData : configData.name;
if (name === compound!.name) {
return Promise.resolve(false);
}
if (typeof configOrName === 'string' && launch) {
config = launch.getConfiguration(configOrName);
compound = launch.getCompound(configOrName);
const sessions = this.model.getSessions();
const alreadyRunningMessage = nls.localize('configurationAlreadyRunning', "There is already a debug configuration \"{0}\" running.", configOrName);
if (sessions.some(s => s.configuration.name === configOrName && (!launch || !launch.workspace || !s.root || s.root.uri.toString() === launch.workspace.uri.toString()))) {
return Promise.reject(new Error(alreadyRunningMessage));
let launchForName: ILaunch | undefined;
if (typeof configData === 'string') {
const launchesContainingName = this.configurationManager.getLaunches().filter(l => !!l.getConfiguration(name));
if (launchesContainingName.length === 1) {
launchForName = launchesContainingName[0];
} else if (launch && 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 {
throw 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));
}
if (compound && compound.configurations && sessions.some(p => compound!.configurations.indexOf(p.configuration.name) !== -1)) {
return Promise.reject(new Error(alreadyRunningMessage));
} 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 {
throw new Error(nls.localize('noFolderWithName', "Can not find folder with name '{0}' for configuration '{1}' in compound '{2}'.", configData.folder, configData.name, compound!.name));
}
} else if (typeof configOrName !== 'string') {
config = configOrName;
}
if (compound) {
// we are starting a compound debug, first do some error checking and than start each configuration in the compound
if (!compound.configurations) {
return Promise.reject(new Error(nls.localize({ key: 'compoundMustHaveConfigurations', comment: ['compound indicates a "compounds" configuration item', '"configurations" is an attribute and should not be localized'] },
"Compound must have \"configurations\" attribute set in order to start multiple configurations.")));
}
return this.createSession(launchForName, launchForName!.getConfiguration(name), options);
}));
return Promise.all(compound.configurations.map(configData => {
const name = typeof configData === 'string' ? configData : configData.name;
if (name === compound!.name) {
return Promise.resolve(false);
}
const result = values.every(success => !!success); // Compound launch is a success only if each configuration launched successfully
this.endInitializingState();
return result;
}
let launchForName: ILaunch | undefined;
if (typeof configData === 'string') {
const launchesContainingName = this.configurationManager.getLaunches().filter(l => !!l.getConfiguration(name));
if (launchesContainingName.length === 1) {
launchForName = launchesContainingName[0];
} else if (launch && 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 Promise.reject(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 Promise.reject(new Error(nls.localize('noFolderWithName', "Can not find folder with name '{0}' for configuration '{1}' in compound '{2}'.", configData.folder, configData.name, compound!.name)));
}
}
if (configOrName && !config) {
const message = !!launch ? nls.localize('configMissing', "Configuration '{0}' is missing in 'launch.json'.", typeof configOrName === 'string' ? configOrName : JSON.stringify(configOrName)) :
nls.localize('launchJsonDoesNotExist', "'launch.json' does not exist.");
throw new Error(message);
}
return this.createSession(launchForName, launchForName!.getConfiguration(name), options);
})).then(values => values.every(success => !!success)); // Compound launch is a success only if each configuration launched successfully
}
if (configOrName && !config) {
const message = !!launch ? nls.localize('configMissing', "Configuration '{0}' is missing in 'launch.json'.", typeof configOrName === 'string' ? configOrName : JSON.stringify(configOrName)) :
nls.localize('launchJsonDoesNotExist', "'launch.json' does not exist.");
return Promise.reject(new Error(message));
}
return this.createSession(launch, config, options);
});
}));
}).then(success => {
const result = await this.createSession(launch, config, options);
this.endInitializingState();
return result;
} catch (err) {
// make sure to get out of initializing state, and propagate the result
this.endInitializingState();
return success;
}, err => {
this.endInitializingState();
return Promise.reject(err);
});
}
}
/**
* gets the debugger for the type, resolves configurations by providers, substitutes variables and runs prelaunch tasks
*/
private createSession(launch: ILaunch | undefined, config: IConfig | undefined, options?: IDebugSessionOptions): Promise<boolean> {
private async createSession(launch: ILaunch | undefined, config: IConfig | undefined, options?: IDebugSessionOptions): Promise<boolean> {
// We keep the debug type in a separate variable 'type' so that a no-folder config has no attributes.
// Storing the type in the config would break extensions that assume that the no-folder case is indicated by an empty config.
let type: string | undefined;
@@ -358,66 +360,71 @@ export class DebugService implements IDebugService {
config!.noDebug = true;
}
const debuggerThenable: Promise<void> = type ? Promise.resolve() : this.configurationManager.guessDebugger().then(dbgr => { type = dbgr && dbgr.type; });
return debuggerThenable.then(() => {
this.initCancellationToken = new CancellationTokenSource();
return this.configurationManager.resolveConfigurationByProviders(launch && launch.workspace ? launch.workspace.uri : undefined, type, config!, this.initCancellationToken.token).then(config => {
// a falsy config indicates an aborted launch
if (config && config.type) {
return this.substituteVariables(launch, config).then(resolvedConfig => {
if (!type) {
const guess = await this.configurationManager.guessDebugger();
if (guess) {
type = guess.type;
}
}
if (!resolvedConfig) {
// User canceled resolving of interactive variables, silently return
return false;
}
this.initCancellationToken = new CancellationTokenSource();
const configByProviders = await this.configurationManager.resolveConfigurationByProviders(launch && launch.workspace ? launch.workspace.uri : undefined, type, config!, this.initCancellationToken.token);
// a falsy config indicates an aborted launch
if (configByProviders && configByProviders.type) {
try {
const resolvedConfig = await this.substituteVariables(launch, configByProviders);
if (!this.configurationManager.getDebugger(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)
: 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.");
}
return this.showError(message).then(() => false);
}
const workspace = launch ? launch.workspace : undefined;
return this.runTaskAndCheckErrors(workspace, resolvedConfig.preLaunchTask).then(result => {
if (result === TaskRunResult.Success) {
return this.doCreateSession(workspace, { resolved: resolvedConfig, unresolved: unresolvedConfig }, options);
}
return false;
});
}, err => {
if (err && err.message) {
return this.showError(err.message).then(() => false);
}
if (this.contextService.getWorkbenchState() === WorkbenchState.EMPTY) {
return this.showError(nls.localize('noFolderWorkspaceDebugError', "The active file can not be debugged. Make sure it is saved and that you have a debug extension installed for that file type."))
.then(() => false);
}
return !!launch && launch.openConfigFile(false, true, undefined, this.initCancellationToken ? this.initCancellationToken.token : undefined).then(() => false);
});
if (!resolvedConfig) {
// User canceled resolving of interactive variables, silently return
return false;
}
if (launch && type && config === null) { // show launch.json only for "config" being "null".
return launch.openConfigFile(false, true, type, this.initCancellationToken ? this.initCancellationToken.token : undefined).then(() => false);
if (!this.configurationManager.getDebugger(resolvedConfig.type) || (configByProviders.request !== 'attach' && configByProviders.request !== 'launch')) {
let message: string;
if (configByProviders.request !== 'attach' && configByProviders.request !== 'launch') {
message = configByProviders.request ? nls.localize('debugRequestNotSupported', "Attribute '{0}' has an unsupported value '{1}' in the chosen debug configuration.", 'request', configByProviders.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.");
}
await this.showError(message);
return false;
}
const workspace = launch ? launch.workspace : undefined;
const taskResult = await this.runTaskAndCheckErrors(workspace, resolvedConfig.preLaunchTask);
if (taskResult === TaskRunResult.Success) {
return this.doCreateSession(workspace, { resolved: resolvedConfig, unresolved: unresolvedConfig }, options);
}
return false;
} catch (err) {
if (err && err.message) {
await this.showError(err.message);
} else if (this.contextService.getWorkbenchState() === WorkbenchState.EMPTY) {
await this.showError(nls.localize('noFolderWorkspaceDebugError', "The active file can not be debugged. Make sure it is saved and that you have a debug extension installed for that file type."));
}
if (launch) {
await launch.openConfigFile(false, true, undefined, this.initCancellationToken ? this.initCancellationToken.token : undefined);
}
return false;
});
});
}
}
if (launch && type && configByProviders === null) { // show launch.json only for "config" being "null".
await launch.openConfigFile(false, true, type, this.initCancellationToken ? this.initCancellationToken.token : undefined);
}
return false;
}
/**
* instantiates the new session, initializes the session, registers session listeners and reports telemetry
*/
private doCreateSession(root: IWorkspaceFolder | undefined, configuration: { resolved: IConfig, unresolved: IConfig | undefined }, options?: IDebugSessionOptions): Promise<boolean> {
private async doCreateSession(root: IWorkspaceFolder | undefined, configuration: { resolved: IConfig, unresolved: IConfig | undefined }, options?: IDebugSessionOptions): Promise<boolean> {
const session = this.instantiationService.createInstance(DebugSession, configuration, root, this.model, options);
this.model.addSession(session);
@@ -431,10 +438,11 @@ export class DebugService implements IDebugService {
const openDebug = this.configurationService.getValue<IDebugConfiguration>('debug').openDebug;
// Open debug viewlet based on the visibility of the side bar and openDebug setting. Do not open for 'run without debug'
if (!configuration.resolved.noDebug && (openDebug === 'openOnSessionStart' || (openDebug === 'openOnFirstSessionStart' && this.viewModel.firstSessionStart))) {
this.viewletService.openViewlet(VIEWLET_ID).then(undefined, errors.onUnexpectedError);
await this.viewletService.openViewlet(VIEWLET_ID);
}
return this.launchOrAttachToSession(session).then(() => {
try {
await this.launchOrAttachToSession(session);
const internalConsoleOptions = session.configuration.internalConsoleOptions || this.configurationService.getValue<IDebugConfiguration>('debug').internalConsoleOptions;
if (internalConsoleOptions === 'openOnSessionStart' || (this.viewModel.firstSessionStart && internalConsoleOptions === 'openOnFirstSessionStart')) {
@@ -452,12 +460,14 @@ export class DebugService implements IDebugService {
// since the initialized response has arrived announce the new Session (including extensions)
this._onDidNewSession.fire(session);
return this.telemetryDebugSessionStart(root, session.configuration.type);
}).then(() => true, (error: Error | string) => {
await this.telemetryDebugSessionStart(root, session.configuration.type);
return true;
} catch (error) {
if (errors.isPromiseCanceledError(error)) {
// don't show 'canceled' error messages to the user #7906
return Promise.resolve(false);
return false;
}
// Show the repl if some error got logged there #5870
@@ -467,27 +477,29 @@ export class DebugService implements IDebugService {
if (session.configuration && session.configuration.request === 'attach' && session.configuration.__autoAttach) {
// ignore attach timeouts in auto attach mode
return Promise.resolve(false);
return false;
}
const errorMessage = error instanceof Error ? error.message : error;
this.telemetryDebugMisconfiguration(session.configuration ? session.configuration.type : undefined, errorMessage);
return this.showError(errorMessage, isErrorWithActions(error) ? error.actions : []).then(() => false);
});
await this.showError(errorMessage, isErrorWithActions(error) ? error.actions : []);
return false;
}
}
private launchOrAttachToSession(session: IDebugSession, forceFocus = false): Promise<void> {
private async launchOrAttachToSession(session: IDebugSession, forceFocus = false): Promise<void> {
const dbgr = this.configurationManager.getDebugger(session.configuration.type);
return session.initialize(dbgr!).then(() => {
return session.launchOrAttach(session.configuration).then(() => {
if (forceFocus || !this.viewModel.focusedSession) {
this.focusStackFrame(undefined, undefined, session);
}
});
}).then(undefined, err => {
try {
await session.initialize(dbgr!);
await session.launchOrAttach(session.configuration);
if (forceFocus || !this.viewModel.focusedSession) {
await this.focusStackFrame(undefined, undefined, session);
}
} catch (err) {
session.shutdown();
return Promise.reject(err);
});
}
}
private registerSessionListeners(session: IDebugSession): void {
@@ -506,7 +518,7 @@ export class DebugService implements IDebugService {
}
}));
this.toDispose.push(session.onDidEndAdapter(adapterExitEvent => {
this.toDispose.push(session.onDidEndAdapter(async adapterExitEvent => {
if (adapterExitEvent.error) {
this.notificationService.error(nls.localize('debugAdapterCrash', "Debug adapter process has terminated unexpectedly ({0})", adapterExitEvent.error.message || adapterExitEvent.error.toString()));
@@ -520,9 +532,11 @@ export class DebugService implements IDebugService {
this.telemetryDebugSessionStop(session, adapterExitEvent);
if (session.configuration.postDebugTask) {
this.runTask(session.root, session.configuration.postDebugTask).then(undefined, err =>
this.notificationService.error(err)
);
try {
await this.runTask(session.root, session.configuration.postDebugTask);
} catch (err) {
this.notificationService.error(err);
}
}
session.shutdown();
this.endInitializingState();
@@ -530,7 +544,7 @@ export class DebugService implements IDebugService {
const focusedSession = this.viewModel.focusedSession;
if (focusedSession && focusedSession.getId() === session.getId()) {
this.focusStackFrame(undefined);
await this.focusStackFrame(undefined);
}
if (this.model.getSessions().length === 0) {
@@ -548,87 +562,93 @@ export class DebugService implements IDebugService {
}));
}
restartSession(session: IDebugSession, restartData?: any): Promise<any> {
return this.textFileService.saveAll().then(() => {
const isAutoRestart = !!restartData;
const runTasks: () => Promise<TaskRunResult> = () => {
if (isAutoRestart) {
// Do not run preLaunch and postDebug tasks for automatic restarts
return Promise.resolve(TaskRunResult.Success);
async restartSession(session: IDebugSession, restartData?: any): Promise<any> {
await this.textFileService.saveAll();
const isAutoRestart = !!restartData;
const runTasks: () => Promise<TaskRunResult> = async () => {
if (isAutoRestart) {
// Do not run preLaunch and postDebug tasks for automatic restarts
return Promise.resolve(TaskRunResult.Success);
}
await this.runTask(session.root, session.configuration.postDebugTask);
return this.runTaskAndCheckErrors(session.root, session.configuration.preLaunchTask);
};
if (session.capabilities.supportsRestartRequest) {
const taskResult = await runTasks();
if (taskResult === TaskRunResult.Success) {
await session.restart();
}
return;
}
if (isExtensionHostDebugging(session.configuration)) {
const taskResult = await runTasks();
if (taskResult === TaskRunResult.Success) {
this.extensionHostDebugService.reload(session.getId());
}
return;
}
const shouldFocus = !!this.viewModel.focusedSession && session.getId() === this.viewModel.focusedSession.getId();
// If the restart is automatic -> disconnect, otherwise -> terminate #55064
if (isAutoRestart) {
await session.disconnect(true);
} else {
await session.terminate(true);
}
return new Promise<void>((c, e) => {
setTimeout(async () => {
const taskResult = await runTasks();
if (taskResult !== TaskRunResult.Success) {
return;
}
return this.runTask(session.root, session.configuration.postDebugTask)
.then(() => this.runTaskAndCheckErrors(session.root, session.configuration.preLaunchTask));
};
// Read the configuration again if a launch.json has been changed, if not just use the inmemory configuration
let needsToSubstitute = false;
let unresolved: IConfig | undefined;
const launch = session.root ? this.configurationManager.getLaunch(session.root.uri) : undefined;
if (launch) {
unresolved = launch.getConfiguration(session.configuration.name);
if (unresolved && !equals(unresolved, session.unresolvedConfiguration)) {
// Take the type from the session since the debug extension might overwrite it #21316
unresolved.type = session.configuration.type;
unresolved.noDebug = session.configuration.noDebug;
needsToSubstitute = true;
}
}
if (session.capabilities.supportsRestartRequest) {
return runTasks().then(taskResult => taskResult === TaskRunResult.Success ? session.restart() : undefined);
}
let resolved: IConfig | undefined | null = session.configuration;
if (launch && needsToSubstitute && unresolved) {
this.initCancellationToken = new CancellationTokenSource();
const resolvedByProviders = await this.configurationManager.resolveConfigurationByProviders(launch.workspace ? launch.workspace.uri : undefined, unresolved.type, unresolved, this.initCancellationToken.token);
if (resolvedByProviders) {
resolved = await this.substituteVariables(launch, resolvedByProviders);
} else {
resolved = resolvedByProviders;
}
}
if (isExtensionHostDebugging(session.configuration)) {
return runTasks().then(taskResult => taskResult === TaskRunResult.Success ? this.extensionHostDebugService.reload(session.getId()) : undefined);
}
if (!resolved) {
return c(undefined);
}
const shouldFocus = !!this.viewModel.focusedSession && session.getId() === this.viewModel.focusedSession.getId();
// If the restart is automatic -> disconnect, otherwise -> terminate #55064
return (isAutoRestart ? session.disconnect(true) : session.terminate(true)).then(() => {
session.setConfiguration({ resolved, unresolved });
session.configuration.__restart = restartData;
return new Promise<void>((c, e) => {
setTimeout(() => {
runTasks().then(taskResult => {
if (taskResult !== TaskRunResult.Success) {
return;
}
// Read the configuration again if a launch.json has been changed, if not just use the inmemory configuration
let needsToSubstitute = false;
let unresolved: IConfig | undefined;
const launch = session.root ? this.configurationManager.getLaunch(session.root.uri) : undefined;
if (launch) {
unresolved = launch.getConfiguration(session.configuration.name);
if (unresolved && !equals(unresolved, session.unresolvedConfiguration)) {
// Take the type from the session since the debug extension might overwrite it #21316
unresolved.type = session.configuration.type;
unresolved.noDebug = session.configuration.noDebug;
needsToSubstitute = true;
}
}
let substitutionThenable: Promise<IConfig | null | undefined> = Promise.resolve(session.configuration);
if (launch && needsToSubstitute && unresolved) {
this.initCancellationToken = new CancellationTokenSource();
substitutionThenable = this.configurationManager.resolveConfigurationByProviders(launch.workspace ? launch.workspace.uri : undefined, unresolved.type, unresolved, this.initCancellationToken.token)
.then(resolved => {
if (resolved) {
// start debugging
return this.substituteVariables(launch, resolved);
} else if (resolved === null) {
// abort debugging silently and open launch.json
return Promise.resolve(null);
} else {
// abort debugging silently
return Promise.resolve(undefined);
}
});
}
substitutionThenable.then(resolved => {
if (!resolved) {
return c(undefined);
}
session.setConfiguration({ resolved, unresolved });
session.configuration.__restart = restartData;
this.launchOrAttachToSession(session, shouldFocus).then(() => {
this._onDidNewSession.fire(session);
c(undefined);
}, err => e(err));
});
});
}, 300);
});
});
try {
await this.launchOrAttachToSession(session, shouldFocus);
this._onDidNewSession.fire(session);
c(undefined);
} catch (error) {
e(error);
}
}, 300);
});
}
@@ -646,7 +666,7 @@ export class DebugService implements IDebugService {
return Promise.all(sessions.map(s => s.terminate()));
}
private substituteVariables(launch: ILaunch | undefined, config: IConfig): Promise<IConfig | undefined> {
private async substituteVariables(launch: ILaunch | undefined, config: IConfig): Promise<IConfig | undefined> {
const dbg = this.configurationManager.getDebugger(config.type);
if (dbg) {
let folder: IWorkspaceFolder | undefined = undefined;
@@ -658,12 +678,12 @@ export class DebugService implements IDebugService {
folder = folders[0];
}
}
return dbg.substituteVariables(folder, config).then(config => {
return config;
}, (err: Error) => {
try {
return await dbg.substituteVariables(folder, config);
} catch (err) {
this.showError(err.message);
return undefined; // bail out
});
}
}
return Promise.resolve(config);
}
@@ -681,13 +701,13 @@ export class DebugService implements IDebugService {
//---- task management
private runTaskAndCheckErrors(root: IWorkspaceFolder | undefined, taskId: string | TaskIdentifier | undefined): Promise<TaskRunResult> {
return this.runTask(root, taskId).then((taskSummary: ITaskSummary) => {
private async runTaskAndCheckErrors(root: IWorkspaceFolder | undefined, taskId: string | TaskIdentifier | undefined): Promise<TaskRunResult> {
try {
const taskSummary = await this.runTask(root, taskId);
const errorCount = taskId ? this.markerService.getStatistics().errors : 0;
const successExitCode = taskSummary && taskSummary.exitCode === 0;
const failureExitCode = taskSummary && taskSummary.exitCode !== undefined && taskSummary.exitCode !== 0;
const failureExitCode = taskSummary && taskSummary.exitCode !== 0;
const onTaskErrors = this.configurationService.getValue<IDebugConfiguration>('debug').onTaskErrors;
if (successExitCode || onTaskErrors === 'debugAnyway' || (errorCount === 0 && !failureExitCode)) {
return TaskRunResult.Success;
@@ -702,34 +722,35 @@ export class DebugService implements IDebugService {
? nls.localize('preLaunchTaskErrors', "Errors exist after running preLaunchTask '{0}'.", taskLabel)
: errorCount === 1
? nls.localize('preLaunchTaskError', "Error exists after running preLaunchTask '{0}'.", taskLabel)
: nls.localize('preLaunchTaskExitCode', "The preLaunchTask '{0}' terminated with exit code {1}.", taskLabel, taskSummary.exitCode);
: nls.localize('preLaunchTaskExitCode', "The preLaunchTask '{0}' terminated with exit code {1}.", taskLabel, taskSummary ? taskSummary.exitCode : 0);
return this.dialogService.show(severity.Warning, message, [nls.localize('debugAnyway', "Debug Anyway"), nls.localize('showErrors', "Show Errors"), nls.localize('cancel', "Cancel")], {
const result = await this.dialogService.show(severity.Warning, message, [nls.localize('debugAnyway', "Debug Anyway"), nls.localize('showErrors', "Show Errors"), nls.localize('cancel', "Cancel")], {
checkbox: {
label: nls.localize('remember', "Remember my choice in user settings"),
},
cancelId: 2
}).then(result => {
if (result.choice === 2) {
return Promise.resolve(TaskRunResult.Failure);
}
const debugAnyway = result.choice === 0;
if (result.checkboxChecked) {
this.configurationService.updateValue('debug.onTaskErrors', debugAnyway ? 'debugAnyway' : 'showErrors');
}
if (debugAnyway) {
return TaskRunResult.Success;
}
this.panelService.openPanel(Constants.MARKERS_PANEL_ID);
return Promise.resolve(TaskRunResult.Failure);
});
}, (err: TaskError) => {
return this.showError(err.message, [this.taskService.configureAction()]);
});
if (result.choice === 2) {
return Promise.resolve(TaskRunResult.Failure);
}
const debugAnyway = result.choice === 0;
if (result.checkboxChecked) {
this.configurationService.updateValue('debug.onTaskErrors', debugAnyway ? 'debugAnyway' : 'showErrors');
}
if (debugAnyway) {
return TaskRunResult.Success;
}
this.panelService.openPanel(Constants.MARKERS_PANEL_ID);
return Promise.resolve(TaskRunResult.Failure);
} catch (err) {
await this.showError(err.message, [this.taskService.configureAction()]);
return TaskRunResult.Failure;
}
}
private runTask(root: IWorkspaceFolder | undefined, taskId: string | TaskIdentifier | undefined): Promise<ITaskSummary | null> {
private async runTask(root: IWorkspace | IWorkspaceFolder | undefined, taskId: string | TaskIdentifier | undefined): Promise<ITaskSummary | null> {
if (!taskId) {
return Promise.resolve(null);
}
@@ -737,58 +758,72 @@ export class DebugService implements IDebugService {
return Promise.reject(new Error(nls.localize('invalidTaskReference', "Task '{0}' can not be referenced from a launch configuration that is in a different workspace folder.", typeof taskId === 'string' ? taskId : taskId.type)));
}
// run a task before starting a debug session
return this.taskService.getTask(root, taskId).then(task => {
if (!task) {
const errorMessage = typeof taskId === 'string'
? nls.localize('DebugTaskNotFoundWithTaskId', "Could not find the task '{0}'.", taskId)
: nls.localize('DebugTaskNotFound', "Could not find the specified task.");
return Promise.reject(createErrorWithActions(errorMessage));
const task = await this.taskService.getTask(root, taskId);
if (!task) {
const errorMessage = typeof taskId === 'string'
? nls.localize('DebugTaskNotFoundWithTaskId', "Could not find the task '{0}'.", taskId)
: nls.localize('DebugTaskNotFound', "Could not find the specified task.");
return Promise.reject(createErrorWithActions(errorMessage));
}
// If a task is missing the problem matcher the promise will never complete, so we need to have a workaround #35340
let taskStarted = false;
const inactivePromise: Promise<ITaskSummary | null> = new Promise((c, e) => once(e => {
// When a task isBackground it will go inactive when it is safe to launch.
// But when a background task is terminated by the user, it will also fire an inactive event.
// This means that we will not get to see the real exit code from running the task (undefined when terminated by the user).
// Catch the ProcessEnded event here, which occurs before inactive, and capture the exit code to prevent this.
return (e.kind === TaskEventKind.Inactive
|| (e.kind === TaskEventKind.ProcessEnded && e.exitCode === undefined))
&& e.taskId === task._id;
}, this.taskService.onDidStateChange)(e => {
taskStarted = true;
c(e.kind === TaskEventKind.ProcessEnded ? { exitCode: e.exitCode } : null);
}));
const promise: Promise<ITaskSummary | null> = this.taskService.getActiveTasks().then(async (tasks): Promise<ITaskSummary | null> => {
if (tasks.filter(t => t._id === task._id).length) {
// Check that the task isn't busy and if it is, wait for it
const busyTasks = await this.taskService.getBusyTasks();
if (busyTasks.filter(t => t._id === task._id).length) {
return inactivePromise;
}
// task is already running and isn't busy - nothing to do.
return Promise.resolve(null);
}
once(e => ((e.kind === TaskEventKind.Active) || (e.kind === TaskEventKind.DependsOnStarted)) && e.taskId === task._id, this.taskService.onDidStateChange)(() => {
// Task is active, so everything seems to be fine, no need to prompt after 10 seconds
// Use case being a slow running task should not be prompted even though it takes more than 10 seconds
taskStarted = true;
});
const taskPromise = this.taskService.run(task);
if (task.configurationProperties.isBackground) {
return inactivePromise;
}
// If a task is missing the problem matcher the promise will never complete, so we need to have a workaround #35340
let taskStarted = false;
const promise: Promise<ITaskSummary | null> = this.taskService.getActiveTasks().then(tasks => {
if (tasks.filter(t => t._id === task._id).length) {
// task is already running - nothing to do.
return Promise.resolve(null);
return taskPromise;
});
return new Promise((c, e) => {
promise.then(result => {
taskStarted = true;
c(result);
}, error => e(error));
setTimeout(() => {
if (!taskStarted) {
const errorMessage = typeof taskId === 'string'
? nls.localize('taskNotTrackedWithTaskId', "The specified task cannot be tracked.")
: nls.localize('taskNotTracked', "The task '{0}' cannot be tracked.", JSON.stringify(taskId));
e({ severity: severity.Error, message: errorMessage });
}
once(e => ((e.kind === TaskEventKind.Active) || (e.kind === TaskEventKind.DependsOnStarted)) && e.taskId === task._id, this.taskService.onDidStateChange)(() => {
// Task is active, so everything seems to be fine, no need to prompt after 10 seconds
// Use case being a slow running task should not be prompted even though it takes more than 10 seconds
taskStarted = true;
});
const taskPromise = this.taskService.run(task);
if (task.configurationProperties.isBackground) {
return new Promise((c, e) => once(e => e.kind === TaskEventKind.Inactive && e.taskId === task._id, this.taskService.onDidStateChange)(() => {
taskStarted = true;
c(null);
}));
}
return taskPromise;
});
return new Promise((c, e) => {
promise.then(result => {
taskStarted = true;
c(result);
}, error => e(error));
setTimeout(() => {
if (!taskStarted) {
const errorMessage = typeof taskId === 'string'
? nls.localize('taskNotTrackedWithTaskId', "The specified task cannot be tracked.")
: nls.localize('taskNotTracked', "The task '{0}' cannot be tracked.", JSON.stringify(taskId));
e({ severity: severity.Error, message: errorMessage });
}
}, 10000);
});
}, 10000);
});
}
//---- focus management
focusStackFrame(stackFrame: IStackFrame | undefined, thread?: IThread, session?: IDebugSession, explicit?: boolean): void {
async focusStackFrame(stackFrame: IStackFrame | undefined, thread?: IThread, session?: IDebugSession, explicit?: boolean): Promise<void> {
if (!session) {
if (stackFrame || thread) {
session = stackFrame ? stackFrame.thread.session : thread!.session;
@@ -817,18 +852,17 @@ export class DebugService implements IDebugService {
}
if (stackFrame) {
stackFrame.openInEditor(this.editorService, true).then(editor => {
if (editor) {
const control = editor.getControl();
if (stackFrame && isCodeEditor(control) && control.hasModel()) {
const model = control.getModel();
if (stackFrame.range.startLineNumber <= model.getLineCount()) {
const lineContent = control.getModel().getLineContent(stackFrame.range.startLineNumber);
aria.alert(nls.localize('debuggingPaused', "Debugging paused {0}, {1} {2} {3}", thread && thread.stoppedDetails ? `, reason ${thread.stoppedDetails.reason}` : '', stackFrame.source ? stackFrame.source.name : '', stackFrame.range.startLineNumber, lineContent));
}
const editor = await stackFrame.openInEditor(this.editorService, true);
if (editor) {
const control = editor.getControl();
if (stackFrame && isCodeEditor(control) && control.hasModel()) {
const model = control.getModel();
if (stackFrame.range.startLineNumber <= model.getLineCount()) {
const lineContent = control.getModel().getLineContent(stackFrame.range.startLineNumber);
aria.alert(nls.localize('debuggingPaused', "Debugging paused {0}, {1} {2} {3}", thread && thread.stoppedDetails ? `, reason ${thread.stoppedDetails.reason}` : '', stackFrame.source ? stackFrame.source.name : '', stackFrame.range.startLineNumber, lineContent));
}
}
});
}
}
if (session) {
this.debugType.set(session.configuration.type);
@@ -949,12 +983,12 @@ export class DebugService implements IDebugService {
this.storeBreakpoints();
}
sendAllBreakpoints(session?: IDebugSession): Promise<any> {
return Promise.all(distinct(this.model.getBreakpoints(), bp => bp.uri.toString()).map(bp => this.sendBreakpoints(bp.uri, false, session)))
.then(() => this.sendFunctionBreakpoints(session))
// send exception breakpoints at the end since some debug adapters rely on the order
.then(() => this.sendExceptionBreakpoints(session))
.then(() => this.sendDataBreakpoints(session));
async sendAllBreakpoints(session?: IDebugSession): Promise<any> {
await Promise.all(distinct(this.model.getBreakpoints(), bp => bp.uri.toString()).map(bp => this.sendBreakpoints(bp.uri, false, session)));
await this.sendFunctionBreakpoints(session);
await this.sendDataBreakpoints(session);
// send exception breakpoints at the end since some debug adapters rely on the order
await this.sendExceptionBreakpoints(session);
}
private sendBreakpoints(modelUri: uri, sourceModified = false, session?: IDebugSession): Promise<void> {
@@ -989,11 +1023,12 @@ export class DebugService implements IDebugService {
});
}
private sendToOneOrAllSessions(session: IDebugSession | undefined, send: (session: IDebugSession) => Promise<void>): Promise<void> {
private async sendToOneOrAllSessions(session: IDebugSession | undefined, send: (session: IDebugSession) => Promise<void>): Promise<void> {
if (session) {
return send(session);
await send(session);
} else {
await Promise.all(this.model.getSessions().map(s => send(s)));
}
return Promise.all(this.model.getSessions().map(s => send(s))).then(() => undefined);
}
private onFileChanges(fileChangesEvent: FileChangesEvent): void {

View File

@@ -30,8 +30,6 @@ import { Range } from 'vs/editor/common/core/range';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
import { ReplModel } from 'vs/workbench/contrib/debug/common/replModel';
import { onUnexpectedError } from 'vs/base/common/errors';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { variableSetEmitter } from 'vs/workbench/contrib/debug/browser/variablesView';
import { CancellationTokenSource, CancellationToken } from 'vs/base/common/cancellation';
@@ -65,7 +63,7 @@ export class DebugSession implements IDebugSession {
constructor(
private _configuration: { resolved: IConfig, unresolved: IConfig | undefined },
public root: IWorkspaceFolder,
public root: IWorkspaceFolder | undefined,
private model: DebugModel,
options: IDebugSessionOptions | undefined,
@IDebugService private readonly debugService: IDebugService,
@@ -74,7 +72,6 @@ export class DebugSession implements IDebugSession {
@IConfigurationService private readonly configurationService: IConfigurationService,
@IViewletService private readonly viewletService: IViewletService,
@IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService,
@INotificationService private readonly notificationService: INotificationService,
@IProductService private readonly productService: IProductService,
@IExtensionHostDebugService private readonly extensionHostDebugService: IExtensionHostDebugService,
@IOpenerService private readonly openerService: IOpenerService
@@ -183,110 +180,100 @@ export class DebugSession implements IDebugSession {
/**
* create and initialize a new debug adapter for this session
*/
initialize(dbgr: IDebugger): Promise<void> {
async initialize(dbgr: IDebugger): Promise<void> {
if (this.raw) {
// if there was already a connection make sure to remove old listeners
this.shutdown();
}
return dbgr.getCustomTelemetryService().then(customTelemetryService => {
try {
const customTelemetryService = await dbgr.getCustomTelemetryService();
const debugAdapter = await dbgr.createDebugAdapter(this);
this.raw = new RawDebugSession(debugAdapter, dbgr, this.telemetryService, customTelemetryService, this.extensionHostDebugService, this.openerService);
return dbgr.createDebugAdapter(this).then(debugAdapter => {
this.raw = new RawDebugSession(debugAdapter, dbgr, this.telemetryService, customTelemetryService, this.extensionHostDebugService, this.openerService);
return this.raw.start().then(() => {
this.registerListeners();
return this.raw!.initialize({
clientID: 'vscode',
clientName: this.productService.nameLong,
adapterID: this.configuration.type,
pathFormat: 'path',
linesStartAt1: true,
columnsStartAt1: true,
supportsVariableType: true, // #8858
supportsVariablePaging: true, // #9537
supportsRunInTerminalRequest: true, // #10574
locale: platform.locale
}).then(() => {
this.initialized = true;
this._onDidChangeState.fire();
this.model.setExceptionBreakpoints(this.raw!.capabilities.exceptionBreakpointFilters || []);
});
});
await this.raw.start();
this.registerListeners();
await this.raw!.initialize({
clientID: 'vscode',
clientName: this.productService.nameLong,
adapterID: this.configuration.type,
pathFormat: 'path',
linesStartAt1: true,
columnsStartAt1: true,
supportsVariableType: true, // #8858
supportsVariablePaging: true, // #9537
supportsRunInTerminalRequest: true, // #10574
locale: platform.locale
});
}).then(undefined, err => {
this.initialized = true;
this._onDidChangeState.fire();
return Promise.reject(err);
});
this.model.setExceptionBreakpoints(this.raw!.capabilities.exceptionBreakpointFilters || []);
} catch (err) {
this.initialized = true;
this._onDidChangeState.fire();
throw err;
}
}
/**
* launch or attach to the debuggee
*/
launchOrAttach(config: IConfig): Promise<void> {
if (this.raw) {
// __sessionID only used for EH debugging (but we add it always for now...)
config.__sessionId = this.getId();
return this.raw.launchOrAttach(config).then(result => {
return undefined;
});
async launchOrAttach(config: IConfig): Promise<void> {
if (!this.raw) {
throw new Error('no debug adapter');
}
return Promise.reject(new Error('no debug adapter'));
// __sessionID only used for EH debugging (but we add it always for now...)
config.__sessionId = this.getId();
await this.raw.launchOrAttach(config);
}
/**
* end the current debug adapter session
*/
terminate(restart = false): Promise<void> {
if (this.raw) {
this.cancelAllRequests();
if (this.raw.capabilities.supportsTerminateRequest && this._configuration.resolved.request === 'launch') {
return this.raw.terminate(restart).then(response => {
return undefined;
});
}
return this.raw.disconnect(restart).then(response => {
return undefined;
});
async terminate(restart = false): Promise<void> {
if (!this.raw) {
throw new Error('no debug adapter');
}
this.cancelAllRequests();
if (this.raw.capabilities.supportsTerminateRequest && this._configuration.resolved.request === 'launch') {
await this.raw.terminate(restart);
} else {
await this.raw.disconnect(restart);
}
return Promise.reject(new Error('no debug adapter'));
}
/**
* end the current debug adapter session
*/
disconnect(restart = false): Promise<void> {
if (this.raw) {
this.cancelAllRequests();
return this.raw.disconnect(restart).then(response => {
return undefined;
});
async disconnect(restart = false): Promise<void> {
if (!this.raw) {
throw new Error('no debug adapter');
}
return Promise.reject(new Error('no debug adapter'));
this.cancelAllRequests();
await this.raw.disconnect(restart);
}
/**
* restart debug adapter session
*/
restart(): Promise<void> {
if (this.raw) {
this.cancelAllRequests();
return this.raw.restart().then(() => undefined);
async restart(): Promise<void> {
if (!this.raw) {
throw new Error('no debug adapter');
}
return Promise.reject(new Error('no debug adapter'));
this.cancelAllRequests();
await this.raw.restart();
}
sendBreakpoints(modelUri: URI, breakpointsToSend: IBreakpoint[], sourceModified: boolean): Promise<void> {
async sendBreakpoints(modelUri: URI, breakpointsToSend: IBreakpoint[], sourceModified: boolean): Promise<void> {
if (!this.raw) {
return Promise.reject(new Error('no debug adapter'));
throw new Error('no debug adapter');
}
if (!this.raw.readyForBreakpoints) {
@@ -302,233 +289,252 @@ export class DebugSession implements IDebugSession {
rawSource.path = normalizeDriveLetter(rawSource.path);
}
return this.raw.setBreakpoints({
const response = await this.raw.setBreakpoints({
source: rawSource,
lines: breakpointsToSend.map(bp => bp.sessionAgnosticData.lineNumber),
breakpoints: breakpointsToSend.map(bp => ({ line: bp.sessionAgnosticData.lineNumber, column: bp.sessionAgnosticData.column, condition: bp.condition, hitCondition: bp.hitCondition, logMessage: bp.logMessage })),
sourceModified
}).then(response => {
});
if (response && response.body) {
const data = new Map<string, DebugProtocol.Breakpoint>();
for (let i = 0; i < breakpointsToSend.length; i++) {
data.set(breakpointsToSend[i].getId(), response.body.breakpoints[i]);
}
this.model.setBreakpointSessionData(this.getId(), this.capabilities, data);
}
}
async sendFunctionBreakpoints(fbpts: IFunctionBreakpoint[]): Promise<void> {
if (!this.raw) {
throw new Error('no debug adapter');
}
if (this.raw.readyForBreakpoints) {
const response = await this.raw.setFunctionBreakpoints({ breakpoints: fbpts });
if (response && response.body) {
const data = new Map<string, DebugProtocol.Breakpoint>();
for (let i = 0; i < breakpointsToSend.length; i++) {
data.set(breakpointsToSend[i].getId(), response.body.breakpoints[i]);
for (let i = 0; i < fbpts.length; i++) {
data.set(fbpts[i].getId(), response.body.breakpoints[i]);
}
this.model.setBreakpointSessionData(this.getId(), this.capabilities, data);
}
});
}
}
sendFunctionBreakpoints(fbpts: IFunctionBreakpoint[]): Promise<void> {
if (this.raw) {
if (this.raw.readyForBreakpoints) {
return this.raw.setFunctionBreakpoints({ breakpoints: fbpts }).then(response => {
if (response && response.body) {
const data = new Map<string, DebugProtocol.Breakpoint>();
for (let i = 0; i < fbpts.length; i++) {
data.set(fbpts[i].getId(), response.body.breakpoints[i]);
}
this.model.setBreakpointSessionData(this.getId(), this.capabilities, data);
}
});
}
return Promise.resolve(undefined);
async sendExceptionBreakpoints(exbpts: IExceptionBreakpoint[]): Promise<void> {
if (!this.raw) {
throw new Error('no debug adapter');
}
return Promise.reject(new Error('no debug adapter'));
if (this.raw.readyForBreakpoints) {
await this.raw.setExceptionBreakpoints({ filters: exbpts.map(exb => exb.filter) });
}
}
sendExceptionBreakpoints(exbpts: IExceptionBreakpoint[]): Promise<void> {
if (this.raw) {
if (this.raw.readyForBreakpoints) {
return this.raw.setExceptionBreakpoints({ filters: exbpts.map(exb => exb.filter) }).then(() => undefined);
}
return Promise.resolve(undefined);
async dataBreakpointInfo(name: string, variablesReference?: number): Promise<{ dataId: string | null, description: string, canPersist?: boolean }> {
if (!this.raw) {
throw new Error('no debug adapter');
}
return Promise.reject(new Error('no debug adapter'));
if (!this.raw.readyForBreakpoints) {
throw new Error(nls.localize('sessionNotReadyForBreakpoints', "Session is not ready for breakpoints"));
}
const response = await this.raw.dataBreakpointInfo({ name, variablesReference });
return response.body;
}
dataBreakpointInfo(name: string, variablesReference?: number): Promise<{ dataId: string | null, description: string, canPersist?: boolean }> {
if (this.raw) {
if (this.raw.readyForBreakpoints) {
return this.raw.dataBreakpointInfo({ name, variablesReference }).then(response => response.body);
}
return Promise.reject(new Error(nls.localize('sessionNotReadyForBreakpoints', "Session is not ready for breakpoints")));
async sendDataBreakpoints(dataBreakpoints: IDataBreakpoint[]): Promise<void> {
if (!this.raw) {
throw new Error('no debug adapter');
}
return Promise.reject(new Error('no debug adapter'));
}
sendDataBreakpoints(dataBreakpoints: IDataBreakpoint[]): Promise<void> {
if (this.raw) {
if (this.raw.readyForBreakpoints) {
return this.raw.setDataBreakpoints({ breakpoints: dataBreakpoints }).then(response => {
if (response && response.body) {
const data = new Map<string, DebugProtocol.Breakpoint>();
for (let i = 0; i < dataBreakpoints.length; i++) {
data.set(dataBreakpoints[i].getId(), response.body.breakpoints[i]);
}
this.model.setBreakpointSessionData(this.getId(), this.capabilities, data);
}
});
if (this.raw.readyForBreakpoints) {
const response = await this.raw.setDataBreakpoints({ breakpoints: dataBreakpoints });
if (response && response.body) {
const data = new Map<string, DebugProtocol.Breakpoint>();
for (let i = 0; i < dataBreakpoints.length; i++) {
data.set(dataBreakpoints[i].getId(), response.body.breakpoints[i]);
}
this.model.setBreakpointSessionData(this.getId(), this.capabilities, data);
}
return Promise.resolve(undefined);
}
return Promise.reject(new Error('no debug adapter'));
}
async breakpointsLocations(uri: URI, lineNumber: number): Promise<IPosition[]> {
if (this.raw) {
const source = this.getRawSource(uri);
const response = await this.raw.breakpointLocations({ source, line: lineNumber });
const positions = response.body.breakpoints.map(bp => ({ lineNumber: bp.line, column: bp.column || 1 }));
return distinct(positions, p => `${p.lineNumber}:${p.column}`);
if (!this.raw) {
throw new Error('no debug adapter');
}
return Promise.reject(new Error('no debug adapter'));
const source = this.getRawSource(uri);
const response = await this.raw.breakpointLocations({ source, line: lineNumber });
if (!response.body || !response.body.breakpoints) {
return [];
}
const positions = response.body.breakpoints.map(bp => ({ lineNumber: bp.line, column: bp.column || 1 }));
return distinct(positions, p => `${p.lineNumber}:${p.column}`);
}
customRequest(request: string, args: any): Promise<DebugProtocol.Response> {
if (this.raw) {
return this.raw.custom(request, args);
if (!this.raw) {
throw new Error('no debug adapter');
}
return Promise.reject(new Error('no debug adapter'));
return this.raw.custom(request, args);
}
stackTrace(threadId: number, startFrame: number, levels: number): Promise<DebugProtocol.StackTraceResponse> {
if (this.raw) {
const token = this.getNewCancellationToken(threadId);
return this.raw.stackTrace({ threadId, startFrame, levels }, token);
if (!this.raw) {
throw new Error('no debug adapter');
}
return Promise.reject(new Error('no debug adapter'));
const token = this.getNewCancellationToken(threadId);
return this.raw.stackTrace({ threadId, startFrame, levels }, token);
}
exceptionInfo(threadId: number): Promise<IExceptionInfo | undefined> {
if (this.raw) {
return this.raw.exceptionInfo({ threadId }).then(response => {
if (response) {
return {
id: response.body.exceptionId,
description: response.body.description,
breakMode: response.body.breakMode,
details: response.body.details
};
}
return undefined;
});
async exceptionInfo(threadId: number): Promise<IExceptionInfo | undefined> {
if (!this.raw) {
throw new Error('no debug adapter');
}
return Promise.reject(new Error('no debug adapter'));
const response = await this.raw.exceptionInfo({ threadId });
if (response) {
return {
id: response.body.exceptionId,
description: response.body.description,
breakMode: response.body.breakMode,
details: response.body.details
};
}
return undefined;
}
scopes(frameId: number, threadId: number): Promise<DebugProtocol.ScopesResponse> {
if (this.raw) {
const token = this.getNewCancellationToken(threadId);
return this.raw.scopes({ frameId }, token);
if (!this.raw) {
throw new Error('no debug adapter');
}
return Promise.reject(new Error('no debug adapter'));
const token = this.getNewCancellationToken(threadId);
return this.raw.scopes({ frameId }, token);
}
variables(variablesReference: number, threadId: number | undefined, filter: 'indexed' | 'named' | undefined, start: number | undefined, count: number | undefined): Promise<DebugProtocol.VariablesResponse> {
if (this.raw) {
const token = threadId ? this.getNewCancellationToken(threadId) : undefined;
return this.raw.variables({ variablesReference, filter, start, count }, token);
if (!this.raw) {
throw new Error('no debug adapter');
}
return Promise.reject(new Error('no debug adapter'));
const token = threadId ? this.getNewCancellationToken(threadId) : undefined;
return this.raw.variables({ variablesReference, filter, start, count }, token);
}
evaluate(expression: string, frameId: number, context?: string): Promise<DebugProtocol.EvaluateResponse> {
if (this.raw) {
return this.raw.evaluate({ expression, frameId, context });
if (!this.raw) {
throw new Error('no debug adapter');
}
return Promise.reject(new Error('no debug adapter'));
return this.raw.evaluate({ expression, frameId, context });
}
restartFrame(frameId: number, threadId: number): Promise<void> {
if (this.raw) {
return this.raw.restartFrame({ frameId }, threadId).then(() => undefined);
async restartFrame(frameId: number, threadId: number): Promise<void> {
if (!this.raw) {
throw new Error('no debug adapter');
}
return Promise.reject(new Error('no debug adapter'));
await this.raw.restartFrame({ frameId }, threadId);
}
next(threadId: number): Promise<void> {
if (this.raw) {
return this.raw.next({ threadId }).then(() => undefined);
async next(threadId: number): Promise<void> {
if (!this.raw) {
throw new Error('no debug adapter');
}
return Promise.reject(new Error('no debug adapter'));
await this.raw.next({ threadId });
}
stepIn(threadId: number): Promise<void> {
if (this.raw) {
return this.raw.stepIn({ threadId }).then(() => undefined);
async stepIn(threadId: number): Promise<void> {
if (!this.raw) {
throw new Error('no debug adapter');
}
return Promise.reject(new Error('no debug adapter'));
await this.raw.stepIn({ threadId });
}
stepOut(threadId: number): Promise<void> {
if (this.raw) {
return this.raw.stepOut({ threadId }).then(() => undefined);
async stepOut(threadId: number): Promise<void> {
if (!this.raw) {
throw new Error('no debug adapter');
}
return Promise.reject(new Error('no debug adapter'));
await this.raw.stepOut({ threadId });
}
stepBack(threadId: number): Promise<void> {
if (this.raw) {
return this.raw.stepBack({ threadId }).then(() => undefined);
async stepBack(threadId: number): Promise<void> {
if (!this.raw) {
throw new Error('no debug adapter');
}
return Promise.reject(new Error('no debug adapter'));
await this.raw.stepBack({ threadId });
}
continue(threadId: number): Promise<void> {
if (this.raw) {
return this.raw.continue({ threadId }).then(() => undefined);
async continue(threadId: number): Promise<void> {
if (!this.raw) {
throw new Error('no debug adapter');
}
return Promise.reject(new Error('no debug adapter'));
await this.raw.continue({ threadId });
}
reverseContinue(threadId: number): Promise<void> {
if (this.raw) {
return this.raw.reverseContinue({ threadId }).then(() => undefined);
async reverseContinue(threadId: number): Promise<void> {
if (!this.raw) {
throw new Error('no debug adapter');
}
return Promise.reject(new Error('no debug adapter'));
await this.raw.reverseContinue({ threadId });
}
pause(threadId: number): Promise<void> {
if (this.raw) {
return this.raw.pause({ threadId }).then(() => undefined);
async pause(threadId: number): Promise<void> {
if (!this.raw) {
throw new Error('no debug adapter');
}
return Promise.reject(new Error('no debug adapter'));
await this.raw.pause({ threadId });
}
terminateThreads(threadIds?: number[]): Promise<void> {
if (this.raw) {
return this.raw.terminateThreads({ threadIds }).then(() => undefined);
async terminateThreads(threadIds?: number[]): Promise<void> {
if (!this.raw) {
throw new Error('no debug adapter');
}
return Promise.reject(new Error('no debug adapter'));
await this.raw.terminateThreads({ threadIds });
}
setVariable(variablesReference: number, name: string, value: string): Promise<DebugProtocol.SetVariableResponse> {
if (this.raw) {
return this.raw.setVariable({ variablesReference, name, value });
if (!this.raw) {
throw new Error('no debug adapter');
}
return Promise.reject(new Error('no debug adapter'));
return this.raw.setVariable({ variablesReference, name, value });
}
gotoTargets(source: DebugProtocol.Source, line: number, column?: number): Promise<DebugProtocol.GotoTargetsResponse> {
if (this.raw) {
return this.raw.gotoTargets({ source, line, column });
if (!this.raw) {
throw new Error('no debug adapter');
}
return Promise.reject(new Error('no debug adapter'));
return this.raw.gotoTargets({ source, line, column });
}
goto(threadId: number, targetId: number): Promise<DebugProtocol.GotoResponse> {
if (this.raw) {
return this.raw.goto({ threadId, targetId });
if (!this.raw) {
throw new Error('no debug adapter');
}
return Promise.reject(new Error('no debug adapter'));
return this.raw.goto({ threadId, targetId });
}
loadSource(resource: URI): Promise<DebugProtocol.SourceResponse> {
if (!this.raw) {
return Promise.reject(new Error('no debug adapter'));
}
@@ -546,50 +552,48 @@ export class DebugSession implements IDebugSession {
return this.raw.source({ sourceReference: rawSource.sourceReference || 0, source: rawSource });
}
getLoadedSources(): Promise<Source[]> {
if (this.raw) {
return this.raw.loadedSources({}).then(response => {
if (response.body && response.body.sources) {
return response.body.sources.map(src => this.getSource(src));
} else {
return [];
}
}, () => {
return [];
});
async getLoadedSources(): Promise<Source[]> {
if (!this.raw) {
return Promise.reject(new Error('no debug adapter'));
}
const response = await this.raw.loadedSources({});
if (response.body && response.body.sources) {
return response.body.sources.map(src => this.getSource(src));
} else {
return [];
}
return Promise.reject(new Error('no debug adapter'));
}
completions(frameId: number | undefined, text: string, position: Position, overwriteBefore: number, token: CancellationToken): Promise<CompletionItem[]> {
if (this.raw) {
return this.raw.completions({
frameId,
text,
column: position.column,
line: position.lineNumber,
}, token).then(response => {
async completions(frameId: number | undefined, text: string, position: Position, overwriteBefore: number, token: CancellationToken): Promise<CompletionItem[]> {
if (!this.raw) {
return Promise.reject(new Error('no debug adapter'));
}
const result: CompletionItem[] = [];
if (response && response.body && response.body.targets) {
response.body.targets.forEach(item => {
if (item && item.label) {
result.push({
label: item.label,
insertText: item.text || item.label,
kind: completionKindFromString(item.type || 'property'),
filterText: (item.start && item.length) ? text.substr(item.start, item.length).concat(item.label) : undefined,
range: Range.fromPositions(position.delta(0, -(item.length || overwriteBefore)), position),
sortText: item.sortText
});
}
const response = await this.raw.completions({
frameId,
text,
column: position.column,
line: position.lineNumber,
}, token);
const result: CompletionItem[] = [];
if (response && response.body && response.body.targets) {
response.body.targets.forEach(item => {
if (item && item.label) {
result.push({
label: item.label,
insertText: item.text || item.label,
kind: completionKindFromString(item.type || 'property'),
filterText: (item.start && item.length) ? text.substr(item.start, item.length).concat(item.label) : undefined,
range: Range.fromPositions(position.delta(0, -(item.length || overwriteBefore)), position),
sortText: item.sortText
});
}
return result;
});
}
return Promise.reject(new Error('no debug adapter'));
return result;
}
//---- threads
@@ -674,8 +678,9 @@ export class DebugSession implements IDebugSession {
}
}
private fetchThreads(stoppedDetails?: IRawStoppedDetails): Promise<void> {
return this.raw ? this.raw.threads().then(response => {
private async fetchThreads(stoppedDetails?: IRawStoppedDetails): Promise<void> {
if (this.raw) {
const response = await this.raw.threads();
if (response && response.body && response.body.threads) {
this.model.rawUpdate({
sessionId: this.getId(),
@@ -683,7 +688,7 @@ export class DebugSession implements IDebugSession {
stoppedDetails
});
}
}) : Promise.resolve(undefined);
}
}
//---- private
@@ -693,60 +698,63 @@ export class DebugSession implements IDebugSession {
return;
}
this.rawListeners.push(this.raw.onDidInitialize(() => {
this.rawListeners.push(this.raw.onDidInitialize(async () => {
aria.status(nls.localize('debuggingStarted', "Debugging started."));
const sendConfigurationDone = () => {
const sendConfigurationDone = async () => {
if (this.raw && this.raw.capabilities.supportsConfigurationDoneRequest) {
return this.raw.configurationDone().then(undefined, e => {
try {
await this.raw.configurationDone();
} catch (e) {
// Disconnect the debug session on configuration done error #10596
if (this.raw) {
this.raw.disconnect();
}
if (e.command !== 'canceled' && e.message !== 'canceled') {
this.notificationService.error(e);
}
});
}
}
return undefined;
};
// Send all breakpoints
this.debugService.sendAllBreakpoints(this).then(sendConfigurationDone, sendConfigurationDone)
.then(() => this.fetchThreads());
try {
await this.debugService.sendAllBreakpoints(this);
} finally {
await sendConfigurationDone();
}
await this.fetchThreads();
}));
this.rawListeners.push(this.raw.onDidStop(event => {
this.fetchThreads(event.body).then(() => {
const thread = typeof event.body.threadId === 'number' ? this.getThread(event.body.threadId) : undefined;
if (thread) {
// Call fetch call stack twice, the first only return the top stack frame.
// Second retrieves the rest of the call stack. For performance reasons #25605
const promises = this.model.fetchCallStack(<Thread>thread);
const focus = () => {
if (!event.body.preserveFocusHint && thread.getCallStack().length) {
this.debugService.focusStackFrame(undefined, thread);
if (thread.stoppedDetails) {
if (this.configurationService.getValue<IDebugConfiguration>('debug').openDebug === 'openOnDebugBreak') {
this.viewletService.openViewlet(VIEWLET_ID);
}
this.rawListeners.push(this.raw.onDidStop(async event => {
await this.fetchThreads(event.body);
const thread = typeof event.body.threadId === 'number' ? this.getThread(event.body.threadId) : undefined;
if (thread) {
// Call fetch call stack twice, the first only return the top stack frame.
// Second retrieves the rest of the call stack. For performance reasons #25605
const promises = this.model.fetchCallStack(<Thread>thread);
const focus = async () => {
if (!event.body.preserveFocusHint && thread.getCallStack().length) {
await this.debugService.focusStackFrame(undefined, thread);
if (thread.stoppedDetails) {
if (this.configurationService.getValue<IDebugConfiguration>('debug').openDebug === 'openOnDebugBreak') {
this.viewletService.openViewlet(VIEWLET_ID);
}
if (this.configurationService.getValue<IDebugConfiguration>('debug').focusWindowOnBreak) {
this.hostService.focus();
}
if (this.configurationService.getValue<IDebugConfiguration>('debug').focusWindowOnBreak) {
this.hostService.focus();
}
}
};
}
};
promises.topCallStack.then(focus);
promises.wholeCallStack.then(() => {
if (!this.debugService.getViewModel().focusedStackFrame) {
// The top stack frame can be deemphesized so try to focus again #68616
focus();
}
});
await promises.topCallStack;
focus();
await promises.wholeCallStack;
if (!this.debugService.getViewModel().focusedStackFrame) {
// The top stack frame can be deemphesized so try to focus again #68616
focus();
}
}).then(() => this._onDidChangeState.fire());
}
this._onDidChangeState.fire();
}));
this.rawListeners.push(this.raw.onDidThread(event => {
@@ -763,15 +771,21 @@ export class DebugSession implements IDebugSession {
}
} else if (event.body.reason === 'exited') {
this.model.clearThreads(this.getId(), true, event.body.threadId);
const viewModel = this.debugService.getViewModel();
const focusedThread = viewModel.focusedThread;
if (focusedThread && event.body.threadId === focusedThread.threadId) {
// De-focus the thread in case it was focused
this.debugService.focusStackFrame(undefined, undefined, viewModel.focusedSession, false);
}
}
}));
this.rawListeners.push(this.raw.onDidTerminateDebugee(event => {
this.rawListeners.push(this.raw.onDidTerminateDebugee(async event => {
aria.status(nls.localize('debuggingStopped', "Debugging stopped."));
if (event.body && event.body.restart) {
this.debugService.restartSession(this, event.body.restart).then(undefined, onUnexpectedError);
await this.debugService.restartSession(this, event.body.restart);
} else if (this.raw) {
this.raw.disconnect();
await this.raw.disconnect();
}
}));
@@ -792,7 +806,7 @@ export class DebugSession implements IDebugSession {
}));
let outpuPromises: Promise<void>[] = [];
this.rawListeners.push(this.raw.onDidOutput(event => {
this.rawListeners.push(this.raw.onDidOutput(async event => {
if (!event.body || !this.raw) {
return;
}
@@ -818,17 +832,21 @@ export class DebugSession implements IDebugSession {
} : undefined;
if (event.body.variablesReference) {
const container = new ExpressionContainer(this, undefined, event.body.variablesReference, generateUuid());
outpuPromises.push(container.getChildren().then(children => {
return Promise.all(waitFor).then(() => children.forEach(child => {
outpuPromises.push(container.getChildren().then(async children => {
await Promise.all(waitFor);
children.forEach(child => {
// Since we can not display multiple trees in a row, we are displaying these variables one after the other (ignoring their names)
(<any>child).name = null;
this.appendToRepl(child, outputSeverity, source);
}));
});
}));
} else if (typeof event.body.output === 'string') {
Promise.all(waitFor).then(() => this.appendToRepl(event.body.output, outputSeverity, source));
await Promise.all(waitFor);
this.appendToRepl(event.body.output, outputSeverity, source);
}
Promise.all(outpuPromises).then(() => outpuPromises = []);
await Promise.all(outpuPromises);
outpuPromises = [];
}));
this.rawListeners.push(this.raw.onDidBreakpoint(event => {

View File

@@ -176,7 +176,7 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution {
});
}));
this._register(this.layoutService.onTitleBarVisibilityChange(() => this.setYCoordinate()));
this._register(this.layoutService.onPartVisibilityChange(() => this.setYCoordinate()));
this._register(browser.onDidChangeZoomLevel(() => this.setYCoordinate()));
}
@@ -192,10 +192,10 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution {
super.updateStyles();
if (this.$el) {
this.$el.style.backgroundColor = this.getColor(debugToolBarBackground);
this.$el.style.backgroundColor = this.getColor(debugToolBarBackground) || '';
const widgetShadowColor = this.getColor(widgetShadow);
this.$el.style.boxShadow = widgetShadowColor ? `0 5px 8px ${widgetShadowColor}` : null;
this.$el.style.boxShadow = widgetShadowColor ? `0 5px 8px ${widgetShadowColor}` : '';
const contrastBorderColor = this.getColor(contrastBorder);
const borderColor = this.getColor(debugToolBarBorder);

View File

@@ -214,7 +214,7 @@ export class DebugViewlet extends ViewContainerViewlet {
class ToggleReplAction extends TogglePanelAction {
static readonly ID = 'debug.toggleRepl';
static LABEL = nls.localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'debugConsoleAction' }, 'Debug Console');
static readonly LABEL = nls.localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'debugConsoleAction' }, 'Debug Console');
constructor(id: string, label: string,
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,

View File

@@ -5,7 +5,6 @@
import { ExtensionHostDebugChannelClient, ExtensionHostDebugBroadcastChannel } from 'vs/platform/debug/common/extensionHostDebugIpc';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IExtensionHostDebugService } from 'vs/platform/debug/common/extensionHostDebug';
import { IDebugHelperService } from 'vs/workbench/contrib/debug/common/debug';
@@ -13,17 +12,20 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
import { TelemetryService } from 'vs/platform/telemetry/common/telemetryService';
import { IChannel } from 'vs/base/parts/ipc/common/ipc';
import { Event } from 'vs/base/common/event';
import { IProcessEnvironment } from 'vs/base/common/platform';
import { URI } from 'vs/base/common/uri';
import { mapToSerializable } from 'vs/base/common/map';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { IWorkspaceProvider, IWorkspace } from 'vs/workbench/services/host/browser/browserHostService';
class BrowserExtensionHostDebugService extends ExtensionHostDebugChannelClient implements IExtensionHostDebugService {
private workspaceProvider: IWorkspaceProvider;
constructor(
@IRemoteAgentService remoteAgentService: IRemoteAgentService,
@IEnvironmentService environmentService: IEnvironmentService
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService
) {
const connection = remoteAgentService.getConnection();
let channel: IChannel;
if (connection) {
channel = connection.getChannel(ExtensionHostDebugBroadcastChannel.ChannelName);
@@ -35,11 +37,26 @@ class BrowserExtensionHostDebugService extends ExtensionHostDebugChannelClient i
super(channel);
if (environmentService.options && environmentService.options.workspaceProvider) {
this.workspaceProvider = environmentService.options.workspaceProvider;
} else {
this.workspaceProvider = { open: async () => undefined, workspace: undefined };
console.warn('Extension Host Debugging not available due to missing workspace provider.');
}
this.registerListeners(environmentService);
}
private registerListeners(environmentService: IWorkbenchEnvironmentService): void {
// Reload window on reload request
this._register(this.onReload(event => {
if (environmentService.isExtensionDevelopment && environmentService.debugExtensionHost.debugId === event.sessionId) {
window.location.reload();
}
}));
// Close window on close request
this._register(this.onClose(event => {
if (environmentService.isExtensionDevelopment && environmentService.debugExtensionHost.debugId === event.sessionId) {
window.close();
@@ -47,8 +64,46 @@ class BrowserExtensionHostDebugService extends ExtensionHostDebugChannelClient i
}));
}
openExtensionDevelopmentHostWindow(args: ParsedArgs, env: IProcessEnvironment): Promise<void> {
// we pass the "ParsedArgs" as query parameters of the URL
async openExtensionDevelopmentHostWindow(args: string[]): Promise<void> {
if (!this.workspaceProvider.payload) {
// TODO@Ben remove me once environment is adopted
return this.openExtensionDevelopmentHostWindowLegacy(args);
}
// Find out which workspace to open debug window on
let debugWorkspace: IWorkspace = undefined;
const folderUriArg = this.findArgument('folder-uri', args);
if (folderUriArg) {
debugWorkspace = { folderUri: URI.parse(folderUriArg) };
}
// Add environment parameters required for debug to work
const environment = new Map<string, string>();
const extensionDevelopmentPath = this.findArgument('extensionDevelopmentPath', args);
if (extensionDevelopmentPath) {
environment.set('extensionDevelopmentPath', extensionDevelopmentPath);
}
const debugId = this.findArgument('debugId', args);
if (debugId) {
environment.set('debugId', debugId);
}
const inspectBrkExtensions = this.findArgument('inspect-brk-extensions', args);
if (inspectBrkExtensions) {
environment.set('inspect-brk-extensions', inspectBrkExtensions);
}
// Open debug window as new window. Pass ParsedArgs over.
this.workspaceProvider.open(debugWorkspace, {
reuse: false, // debugging always requires a new window
payload: mapToSerializable(environment) // mandatory properties to enable debugging
});
}
private async openExtensionDevelopmentHostWindowLegacy(args: string[]): Promise<void> {
// we pass the "args" as query parameters of the URL
let newAddress = `${document.location.origin}${document.location.pathname}?`;
let gotFolder = false;
@@ -61,9 +116,19 @@ class BrowserExtensionHostDebugService extends ExtensionHostDebugChannelClient i
newAddress += `${key}=${encodeURIComponent(value)}`;
};
const f = args['folder-uri'];
const findArgument = (key: string) => {
for (let a of args) {
const k = `--${key}=`;
if (a.indexOf(k) === 0) {
return a.substr(k.length);
}
}
return undefined;
};
const f = findArgument('folder-uri');
if (f) {
const u = URI.parse(f[0]);
const u = URI.parse(f);
gotFolder = true;
addQueryParameter('folder', u.path);
}
@@ -72,26 +137,36 @@ class BrowserExtensionHostDebugService extends ExtensionHostDebugChannelClient i
addQueryParameter('ew', 'true');
}
const ep = args['extensionDevelopmentPath'];
const ep = findArgument('extensionDevelopmentPath');
if (ep) {
let u = ep[0];
addQueryParameter('edp', u);
addQueryParameter('extensionDevelopmentPath', ep);
}
const di = args['debugId'];
const di = findArgument('debugId');
if (di) {
addQueryParameter('di', di);
addQueryParameter('debugId', di);
}
const ibe = args['inspect-brk-extensions'];
const ibe = findArgument('inspect-brk-extensions');
if (ibe) {
addQueryParameter('ibe', ibe);
addQueryParameter('inspect-brk-extensions', ibe);
}
window.open(newAddress);
return Promise.resolve();
}
private findArgument(key: string, args: string[]): string | undefined {
for (const a of args) {
const k = `--${key}=`;
if (a.indexOf(k) === 0) {
return a.substr(k.length);
}
}
return undefined;
}
}
registerSingleton(IExtensionHostDebugService, BrowserExtensionHostDebugService);

View File

@@ -240,7 +240,7 @@ class RootTreeItem extends BaseTreeItem {
class SessionTreeItem extends BaseTreeItem {
private static URL_REGEXP = /^(https?:\/\/[^/]+)(\/.*)$/;
private static readonly URL_REGEXP = /^(https?:\/\/[^/]+)(\/.*)$/;
private _session: IDebugSession;
private _initialized: boolean;

View File

@@ -14,6 +14,7 @@
.debug-breakpoint-hint {
background: url('breakpoint-hint.svg') center center no-repeat;
cursor: pointer;
}
.debug-breakpoint-disabled,
@@ -50,7 +51,7 @@
.monaco-editor .debug-breakpoint-placeholder::before,
.monaco-editor .debug-top-stack-frame-column::before {
content: " ";
width: 1.3em;
width: 0.9em;
display: inline-block;
vertical-align: text-bottom;
margin-right: 2px;
@@ -62,9 +63,6 @@
}
.monaco-editor .inline-breakpoint-widget {
width: 1.3em;
height: 1.3em;
margin-left: 0.61em;
cursor: pointer;
}
@@ -178,7 +176,7 @@
/* White color when element is selected and list is focused. White looks better on blue selection background. */
.monaco-workbench .monaco-list:focus .monaco-list-row.selected .expression .name,
.monaco-workbench .monaco-list:focus .monaco-list-row.selected .expression .value {
color: white;
color: inherit;
}
.monaco-workbench .monaco-list-row .expression .name {

View File

@@ -22,6 +22,7 @@
padding-left: 15px;
padding-right: 2px;
font-size: 11px;
line-height: 18px;
word-break: normal;
text-overflow: ellipsis;
height: 18px;

View File

@@ -12,7 +12,6 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { formatPII, isUri } from 'vs/workbench/contrib/debug/common/debugUtils';
import { IDebugAdapter, IConfig, AdapterEndEvent, IDebugger } from 'vs/workbench/contrib/debug/common/debug';
import { createErrorWithActions } from 'vs/base/common/errorsWithActions';
import { ParsedArgs } from 'vs/platform/environment/common/environment';
import { IExtensionHostDebugService } from 'vs/platform/debug/common/extensionHostDebug';
import { URI } from 'vs/base/common/uri';
import { IProcessEnvironment } from 'vs/base/common/platform';
@@ -228,25 +227,23 @@ export class RawDebugSession implements IDisposable {
/**
* Starts the underlying debug adapter and tracks the session time for telemetry.
*/
start(): Promise<void> {
async start(): Promise<void> {
if (!this.debugAdapter) {
return Promise.reject(new Error('no debug adapter'));
}
return this.debugAdapter.startSession().then(() => {
this.startTime = new Date().getTime();
}, err => {
return Promise.reject(err);
});
await this.debugAdapter.startSession();
this.startTime = new Date().getTime();
}
/**
* Send client capabilities to the debug adapter and receive DA capabilities in return.
*/
initialize(args: DebugProtocol.InitializeRequestArguments): Promise<DebugProtocol.InitializeResponse> {
return this.send('initialize', args).then((response: DebugProtocol.InitializeResponse) => {
this.mergeCapabilities(response.body);
return response;
});
async initialize(args: DebugProtocol.InitializeRequestArguments): Promise<DebugProtocol.InitializeResponse> {
const response = await this.send('initialize', args);
this.mergeCapabilities(response.body);
return response;
}
/**
@@ -258,11 +255,11 @@ export class RawDebugSession implements IDisposable {
//---- DAP requests
launchOrAttach(config: IConfig): Promise<DebugProtocol.Response> {
return this.send(config.request, config).then(response => {
this.mergeCapabilities(response.body);
return response;
});
async launchOrAttach(config: IConfig): Promise<DebugProtocol.Response> {
const response = await this.send(config.request, config);
this.mergeCapabilities(response.body);
return response;
}
/**
@@ -286,35 +283,32 @@ export class RawDebugSession implements IDisposable {
return Promise.reject(new Error('restart not supported'));
}
next(args: DebugProtocol.NextArguments): Promise<DebugProtocol.NextResponse> {
return this.send('next', args).then(response => {
this.fireSimulatedContinuedEvent(args.threadId);
return response;
});
async next(args: DebugProtocol.NextArguments): Promise<DebugProtocol.NextResponse> {
const response = await this.send('next', args);
this.fireSimulatedContinuedEvent(args.threadId);
return response;
}
stepIn(args: DebugProtocol.StepInArguments): Promise<DebugProtocol.StepInResponse> {
return this.send('stepIn', args).then(response => {
this.fireSimulatedContinuedEvent(args.threadId);
return response;
});
async stepIn(args: DebugProtocol.StepInArguments): Promise<DebugProtocol.StepInResponse> {
const response = await this.send('stepIn', args);
this.fireSimulatedContinuedEvent(args.threadId);
return response;
}
stepOut(args: DebugProtocol.StepOutArguments): Promise<DebugProtocol.StepOutResponse> {
return this.send('stepOut', args).then(response => {
this.fireSimulatedContinuedEvent(args.threadId);
return response;
});
async stepOut(args: DebugProtocol.StepOutArguments): Promise<DebugProtocol.StepOutResponse> {
const response = await this.send('stepOut', args);
this.fireSimulatedContinuedEvent(args.threadId);
return response;
}
continue(args: DebugProtocol.ContinueArguments): Promise<DebugProtocol.ContinueResponse> {
return this.send<DebugProtocol.ContinueResponse>('continue', args).then(response => {
if (response && response.body && response.body.allThreadsContinued !== undefined) {
this.allThreadsContinued = response.body.allThreadsContinued;
}
this.fireSimulatedContinuedEvent(args.threadId, this.allThreadsContinued);
return response;
});
async continue(args: DebugProtocol.ContinueArguments): Promise<DebugProtocol.ContinueResponse> {
const response = await this.send<DebugProtocol.ContinueResponse>('continue', args);
if (response && response.body && response.body.allThreadsContinued !== undefined) {
this.allThreadsContinued = response.body.allThreadsContinued;
}
this.fireSimulatedContinuedEvent(args.threadId, this.allThreadsContinued);
return response;
}
pause(args: DebugProtocol.PauseArguments): Promise<DebugProtocol.PauseResponse> {
@@ -335,12 +329,11 @@ export class RawDebugSession implements IDisposable {
return Promise.reject(new Error('setVariable not supported'));
}
restartFrame(args: DebugProtocol.RestartFrameArguments, threadId: number): Promise<DebugProtocol.RestartFrameResponse> {
async restartFrame(args: DebugProtocol.RestartFrameArguments, threadId: number): Promise<DebugProtocol.RestartFrameResponse> {
if (this.capabilities.supportsRestartFrame) {
return this.send('restartFrame', args).then(response => {
this.fireSimulatedContinuedEvent(threadId);
return response;
});
const response = await this.send('restartFrame', args);
this.fireSimulatedContinuedEvent(threadId);
return response;
}
return Promise.reject(new Error('restartFrame not supported'));
}
@@ -433,26 +426,24 @@ export class RawDebugSession implements IDisposable {
return this.send<DebugProtocol.EvaluateResponse>('evaluate', args);
}
stepBack(args: DebugProtocol.StepBackArguments): Promise<DebugProtocol.StepBackResponse> {
async stepBack(args: DebugProtocol.StepBackArguments): Promise<DebugProtocol.StepBackResponse> {
if (this.capabilities.supportsStepBack) {
return this.send('stepBack', args).then(response => {
if (response.body === undefined) { // TODO@AW why this check?
this.fireSimulatedContinuedEvent(args.threadId);
}
return response;
});
const response = await this.send('stepBack', args);
if (response.body === undefined) { // TODO@AW why this check?
this.fireSimulatedContinuedEvent(args.threadId);
}
return response;
}
return Promise.reject(new Error('stepBack not supported'));
}
reverseContinue(args: DebugProtocol.ReverseContinueArguments): Promise<DebugProtocol.ReverseContinueResponse> {
async reverseContinue(args: DebugProtocol.ReverseContinueArguments): Promise<DebugProtocol.ReverseContinueResponse> {
if (this.capabilities.supportsStepBack) {
return this.send('reverseContinue', args).then(response => {
if (response.body === undefined) { // TODO@AW why this check?
this.fireSimulatedContinuedEvent(args.threadId);
}
return response;
});
const response = await this.send('reverseContinue', args);
if (response.body === undefined) { // TODO@AW why this check?
this.fireSimulatedContinuedEvent(args.threadId);
}
return response;
}
return Promise.reject(new Error('reverseContinue not supported'));
}
@@ -464,13 +455,13 @@ export class RawDebugSession implements IDisposable {
return Promise.reject(new Error('gotoTargets is not supported'));
}
goto(args: DebugProtocol.GotoArguments): Promise<DebugProtocol.GotoResponse> {
async goto(args: DebugProtocol.GotoArguments): Promise<DebugProtocol.GotoResponse> {
if (this.capabilities.supportsGotoTargetsRequest) {
return this.send('goto', args).then(res => {
this.fireSimulatedContinuedEvent(args.threadId);
return res;
});
const response = await this.send('goto', args);
this.fireSimulatedContinuedEvent(args.threadId);
return response;
}
return Promise.reject(new Error('goto is not supported'));
}
@@ -484,36 +475,32 @@ export class RawDebugSession implements IDisposable {
//---- private
private shutdown(error?: Error, restart = false): Promise<any> {
private async shutdown(error?: Error, restart = false): Promise<any> {
if (!this.inShutdown) {
this.inShutdown = true;
if (this.debugAdapter) {
return this.send('disconnect', { restart }, undefined, 500).then(() => {
try {
await this.send('disconnect', { restart }, undefined, 500);
} finally {
this.stopAdapter(error);
}, () => {
// ignore error
this.stopAdapter(error);
});
}
} else {
return this.stopAdapter(error);
}
return this.stopAdapter(error);
}
return Promise.resolve(undefined);
}
private stopAdapter(error?: Error): Promise<any> {
if (this.debugAdapter) {
const da = this.debugAdapter;
this.debugAdapter = null;
return da.stopSession().then(_ => {
private async stopAdapter(error?: Error): Promise<any> {
try {
if (this.debugAdapter) {
const da = this.debugAdapter;
this.debugAdapter = null;
await da.stopSession();
this.debugAdapterStopped = true;
this.fireAdapterExitEvent(error);
}, err => {
this.fireAdapterExitEvent(error);
});
} else {
}
} finally {
this.fireAdapterExitEvent(error);
}
return Promise.resolve(undefined);
}
private fireAdapterExitEvent(error?: Error): void {
@@ -557,18 +544,19 @@ export class RawDebugSession implements IDisposable {
});
break;
case 'runInTerminal':
dbgr.runInTerminal(request.arguments as DebugProtocol.RunInTerminalRequestArguments).then(shellProcessId => {
try {
const shellProcessId = await dbgr.runInTerminal(request.arguments as DebugProtocol.RunInTerminalRequestArguments);
const resp = response as DebugProtocol.RunInTerminalResponse;
resp.body = {};
if (typeof shellProcessId === 'number') {
resp.body.shellProcessId = shellProcessId;
}
safeSendResponse(resp);
}, err => {
} catch (err) {
response.success = false;
response.message = err.message;
safeSendResponse(response);
});
}
break;
default:
response.success = false;
@@ -580,9 +568,7 @@ export class RawDebugSession implements IDisposable {
private launchVsCode(vscodeArgs: ILaunchVSCodeArguments): Promise<void> {
let args: ParsedArgs = {
_: []
};
const args: string[] = [];
for (let arg of vscodeArgs.args) {
if (arg.prefix) {
@@ -594,32 +580,11 @@ export class RawDebugSession implements IDisposable {
if ((key === 'file-uri' || key === 'folder-uri') && !isUri(arg.path)) {
value = URI.file(value).toString();
const v = args[key];
if (v) {
v.push(value);
} else {
args[key] = [value];
}
} else if (key === 'extensionDevelopmentPath' || key === 'enable-proposed-api') {
const v = args[key];
if (v) {
v.push(value);
} else {
args[key] = [value];
}
} else {
(<any>args)[key] = value;
}
args.push(`--${key}=${value}`);
} else {
const match = /^--(.+)$/.exec(a2);
if (match && match.length === 2) {
const key = match[1];
(<any>args)[key] = true;
} else {
args._.push(a2);
}
args.push(a2);
}
}
}

View File

@@ -46,7 +46,7 @@ import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
import { IAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget';
import { Variable } from 'vs/workbench/contrib/debug/common/debugModel';
import { SimpleReplElement, RawObjectReplElement, ReplEvaluationInput, ReplEvaluationResult } from 'vs/workbench/contrib/debug/common/replModel';
import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
import { CachedListVirtualDelegate } from 'vs/base/browser/ui/list/list';
import { ITreeRenderer, ITreeNode, ITreeContextMenuEvent, IAsyncDataSource } from 'vs/base/browser/ui/tree/tree';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { renderExpressionValue, AbstractExpressionsRenderer, IExpressionTemplateData, renderVariable, IInputBoxOptions } from 'vs/workbench/contrib/debug/browser/baseDebugView';
@@ -74,8 +74,8 @@ interface IPrivateReplService {
_serviceBrand: undefined;
acceptReplInput(): void;
getVisibleContent(): string;
selectSession(session?: IDebugSession): void;
clearRepl(): void;
selectSession(session?: IDebugSession): Promise<void>;
clearRepl(): Promise<void>;
focusRepl(): void;
}
@@ -129,7 +129,7 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati
}
private registerListeners(): void {
this._register(this.debugService.getViewModel().onDidFocusSession(session => {
this._register(this.debugService.getViewModel().onDidFocusSession(async session => {
if (session) {
sessionsToIgnore.delete(session);
if (this.completionItemProvider) {
@@ -159,13 +159,13 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati
}
}
this.selectSession();
await this.selectSession();
}));
this._register(this.debugService.onWillNewSession(newSession => {
this._register(this.debugService.onWillNewSession(async newSession => {
// Need to listen to output events for sessions which are not yet fully initialised
const input = this.tree.getInput();
if (!input || input.state === State.Inactive) {
this.selectSession(newSession);
await this.selectSession(newSession);
}
this.updateTitleArea();
}));
@@ -252,7 +252,7 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati
}
}
selectSession(session?: IDebugSession): void {
async selectSession(session?: IDebugSession): Promise<void> {
const treeInput = this.tree.getInput();
if (!session) {
const focusedSession = this.debugService.getViewModel().focusedSession;
@@ -272,7 +272,8 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati
});
if (this.tree && treeInput !== session) {
this.tree.setInput(session).then(() => revealLastElement(this.tree)).then(undefined, errors.onUnexpectedError);
await this.tree.setInput(session);
revealLastElement(this.tree);
}
}
@@ -280,14 +281,14 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati
this.updateInputDecoration();
}
clearRepl(): void {
async clearRepl(): Promise<void> {
const session = this.tree.getInput();
if (session) {
session.removeReplExpressions();
if (session.state === State.Inactive) {
// Ignore inactive sessions which got cleared - so they are not shown any more
sessionsToIgnore.add(session);
this.selectSession();
await this.selectSession();
this.updateTitleArea();
}
}
@@ -646,7 +647,7 @@ class ReplEvaluationResultsRenderer implements ITreeRenderer<ReplEvaluationResul
linkDetector: this.linkDetector
});
if (expression.hasChildren) {
templateData.annotation.className = 'annotation octicon octicon-info';
templateData.annotation.className = 'annotation codicon codicon-info';
templateData.annotation.title = nls.localize('stateCapture', "Object state is captured from first evaluation");
}
}
@@ -781,7 +782,7 @@ class ReplRawObjectsRenderer implements ITreeRenderer<RawObjectReplElement, Fuzz
// annotation if any
if (element.annotation) {
templateData.annotation.className = 'annotation octicon octicon-info';
templateData.annotation.className = 'annotation codicon codicon-info';
templateData.annotation.title = element.annotation;
} else {
templateData.annotation.className = '';
@@ -794,38 +795,33 @@ class ReplRawObjectsRenderer implements ITreeRenderer<RawObjectReplElement, Fuzz
}
}
class ReplDelegate implements IListVirtualDelegate<IReplElement> {
class ReplDelegate extends CachedListVirtualDelegate<IReplElement> {
constructor(private configurationService: IConfigurationService) { }
constructor(private configurationService: IConfigurationService) {
super();
}
getHeight(element: IReplElement): number {
const countNumberOfLines = (str: string) => Math.max(1, (str && str.match(/\r\n|\n/g) || []).length);
// Give approximate heights. Repl has dynamic height so the tree will measure the actual height on its own.
const config = this.configurationService.getValue<IDebugConfiguration>('debug');
const fontSize = config.console.fontSize;
const rowHeight = Math.ceil(1.4 * fontSize);
const wordWrap = config.console.wordWrap;
if (!wordWrap) {
return rowHeight;
if (!config.console.wordWrap) {
return Math.ceil(1.4 * config.console.fontSize);
}
// In order to keep scroll position we need to give a good approximation to the tree
// For every 150 characters increase the number of lines needed
if (element instanceof ReplEvaluationResult) {
return super.getHeight(element);
}
protected estimateHeight(element: IReplElement): number {
const config = this.configurationService.getValue<IDebugConfiguration>('debug');
const rowHeight = Math.ceil(1.4 * config.console.fontSize);
const countNumberOfLines = (str: string) => Math.max(1, (str && str.match(/\r\n|\n/g) || []).length);
const hasValue = (e: any): e is { value: string } => typeof e.value === 'string';
// Calculate a rough overestimation for the height
// For every 30 characters increase the number of lines needed
if (hasValue(element)) {
let value = element.value;
if (element.hasChildren) {
return rowHeight;
}
let valueRows = value ? (countNumberOfLines(value) + Math.floor(value.length / 150)) : 0;
return rowHeight * valueRows;
}
if (element instanceof SimpleReplElement || element instanceof ReplEvaluationInput) {
let value = element.value;
let valueRows = countNumberOfLines(value) + Math.floor(value.length / 150);
let valueRows = countNumberOfLines(value) + Math.floor(value.length / 30);
return valueRows * rowHeight;
}
@@ -851,7 +847,7 @@ class ReplDelegate implements IListVirtualDelegate<IReplElement> {
return ReplRawObjectsRenderer.ID;
}
hasDynamicHeight?(element: IReplElement): boolean {
hasDynamicHeight(element: IReplElement): boolean {
// Empty elements should not have dynamic height since they will be invisible
return element.toString().length > 0;
}
@@ -919,7 +915,7 @@ class AcceptReplInputAction extends EditorAction {
}
run(accessor: ServicesAccessor, editor: ICodeEditor): void | Promise<void> {
SuggestController.get(editor).acceptSelectedSuggestion();
SuggestController.get(editor).acceptSelectedSuggestion(false, true);
accessor.get(IPrivateReplService).acceptReplInput();
}
}
@@ -941,7 +937,7 @@ class FilterReplAction extends EditorAction {
}
run(accessor: ServicesAccessor, editor: ICodeEditor): void | Promise<void> {
SuggestController.get(editor).acceptSelectedSuggestion();
SuggestController.get(editor).acceptSelectedSuggestion(false, true);
accessor.get(IPrivateReplService).focusRepl();
}
}
@@ -984,7 +980,7 @@ class SelectReplActionViewItem extends FocusSessionActionViewItem {
class SelectReplAction extends Action {
static readonly ID = 'workbench.action.debug.selectRepl';
static LABEL = nls.localize('selectRepl', "Select Debug Console");
static readonly LABEL = nls.localize('selectRepl', "Select Debug Console");
constructor(id: string, label: string,
@IDebugService private readonly debugService: IDebugService,
@@ -993,12 +989,12 @@ class SelectReplAction extends Action {
super(id, label);
}
run(session: IDebugSession): Promise<any> {
async run(session: IDebugSession): Promise<any> {
// If session is already the focused session we need to manualy update the tree since view model will not send a focused change event
if (session && session.state !== State.Inactive && session !== this.debugService.getViewModel().focusedSession) {
this.debugService.focusStackFrame(undefined, undefined, session, true);
await this.debugService.focusStackFrame(undefined, undefined, session, true);
} else {
this.replService.selectSession(session);
await this.replService.selectSession(session);
}
return Promise.resolve(undefined);
@@ -1007,7 +1003,7 @@ class SelectReplAction extends Action {
export class ClearReplAction extends Action {
static readonly ID = 'workbench.debug.panel.action.clearReplAction';
static LABEL = nls.localize('clearRepl', "Clear Console");
static readonly LABEL = nls.localize('clearRepl', "Clear Console");
constructor(id: string, label: string,
@IPanelService private readonly panelService: IPanelService
@@ -1015,11 +1011,9 @@ export class ClearReplAction extends Action {
super(id, label, 'debug-action codicon-clear-all');
}
run(): Promise<any> {
async run(): Promise<any> {
const repl = <Repl>this.panelService.openPanel(REPL_ID);
repl.clearRepl();
await repl.clearRepl();
aria.status(nls.localize('debugConsoleCleared', "Debug console was cleared"));
return Promise.resolve(undefined);
}
}

View File

@@ -12,6 +12,7 @@ import { IDebugService, State } from 'vs/workbench/contrib/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, createStyleSheet } from 'vs/base/browser/dom';
import { assertIsDefined } from 'vs/base/common/types';
// colors for theming
@@ -56,7 +57,7 @@ export class StatusBarColorProvider extends Themable implements IWorkbenchContri
protected updateStyles(): void {
super.updateStyles();
const container = this.layoutService.getContainer(Parts.STATUSBAR_PART);
const container = assertIsDefined(this.layoutService.getContainer(Parts.STATUSBAR_PART));
if (isStatusbarInDebugMode(this.debugService)) {
addClass(container, 'debugging');
} else {
@@ -65,7 +66,7 @@ export class StatusBarColorProvider extends Themable implements IWorkbenchContri
// Container Colors
const backgroundColor = this.getColor(this.getColorKey(STATUS_BAR_NO_FOLDER_BACKGROUND, STATUS_BAR_DEBUGGING_BACKGROUND, STATUS_BAR_BACKGROUND));
container.style.backgroundColor = backgroundColor;
container.style.backgroundColor = backgroundColor || '';
container.style.color = this.getColor(this.getColorKey(STATUS_BAR_NO_FOLDER_FOREGROUND, STATUS_BAR_DEBUGGING_FOREGROUND, STATUS_BAR_FOREGROUND));
// Border Color
@@ -108,7 +109,7 @@ export function isStatusbarInDebugMode(debugService: IDebugService): boolean {
}
const session = debugService.getViewModel().focusedSession;
const isRunningWithoutDebug = session && session.configuration && session.configuration.noDebug;
const isRunningWithoutDebug = session?.configuration?.noDebug;
if (isRunningWithoutDebug) {
return false;
}

View File

@@ -25,7 +25,6 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
import { Emitter } from 'vs/base/common/event';
import { WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService';
import { IAsyncDataTreeViewState } from 'vs/base/browser/ui/tree/asyncDataTree';
import { onUnexpectedError } from 'vs/base/common/errors';
import { FuzzyScore, createMatches } from 'vs/base/common/filters';
import { HighlightedLabel, IHighlight } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
@@ -57,28 +56,27 @@ export class VariablesView extends ViewletPanel {
super({ ...(options as IViewletPanelOptions), ariaHeaderLabel: nls.localize('variablesSection', "Variables Section") }, keybindingService, contextMenuService, configurationService, contextKeyService);
// Use scheduler to prevent unnecessary flashing
this.onFocusStackFrameScheduler = new RunOnceScheduler(() => {
this.onFocusStackFrameScheduler = new RunOnceScheduler(async () => {
const stackFrame = this.debugService.getViewModel().focusedStackFrame;
this.needsRefresh = false;
if (stackFrame && this.savedViewState) {
this.tree.setInput(this.debugService.getViewModel(), this.savedViewState).then(null, onUnexpectedError);
await this.tree.setInput(this.debugService.getViewModel(), this.savedViewState);
this.savedViewState = undefined;
} else {
if (!stackFrame) {
// We have no stackFrame, save tree state before it is cleared
this.savedViewState = this.tree.getViewState();
}
this.tree.updateChildren().then(() => {
if (stackFrame) {
stackFrame.getScopes().then(scopes => {
// Expand the first scope if it is not expensive and if there is no expansion state (all are collapsed)
if (scopes.every(s => this.tree.getNode(s).collapsed) && scopes.length > 0 && !scopes[0].expensive) {
this.tree.expand(scopes[0]).then(undefined, onUnexpectedError);
}
});
await this.tree.updateChildren();
if (stackFrame) {
const scopes = await stackFrame.getScopes();
// Expand the first scope if it is not expensive and if there is no expansion state (all are collapsed)
if (scopes.every(s => this.tree.getNode(s).collapsed) && scopes.length > 0 && !scopes[0].expensive) {
this.tree.expand(scopes[0]);
}
}, onUnexpectedError);
}
}
}, 400);
}
@@ -96,12 +94,14 @@ export class VariablesView extends ViewletPanel {
keyboardNavigationLabelProvider: { getKeyboardNavigationLabel: (e: IExpression | IScope) => e }
});
this.tree.setInput(this.debugService.getViewModel()).then(null, onUnexpectedError);
this.tree.setInput(this.debugService.getViewModel());
CONTEXT_VARIABLES_FOCUSED.bindTo(this.tree.contextKeyService);
const collapseAction = new CollapseAction(this.tree, true, 'explorer-action collapse-explorer');
this.toolbar.setActions([collapseAction])();
if (this.toolbar) {
const collapseAction = new CollapseAction(this.tree, true, 'explorer-action collapse-explorer');
this.toolbar.setActions([collapseAction])();
}
this.tree.updateChildren();
this._register(this.debugService.getViewModel().onDidFocusStackFrame(sf => {

View File

@@ -24,7 +24,6 @@ import { IAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget';
import { WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService';
import { IAsyncDataSource, ITreeMouseEvent, ITreeContextMenuEvent, ITreeDragAndDrop, ITreeDragOverReaction } from 'vs/base/browser/ui/tree/tree';
import { IDragAndDropData } from 'vs/base/browser/dnd';
import { onUnexpectedError } from 'vs/base/common/errors';
import { ElementsDragAndDropData } from 'vs/base/browser/ui/list/listView';
import { FuzzyScore } from 'vs/base/common/filters';
import { IHighlight } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel';
@@ -71,13 +70,15 @@ export class WatchExpressionsView extends ViewletPanel {
dnd: new WatchExpressionsDragAndDrop(this.debugService),
});
this.tree.setInput(this.debugService).then(undefined, onUnexpectedError);
this.tree.setInput(this.debugService);
CONTEXT_WATCH_EXPRESSIONS_FOCUSED.bindTo(this.tree.contextKeyService);
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([addWatchExpressionAction, collapseAction, removeAllWatchExpressionsAction])();
if (this.toolbar) {
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([addWatchExpressionAction, collapseAction, removeAllWatchExpressionsAction])();
}
this._register(this.tree.onContextMenu(e => this.onContextMenu(e)));
this._register(this.tree.onMouseDblClick(e => this.onMouseDblClick(e)));
@@ -152,7 +153,7 @@ export class WatchExpressionsView extends ViewletPanel {
return Promise.resolve();
}));
if (!expression.hasChildren) {
actions.push(this.instantiationService.createInstance(CopyValueAction, CopyValueAction.ID, CopyValueAction.LABEL, expression.value, 'watch', this.debugService));
actions.push(this.instantiationService.createInstance(CopyValueAction, CopyValueAction.ID, CopyValueAction.LABEL, expression.value, 'watch'));
}
actions.push(new Separator());
@@ -166,7 +167,7 @@ export class WatchExpressionsView extends ViewletPanel {
if (element instanceof Variable) {
const variable = element as Variable;
if (!variable.hasChildren) {
actions.push(this.instantiationService.createInstance(CopyValueAction, CopyValueAction.ID, CopyValueAction.LABEL, variable, 'watch', this.debugService));
actions.push(this.instantiationService.createInstance(CopyValueAction, CopyValueAction.ID, CopyValueAction.LABEL, variable, 'watch'));
}
actions.push(new Separator());
}

View File

@@ -160,7 +160,7 @@ export interface IDebugSession extends ITreeElement {
readonly configuration: IConfig;
readonly unresolvedConfiguration: IConfig | undefined;
readonly state: State;
readonly root: IWorkspaceFolder;
readonly root: IWorkspaceFolder | undefined;
readonly parentSession: IDebugSession | undefined;
readonly subId: string | undefined;
@@ -467,6 +467,7 @@ export interface IDebugConfiguration {
};
focusWindowOnBreak: boolean;
onTaskErrors: 'debugAnyway' | 'showErrors' | 'prompt';
showBreakpointsInOverviewRuler: boolean;
}
export interface IGlobalConfig {
@@ -732,7 +733,7 @@ export interface IDebugService {
/**
* Sets the focused stack frame and evaluates all expressions against the newly focused stack frame,
*/
focusStackFrame(focusedStackFrame: IStackFrame | undefined, thread?: IThread, session?: IDebugSession, explicit?: boolean): void;
focusStackFrame(focusedStackFrame: IStackFrame | undefined, thread?: IThread, session?: IDebugSession, explicit?: boolean): Promise<void>;
/**
* Adds new breakpoints to the model for the file specified with the uri. Notifies debug adapter of breakpoint changes.

View File

@@ -27,7 +27,7 @@ import { mixin } from 'vs/base/common/objects';
export class ExpressionContainer implements IExpressionContainer {
public static allValues = new Map<string, string>();
public static readonly allValues = new Map<string, string>();
// Use chunks to support variable paging #9537
private static readonly BASE_CHUNK_SIZE = 100;
@@ -65,7 +65,7 @@ export class ExpressionContainer implements IExpressionContainer {
private async doGetChildren(): Promise<IExpression[]> {
if (!this.hasChildren) {
return Promise.resolve([]);
return [];
}
if (!this.getChildrenInChunks) {
@@ -114,13 +114,16 @@ export class ExpressionContainer implements IExpressionContainer {
return !!this.reference && this.reference > 0;
}
private fetchVariables(start: number | undefined, count: number | undefined, filter: 'indexed' | 'named' | undefined): Promise<Variable[]> {
return this.session!.variables(this.reference || 0, this.threadId, filter, start, count).then(response => {
private async fetchVariables(start: number | undefined, count: number | undefined, filter: 'indexed' | 'named' | undefined): Promise<Variable[]> {
try {
const response = await this.session!.variables(this.reference || 0, this.threadId, filter, start, count);
return response && response.body && response.body.variables
? distinct(response.body.variables.filter(v => !!v && isString(v.name)), (v: DebugProtocol.Variable) => v.name).map((v: DebugProtocol.Variable) =>
new Variable(this.session, this.threadId, this, v.variablesReference, v.name, v.evaluateName, v.value, v.namedVariables, v.indexedVariables, v.presentationHint, v.type))
: [];
}, (e: Error) => [new Variable(this.session, this.threadId, this, 0, e.message, e.message, '', 0, 0, { kind: 'virtual' }, undefined, false)]);
} catch (e) {
return [new Variable(this.session, this.threadId, this, 0, e.message, e.message, '', 0, 0, { kind: 'virtual' }, undefined, false)];
}
}
// The adapter explicitly sents the children count of an expression only if there are lots of children which should be chunked.
@@ -172,7 +175,7 @@ export class ExpressionContainer implements IExpressionContainer {
}
export class Expression extends ExpressionContainer implements IExpression {
static DEFAULT_VALUE = nls.localize('notAvailable', "not available");
static readonly DEFAULT_VALUE = nls.localize('notAvailable', "not available");
public available: boolean;
@@ -221,7 +224,7 @@ export class Variable extends ExpressionContainer implements IExpression {
async setVariable(value: string): Promise<any> {
if (!this.session) {
return Promise.resolve(undefined);
return;
}
try {
@@ -313,18 +316,17 @@ export class StackFrame implements IStackFrame {
return (from > 0 ? '...' : '') + this.source.uri.path.substr(from);
}
getMostSpecificScopes(range: IRange): Promise<IScope[]> {
return this.getScopes().then(scopes => {
scopes = scopes.filter(s => !s.expensive);
const haveRangeInfo = scopes.some(s => !!s.range);
if (!haveRangeInfo) {
return scopes;
}
async getMostSpecificScopes(range: IRange): Promise<IScope[]> {
const scopes = await this.getScopes();
const nonExpensiveScopes = scopes.filter(s => !s.expensive);
const haveRangeInfo = nonExpensiveScopes.some(s => !!s.range);
if (!haveRangeInfo) {
return nonExpensiveScopes;
}
const scopesContainingRange = scopes.filter(scope => scope.range && Range.containsRange(scope.range, range))
.sort((first, second) => (first.range!.endLineNumber - first.range!.startLineNumber) - (second.range!.endLineNumber - second.range!.startLineNumber));
return scopesContainingRange.length ? scopesContainingRange : scopes;
});
const scopesContainingRange = nonExpensiveScopes.filter(scope => scope.range && Range.containsRange(scope.range, range))
.sort((first, second) => (first.range!.endLineNumber - first.range!.startLineNumber) - (second.range!.endLineNumber - second.range!.startLineNumber));
return scopesContainingRange.length ? scopesContainingRange : nonExpensiveScopes;
}
restart(): Promise<void> {
@@ -342,9 +344,11 @@ export class StackFrame implements IStackFrame {
return sourceToString === UNKNOWN_SOURCE_LABEL ? this.name : `${this.name} (${sourceToString})`;
}
openInEditor(editorService: IEditorService, preserveFocus?: boolean, sideBySide?: boolean, pinned?: boolean): Promise<ITextEditor | undefined> {
return !this.source.available ? Promise.resolve(undefined) :
this.source.openInEditor(editorService, this.range, preserveFocus, sideBySide, pinned);
async openInEditor(editorService: IEditorService, preserveFocus?: boolean, sideBySide?: boolean, pinned?: boolean): Promise<ITextEditor | undefined> {
if (this.source.available) {
return this.source.openInEditor(editorService, this.range, preserveFocus, sideBySide, pinned);
}
return undefined;
}
equals(other: IStackFrame): boolean {
@@ -399,23 +403,21 @@ export class Thread implements IThread {
* Only fetches the first stack frame for performance reasons. Calling this method consecutive times
* gets the remainder of the call stack.
*/
fetchCallStack(levels = 20): Promise<void> {
if (!this.stopped) {
return Promise.resolve(undefined);
}
const start = this.callStack.length;
return this.getCallStackImpl(start, levels).then(callStack => {
async fetchCallStack(levels = 20): Promise<void> {
if (this.stopped) {
const start = this.callStack.length;
const callStack = await this.getCallStackImpl(start, levels);
if (start < this.callStack.length) {
// Set the stack frames for exact position we requested. To make sure no concurrent requests create duplicate stack frames #30660
this.callStack.splice(start, this.callStack.length - start);
}
this.callStack = this.callStack.concat(callStack || []);
});
}
}
private getCallStackImpl(startFrame: number, levels: number): Promise<IStackFrame[]> {
return this.session.stackTrace(this.threadId, startFrame, levels).then(response => {
private async getCallStackImpl(startFrame: number, levels: number): Promise<IStackFrame[]> {
try {
const response = await this.session.stackTrace(this.threadId, startFrame, levels);
if (!response || !response.body) {
return [];
}
@@ -434,13 +436,13 @@ export class Thread implements IThread {
rsf.endColumn || rsf.column
), startFrame + index);
});
}, (err: Error) => {
} catch (err) {
if (this.stoppedDetails) {
this.stoppedDetails.framesErrorMessage = err.message;
}
return [];
});
}
}
/**
@@ -617,8 +619,7 @@ export class Breakpoint extends BaseBreakpoint implements IBreakpoint {
}
get column(): number | undefined {
// Only respect the column if the user explictly set the column to have an inline breakpoint
return this.verified && this.data && typeof this.data.column === 'number' && typeof this._column === 'number' ? this.data.column : this._column;
return this.verified && this.data && typeof this.data.column === 'number' ? this.data.column : this._column;
}
get message(): string | undefined {

View File

@@ -4,32 +4,13 @@
*--------------------------------------------------------------------------------------------*/
import { equalsIgnoreCase } from 'vs/base/common/strings';
import { IConfig, IDebuggerContribution, IDebugService, IDebugSession } from 'vs/workbench/contrib/debug/common/debug';
import { IConfig, IDebuggerContribution, IDebugSession } from 'vs/workbench/contrib/debug/common/debug';
import { URI as uri } from 'vs/base/common/uri';
import { isAbsolute } from 'vs/base/common/path';
import { deepClone } from 'vs/base/common/objects';
import { IHistoryService } from 'vs/workbench/services/history/common/history';
import { first } from 'vs/base/common/arrays';
const _formatPIIRegexp = /{([^}]+)}/g;
export function startDebugging(debugService: IDebugService, historyService: IHistoryService, noDebug: boolean, ): Promise<boolean> {
const configurationManager = debugService.getConfigurationManager();
let launch = configurationManager.selectedConfiguration.launch;
if (!launch || launch.getConfigurationNames().length === 0) {
const rootUri = historyService.getLastActiveWorkspaceRoot();
launch = configurationManager.getLaunch(rootUri);
if (!launch || launch.getConfigurationNames().length === 0) {
const launches = configurationManager.getLaunches();
launch = first(launches, l => !!(l && l.getConfigurationNames().length), launch);
}
configurationManager.selectConfiguration(launch);
}
return debugService.startDebugging(launch, undefined, { noDebug });
}
export function formatPII(value: string, excludePII: boolean, args: { [key: string]: string }): string {
return value.replace(_formatPIIRegexp, function (match, group) {
if (excludePII && group.length > 0 && group[0] !== '_') {
@@ -196,6 +177,9 @@ function convertPaths(msg: DebugProtocol.ProtocolMessage, fixSourcePath: (toDA:
case 'setBreakpoints':
fixSourcePath(true, (<DebugProtocol.SetBreakpointsArguments>request.arguments).source);
break;
case 'breakpointLocations':
fixSourcePath(true, (<DebugProtocol.BreakpointLocationsArguments>request.arguments).source);
break;
case 'source':
fixSourcePath(true, (<DebugProtocol.SourceArguments>request.arguments).source);
break;

View File

@@ -8,7 +8,6 @@ import { IExtensionHostDebugService } from 'vs/platform/debug/common/extensionHo
import { IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService';
import { ExtensionHostDebugChannelClient, ExtensionHostDebugBroadcastChannel } from 'vs/platform/debug/common/extensionHostDebugIpc';
import { IProcessEnvironment } from 'vs/base/common/platform';
import { ParsedArgs } from 'vs/platform/environment/common/environment';
import { IElectronService } from 'vs/platform/electron/node/electron';
export class ExtensionHostDebugService extends ExtensionHostDebugChannelClient {
@@ -20,7 +19,7 @@ export class ExtensionHostDebugService extends ExtensionHostDebugChannelClient {
super(mainProcessService.getChannel(ExtensionHostDebugBroadcastChannel.ChannelName));
}
openExtensionDevelopmentHostWindow(args: ParsedArgs, env: IProcessEnvironment): Promise<void> {
openExtensionDevelopmentHostWindow(args: string[], env: IProcessEnvironment): Promise<void> {
// TODO@Isidor move into debug IPC channel (https://github.com/microsoft/vscode/issues/81060)
return this.electronService.openExtensionDevelopmentHostWindow(args, env);
}

View File

@@ -133,7 +133,6 @@ export class SocketDebugAdapter extends StreamDebugAdapter {
this.socket.end();
this.socket = undefined;
}
return Promise.resolve(undefined);
}
}
@@ -178,7 +177,6 @@ export class ExecutableDebugAdapter extends StreamDebugAdapter {
if (options.env) {
env = objects.mixin(env, options.env);
}
delete env.VSCODE_PREVENT_FOREIGN_INSPECT;
if (command === 'node') {
if (Array.isArray(args) && args.length > 0) {

View File

@@ -148,7 +148,7 @@ export function prepareCommand(args: DebugProtocol.RunInTerminalRequestArguments
if (value === null) {
command += `set "${key}=" && `;
} else {
value = value.replace(/[\^\&]/g, s => `^${s}`);
value = value.replace(/[\^\&\|\<\>]/g, s => `^${s}`);
command += `set "${key}=${value}" && `;
}
}

View File

@@ -30,7 +30,7 @@ suite('Debug - ANSI Handling', () => {
*/
setup(() => {
model = new DebugModel([], [], [], [], [], <any>{ isDirty: (e: any) => false });
session = new DebugSession({ resolved: { name, type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, undefined, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, NullOpenerService);
session = new DebugSession({ resolved: { name, type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, undefined, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, NullOpenerService);
const instantiationService: TestInstantiationService = <TestInstantiationService>workbenchInstantiationService();
linkDetector = instantiationService.createInstance(LinkDetector);

View File

@@ -16,7 +16,7 @@ import { IBreakpointUpdateData, IDebugSessionOptions } from 'vs/workbench/contri
import { NullOpenerService } from 'vs/platform/opener/common/opener';
function createMockSession(model: DebugModel, name = 'mockSession', options?: IDebugSessionOptions): DebugSession {
return new DebugSession({ resolved: { name, type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, options, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, NullOpenerService);
return new DebugSession({ resolved: { name, type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, options, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, NullOpenerService);
}
suite('Debug - Model', () => {

View File

@@ -40,7 +40,8 @@ export class MockDebugService implements IDebugService {
throw new Error('not implemented');
}
public focusStackFrame(focusedStackFrame: IStackFrame): void {
public focusStackFrame(focusedStackFrame: IStackFrame): Promise<void> {
throw new Error('not implemented');
}
sendAllBreakpoints(session?: IDebugSession): Promise<any> {