提交 e4d4b43f 编写于 作者: D Dmitry Gozman

Allow debug session to share repl with its parent; fixes #62419

Introduced DebugSessionOptions passed to debug.startDebugging which
encapsulated DebugConsoleMode to control this behavior.
上级 bbb56dba
......@@ -616,6 +616,56 @@ declare module 'vscode' {
debugAdapterExecutable?(folder: WorkspaceFolder | undefined, token?: CancellationToken): ProviderResult<DebugAdapterExecutable>;
}
/**
* Debug console mode used by debug session, see [options](#DebugSessionOptions).
*/
export enum DebugConsoleMode {
/**
* Debug session should have a separate debug console.
*/
Separate = 0,
/**
* Debug session should share debug console with its parent session.
* This value has no effect for sessions which do not have a parent session.
*/
MergeWithParent = 1
}
/**
* Options for [starting a debug session](#debug.startDebugging).
*/
export interface DebugSessionOptions {
/**
* When specified the newly created debug session is registered as a "child" session of this
* "parent" debug session.
*/
parentSession?: DebugSession;
/**
* Controls whether this session should have a separate debug console or share it
* with the parent session. Has no effect for sessions which do not have a parent session.
* Defaults to Separate.
*/
consoleMode?: DebugConsoleMode;
}
export namespace debug {
/**
* Start debugging by using either a named launch or named compound configuration,
* or by directly passing a [DebugConfiguration](#DebugConfiguration).
* The named configurations are looked up in '.vscode/launch.json' found in the given folder.
* Before debugging starts, all unsaved files are saved and the launch configurations are brought up-to-date.
* Folder specific variables used in the configuration (e.g. '${workspaceFolder}') are resolved against the given folder.
* @param folder The [workspace folder](#WorkspaceFolder) for looking up named configurations and resolving variables or `undefined` for a non-folder setup.
* @param nameOrConfiguration Either the name of a debug or compound configuration or a [DebugConfiguration](#DebugConfiguration) object.
* @param parentSessionOrOptions Debug sesison options. When passed a parent [debug session](#DebugSession), assumes options with just this parent session.
* @return A thenable that resolves when debugging could be successfully started.
*/
export function startDebugging(folder: WorkspaceFolder | undefined, nameOrConfiguration: string | DebugConfiguration, parentSessionOrOptions?: DebugSession | DebugSessionOptions): Thenable<boolean>;
}
//#endregion
//#region Rob, Matt: logging
......
......@@ -5,10 +5,10 @@
import { DisposableStore } from 'vs/base/common/lifecycle';
import { URI as uri } from 'vs/base/common/uri';
import { IDebugService, IConfig, IDebugConfigurationProvider, IBreakpoint, IFunctionBreakpoint, IBreakpointData, IDebugAdapter, IDebugAdapterDescriptorFactory, IDebugSession, IDebugAdapterFactory, IDataBreakpoint } from 'vs/workbench/contrib/debug/common/debug';
import { IDebugService, IConfig, IDebugConfigurationProvider, IBreakpoint, IFunctionBreakpoint, IBreakpointData, IDebugAdapter, IDebugAdapterDescriptorFactory, IDebugSession, IDebugAdapterFactory, IDataBreakpoint, IDebugSessionOptions } from 'vs/workbench/contrib/debug/common/debug';
import {
ExtHostContext, ExtHostDebugServiceShape, MainThreadDebugServiceShape, DebugSessionUUID, MainContext,
IExtHostContext, IBreakpointsDeltaDto, ISourceMultiBreakpointDto, ISourceBreakpointDto, IFunctionBreakpointDto, IDebugSessionDto, IDataBreakpointDto
IExtHostContext, IBreakpointsDeltaDto, ISourceMultiBreakpointDto, ISourceBreakpointDto, IFunctionBreakpointDto, IDebugSessionDto, IDataBreakpointDto, IStartDebuggingOptions
} from 'vs/workbench/api/common/extHost.protocol';
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
import severity from 'vs/base/common/severity';
......@@ -218,10 +218,15 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb
return undefined;
}
public $startDebugging(_folderUri: uri | undefined, nameOrConfiguration: string | IConfig, parentSessionID: DebugSessionUUID | undefined): Promise<boolean> {
const folderUri = _folderUri ? uri.revive(_folderUri) : undefined;
public $startDebugging(options: IStartDebuggingOptions): Promise<boolean> {
const folderUri = options.folder ? uri.revive(options.folder) : undefined;
const launch = this.debugService.getConfigurationManager().getLaunch(folderUri);
return this.debugService.startDebugging(launch, nameOrConfiguration, false, this.getSession(parentSessionID)).then(success => {
const debugOptions: IDebugSessionOptions = {
noDebug: false,
parentSession: this.getSession(options.parentSessionID),
repl: options.repl
};
return this.debugService.startDebugging(launch, options.nameOrConfig, debugOptions).then(success => {
return success;
}, err => {
return Promise.reject(new Error(err && err.message ? err.message : 'cannot start debugging'));
......
......@@ -754,8 +754,12 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
registerDebugAdapterTrackerFactory(debugType: string, factory: vscode.DebugAdapterTrackerFactory) {
return extHostDebugService.registerDebugAdapterTrackerFactory(debugType, factory);
},
startDebugging(folder: vscode.WorkspaceFolder | undefined, nameOrConfig: string | vscode.DebugConfiguration, parentSession?: vscode.DebugSession) {
return extHostDebugService.startDebugging(folder, nameOrConfig, parentSession);
startDebugging(folder: vscode.WorkspaceFolder | undefined, nameOrConfig: string | vscode.DebugConfiguration, parentSessionOrOptions?: vscode.DebugSession | vscode.DebugSessionOptions) {
if (!parentSessionOrOptions || (typeof parentSessionOrOptions === 'object' && 'configuration' in parentSessionOrOptions)) {
return extHostDebugService.startDebugging(folder, nameOrConfig, { parentSession: parentSessionOrOptions });
}
checkProposedApiEnabled(extension);
return extHostDebugService.startDebugging(folder, nameOrConfig, parentSessionOrOptions || {});
},
addBreakpoints(breakpoints: vscode.Breakpoint[]) {
return extHostDebugService.addBreakpoints(breakpoints);
......@@ -900,6 +904,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
CallHierarchyOutgoingCall: extHostTypes.CallHierarchyOutgoingCall,
CallHierarchyIncomingCall: extHostTypes.CallHierarchyIncomingCall,
CallHierarchyItem: extHostTypes.CallHierarchyItem,
DebugConsoleMode: extHostTypes.DebugConsoleMode,
Decoration: extHostTypes.Decoration,
WebviewEditorState: extHostTypes.WebviewEditorState,
UIKind: UIKind
......
......@@ -39,7 +39,7 @@ import { ThemeColor } from 'vs/platform/theme/common/themeService';
import { EditorViewColumn } from 'vs/workbench/api/common/shared/editor';
import * as tasks from 'vs/workbench/api/common/shared/tasks';
import { IRevealOptions, ITreeItem } from 'vs/workbench/common/views';
import { IAdapterDescriptor, IConfig } from 'vs/workbench/contrib/debug/common/debug';
import { IAdapterDescriptor, IConfig, IDebugSessionReplMode } from 'vs/workbench/contrib/debug/common/debug';
import { ITextQueryBuilderOptions } from 'vs/workbench/contrib/search/common/queryBuilder';
import { ITerminalDimensions, IShellLaunchConfig } from 'vs/workbench/contrib/terminal/common/terminal';
import { ExtensionActivationError } from 'vs/workbench/services/extensions/common/extensions';
......@@ -712,6 +712,13 @@ export interface IDebugConfiguration {
[key: string]: any;
}
export interface IStartDebuggingOptions {
folder: UriComponents | undefined;
nameOrConfig: string | IDebugConfiguration;
parentSessionID?: DebugSessionUUID;
repl?: IDebugSessionReplMode;
}
export interface MainThreadDebugServiceShape extends IDisposable {
$registerDebugTypes(debugTypes: string[]): void;
$sessionCached(sessionID: string): void;
......@@ -722,7 +729,7 @@ export interface MainThreadDebugServiceShape extends IDisposable {
$registerDebugAdapterDescriptorFactory(type: string, handle: number): Promise<void>;
$unregisterDebugConfigurationProvider(handle: number): void;
$unregisterDebugAdapterDescriptorFactory(handle: number): void;
$startDebugging(folder: UriComponents | undefined, nameOrConfig: string | IDebugConfiguration, parentSessionID: string | undefined): Promise<boolean>;
$startDebugging(options: IStartDebuggingOptions): Promise<boolean>;
$setDebugSessionName(id: DebugSessionUUID, name: string): void;
$customDebugAdapterRequest(id: DebugSessionUUID, command: string, args: any): Promise<any>;
$appendDebugConsole(value: string): void;
......
......@@ -26,7 +26,7 @@ export interface IExtHostDebugService extends ExtHostDebugServiceShape {
addBreakpoints(breakpoints0: vscode.Breakpoint[]): Promise<void>;
removeBreakpoints(breakpoints0: vscode.Breakpoint[]): Promise<void>;
startDebugging(folder: vscode.WorkspaceFolder | undefined, nameOrConfig: string | vscode.DebugConfiguration, parentSession?: vscode.DebugSession): Promise<boolean>;
startDebugging(folder: vscode.WorkspaceFolder | undefined, nameOrConfig: string | vscode.DebugConfiguration, options: vscode.DebugSessionOptions): Promise<boolean>;
registerDebugConfigurationProvider(type: string, provider: vscode.DebugConfigurationProvider): vscode.Disposable;
registerDebugAdapterDescriptorFactory(extension: IExtensionDescription, type: string, factory: vscode.DebugAdapterDescriptorFactory): vscode.Disposable;
registerDebugAdapterTrackerFactory(type: string, factory: vscode.DebugAdapterTrackerFactory): vscode.Disposable;
......
......@@ -2352,6 +2352,22 @@ export enum CommentMode {
//#endregion
//#region debug
export enum DebugConsoleMode {
/**
* Debug session should have a separate debug console.
*/
Separate = 0,
/**
* Debug session should share debug console with its parent session.
* This value has no effect for sessions which do not have a parent session.
*/
MergeWithParent = 1
}
//#endregion
@es5ClassCompat
export class QuickInputButtons {
......
......@@ -14,7 +14,7 @@ import {
IBreakpointsDeltaDto, ISourceMultiBreakpointDto, IFunctionBreakpointDto, IDebugSessionDto
} from 'vs/workbench/api/common/extHost.protocol';
import * as vscode from 'vscode';
import { Disposable, Position, Location, SourceBreakpoint, FunctionBreakpoint, DebugAdapterServer, DebugAdapterExecutable, DataBreakpoint } from 'vs/workbench/api/common/extHostTypes';
import { Disposable, Position, Location, SourceBreakpoint, FunctionBreakpoint, DebugAdapterServer, DebugAdapterExecutable, DataBreakpoint, DebugConsoleMode } from 'vs/workbench/api/common/extHostTypes';
import { ExecutableDebugAdapter, SocketDebugAdapter } from 'vs/workbench/contrib/debug/node/debugAdapter';
import { AbstractDebugAdapter } from 'vs/workbench/contrib/debug/common/abstractDebugAdapter';
import { IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace';
......@@ -252,8 +252,13 @@ export class ExtHostDebugService implements IExtHostDebugService, ExtHostDebugSe
return this._debugServiceProxy.$unregisterBreakpoints(ids, fids, dids);
}
public startDebugging(folder: vscode.WorkspaceFolder | undefined, nameOrConfig: string | vscode.DebugConfiguration, parentSession?: vscode.DebugSession): Promise<boolean> {
return this._debugServiceProxy.$startDebugging(folder ? folder.uri : undefined, nameOrConfig, parentSession ? parentSession.id : undefined);
public startDebugging(folder: vscode.WorkspaceFolder | undefined, nameOrConfig: string | vscode.DebugConfiguration, options: vscode.DebugSessionOptions): Promise<boolean> {
return this._debugServiceProxy.$startDebugging({
folder: folder ? folder.uri : undefined,
nameOrConfig,
parentSessionID: options.parentSession ? options.parentSession.id : undefined,
repl: options.consoleMode === DebugConsoleMode.MergeWithParent ? 'mergeWithParent' : 'separate'
});
}
public registerDebugConfigurationProvider(type: string, provider: vscode.DebugConfigurationProvider): vscode.Disposable {
......
......@@ -202,7 +202,7 @@ export class FocusSessionActionViewItem extends SelectActionViewItem {
this._register(attachSelectBoxStyler(this.selectBox, themeService));
this._register(this.debugService.getViewModel().onDidFocusSession(() => {
const session = this.debugService.getViewModel().focusedSession;
const session = this.getSelectedSession();
if (session) {
const index = this.getSessions().indexOf(session);
this.select(index);
......@@ -222,11 +222,11 @@ export class FocusSessionActionViewItem extends SelectActionViewItem {
}
protected getActionContext(_: string, index: number): any {
return this.debugService.getModel().getSessions()[index];
return this.getSessions()[index];
}
private update() {
const session = this.debugService.getViewModel().focusedSession;
const session = this.getSelectedSession();
const sessions = this.getSessions();
const names = sessions.map(s => {
const label = s.getLabel();
......@@ -240,10 +240,23 @@ export class FocusSessionActionViewItem extends SelectActionViewItem {
this.setOptions(names.map(data => <ISelectOptionItem>{ text: data }), session ? sessions.indexOf(session) : undefined);
}
private getSelectedSession(): IDebugSession | undefined {
const session = this.debugService.getViewModel().focusedSession;
return session ? this.mapFocusedSessionToSelected(session) : undefined;
}
protected getSessions(): ReadonlyArray<IDebugSession> {
const showSubSessions = this.configurationService.getValue<IDebugConfiguration>('debug').showSubSessionsInToolBar;
const sessions = this.debugService.getModel().getSessions();
return showSubSessions ? sessions : sessions.filter(s => !s.parentSession);
}
protected mapFocusedSessionToSelected(focusedSession: IDebugSession): IDebugSession {
const showSubSessions = this.configurationService.getValue<IDebugConfiguration>('debug').showSubSessionsInToolBar;
while (focusedSession.parentSession && !showSubSessions) {
focusedSession = focusedSession.parentSession;
}
return focusedSession;
}
}
......@@ -41,7 +41,7 @@ import { IAction } from 'vs/base/common/actions';
import { deepClone, equals } from 'vs/base/common/objects';
import { DebugSession } from 'vs/workbench/contrib/debug/browser/debugSession';
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
import { IDebugService, State, IDebugSession, CONTEXT_DEBUG_TYPE, CONTEXT_DEBUG_STATE, CONTEXT_IN_DEBUG_MODE, IThread, IDebugConfiguration, VIEWLET_ID, REPL_ID, IConfig, ILaunch, IViewModel, IConfigurationManager, IDebugModel, IEnablement, IBreakpoint, IBreakpointData, ICompound, IGlobalConfig, IStackFrame, AdapterEndEvent, getStateLabel } from 'vs/workbench/contrib/debug/common/debug';
import { IDebugService, State, IDebugSession, CONTEXT_DEBUG_TYPE, CONTEXT_DEBUG_STATE, CONTEXT_IN_DEBUG_MODE, IThread, IDebugConfiguration, VIEWLET_ID, REPL_ID, IConfig, ILaunch, IViewModel, IConfigurationManager, IDebugModel, IEnablement, IBreakpoint, IBreakpointData, ICompound, IGlobalConfig, IStackFrame, AdapterEndEvent, getStateLabel, IDebugSessionOptions } from 'vs/workbench/contrib/debug/common/debug';
import { isExtensionHostDebugging } from 'vs/workbench/contrib/debug/common/debugUtils';
import { isErrorWithActions, createErrorWithActions } from 'vs/base/common/errorsWithActions';
import { RunOnceScheduler } from 'vs/base/common/async';
......@@ -255,7 +255,7 @@ 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, noDebug = false, parentSession?: IDebugSession): Promise<boolean> {
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
......@@ -318,7 +318,7 @@ export class DebugService implements IDebugService {
}
}
return this.createSession(launchForName, launchForName!.getConfiguration(name), noDebug, parentSession);
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
}
......@@ -328,7 +328,7 @@ export class DebugService implements IDebugService {
return Promise.reject(new Error(message));
}
return this.createSession(launch, config, noDebug, parentSession);
return this.createSession(launch, config, options);
});
}));
}).then(success => {
......@@ -344,7 +344,7 @@ export class DebugService implements IDebugService {
/**
* gets the debugger for the type, resolves configurations by providers, substitutes variables and runs prelaunch tasks
*/
private createSession(launch: ILaunch | undefined, config: IConfig | undefined, noDebug: boolean, parentSession?: IDebugSession): Promise<boolean> {
private 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;
......@@ -356,7 +356,7 @@ export class DebugService implements IDebugService {
}
const unresolvedConfig = deepClone(config);
if (noDebug) {
if (options && options.noDebug) {
config!.noDebug = true;
}
......@@ -390,7 +390,7 @@ export class DebugService implements IDebugService {
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 }, parentSession);
return this.doCreateSession(workspace, { resolved: resolvedConfig, unresolved: unresolvedConfig }, options);
}
return false;
});
......@@ -419,9 +419,9 @@ export class DebugService implements IDebugService {
/**
* instantiates the new session, initializes the session, registers session listeners and reports telemetry
*/
private doCreateSession(root: IWorkspaceFolder | undefined, configuration: { resolved: IConfig, unresolved: IConfig | undefined }, parentSession?: IDebugSession): Promise<boolean> {
private doCreateSession(root: IWorkspaceFolder | undefined, configuration: { resolved: IConfig, unresolved: IConfig | undefined }, options?: IDebugSessionOptions): Promise<boolean> {
const session = this.instantiationService.createInstance(DebugSession, configuration, root, this.model, parentSession);
const session = this.instantiationService.createInstance(DebugSession, configuration, root, this.model, options);
this.model.addSession(session);
// register listeners as the very first thing!
this.registerSessionListeners(session);
......
......@@ -12,7 +12,7 @@ import { Event, Emitter } from 'vs/base/common/event';
import { CompletionItem, completionKindFromString } from 'vs/editor/common/modes';
import { Position, IPosition } from 'vs/editor/common/core/position';
import * as aria from 'vs/base/browser/ui/aria/aria';
import { IDebugSession, IConfig, IThread, IRawModelUpdate, IDebugService, IRawStoppedDetails, State, LoadedSourceEvent, IFunctionBreakpoint, IExceptionBreakpoint, IBreakpoint, IExceptionInfo, AdapterEndEvent, IDebugger, VIEWLET_ID, IDebugConfiguration, IReplElement, IStackFrame, IExpression, IReplElementSource, IDataBreakpoint } from 'vs/workbench/contrib/debug/common/debug';
import { IDebugSession, IConfig, IThread, IRawModelUpdate, IDebugService, IRawStoppedDetails, State, LoadedSourceEvent, IFunctionBreakpoint, IExceptionBreakpoint, IBreakpoint, IExceptionInfo, AdapterEndEvent, IDebugger, VIEWLET_ID, IDebugConfiguration, IReplElement, IStackFrame, IExpression, IReplElementSource, IDataBreakpoint, IDebugSessionOptions } from 'vs/workbench/contrib/debug/common/debug';
import { Source } from 'vs/workbench/contrib/debug/common/debugSource';
import { mixin } from 'vs/base/common/objects';
import { Thread, ExpressionContainer, DebugModel } from 'vs/workbench/contrib/debug/common/debugModel';
......@@ -43,6 +43,7 @@ export class DebugSession implements IDebugSession {
private _subId: string | undefined;
private raw: RawDebugSession | undefined;
private initialized = false;
private _options: IDebugSessionOptions;
private sources = new Map<string, Source>();
private threads = new Map<number, Thread>();
......@@ -66,7 +67,7 @@ export class DebugSession implements IDebugSession {
private _configuration: { resolved: IConfig, unresolved: IConfig | undefined },
public root: IWorkspaceFolder,
private model: DebugModel,
private _parentSession: IDebugSession | undefined,
options: IDebugSessionOptions | undefined,
@IDebugService private readonly debugService: IDebugService,
@ITelemetryService private readonly telemetryService: ITelemetryService,
@IWindowService private readonly windowService: IWindowService,
......@@ -79,7 +80,12 @@ export class DebugSession implements IDebugSession {
@IOpenerService private readonly openerService: IOpenerService
) {
this.id = generateUuid();
this.repl = new ReplModel(this);
this._options = options || {};
if (this.hasSeparateRepl()) {
this.repl = new ReplModel();
} else {
this.repl = (this.parentSession as DebugSession).repl;
}
}
getId(): string {
......@@ -103,7 +109,7 @@ export class DebugSession implements IDebugSession {
}
get parentSession(): IDebugSession | undefined {
return this._parentSession;
return this._options.parentSession;
}
setConfiguration(configuration: { resolved: IConfig, unresolved: IConfig | undefined }) {
......@@ -954,13 +960,17 @@ export class DebugSession implements IDebugSession {
return this.repl.getReplElements();
}
hasSeparateRepl(): boolean {
return !this.parentSession || this._options.repl !== 'mergeWithParent';
}
removeReplExpressions(): void {
this.repl.removeReplExpressions();
this._onDidChangeREPLElements.fire();
}
async addReplExpression(stackFrame: IStackFrame | undefined, name: string): Promise<void> {
const expressionEvaluated = this.repl.addReplExpression(stackFrame, name);
const expressionEvaluated = this.repl.addReplExpression(this, stackFrame, name);
this._onDidChangeREPLElements.fire();
await expressionEvaluated;
this._onDidChangeREPLElements.fire();
......@@ -974,7 +984,7 @@ export class DebugSession implements IDebugSession {
}
logToRepl(sev: severity, args: any[], frame?: { uri: URI, line: number, column: number }) {
this.repl.logToRepl(sev, args, frame);
this.repl.logToRepl(this, sev, args, frame);
this._onDidChangeREPLElements.fire();
}
}
......@@ -356,7 +356,7 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati
getActions(): IAction[] {
const result: IAction[] = [];
if (this.debugService.getModel().getSessions(true).filter(s => !sessionsToIgnore.has(s)).length > 1) {
if (this.debugService.getModel().getSessions(true).filter(s => s.hasSeparateRepl() && !sessionsToIgnore.has(s)).length > 1) {
result.push(this.selectReplAction);
}
result.push(this.clearReplAction);
......@@ -968,12 +968,15 @@ registerEditorAction(FilterReplAction);
class SelectReplActionViewItem extends FocusSessionActionViewItem {
protected getActionContext(_: string, index: number): any {
return this.debugService.getModel().getSessions(true)[index];
protected getSessions(): ReadonlyArray<IDebugSession> {
return this.debugService.getModel().getSessions(true).filter(s => s.hasSeparateRepl() && !sessionsToIgnore.has(s));
}
protected getSessions(): ReadonlyArray<IDebugSession> {
return this.debugService.getModel().getSessions(true).filter(s => !sessionsToIgnore.has(s));
protected mapFocusedSessionToSelected(focusedSession: IDebugSession): IDebugSession {
while (focusedSession.parentSession && !focusedSession.hasSeparateRepl()) {
focusedSession = focusedSession.parentSession;
}
return focusedSession;
}
}
......
......@@ -147,6 +147,14 @@ export interface LoadedSourceEvent {
source: Source;
}
export type IDebugSessionReplMode = 'separate' | 'mergeWithParent';
export interface IDebugSessionOptions {
noDebug?: boolean;
parentSession?: IDebugSession;
repl?: IDebugSessionReplMode;
}
export interface IDebugSession extends ITreeElement {
readonly configuration: IConfig;
......@@ -173,7 +181,7 @@ export interface IDebugSession extends ITreeElement {
clearThreads(removeThreads: boolean, reference?: number): void;
getReplElements(): IReplElement[];
hasSeparateRepl(): boolean;
removeReplExpressions(): void;
addReplExpression(stackFrame: IStackFrame | undefined, name: string): Promise<void>;
appendToRepl(data: string | IExpression, severity: severity, source?: IReplElementSource): void;
......@@ -815,7 +823,7 @@ export interface IDebugService {
* Returns true if the start debugging was successfull. For compound launches, all configurations have to start successfuly for it to return success.
* On errors the startDebugging will throw an error, however some error and cancelations are handled and in that case will simply return false.
*/
startDebugging(launch: ILaunch | undefined, configOrName?: IConfig | string, noDebug?: boolean, parentSession?: IDebugSession): Promise<boolean>;
startDebugging(launch: ILaunch | undefined, configOrName?: IConfig | string, options?: IDebugSessionOptions): Promise<boolean>;
/**
* Restarts a session or creates a new one if there is no active session.
......
......@@ -27,7 +27,7 @@ export function startDebugging(debugService: IDebugService, historyService: IHis
configurationManager.selectConfiguration(launch);
}
return debugService.startDebugging(launch, undefined, noDebug);
return debugService.startDebugging(launch, undefined, { noDebug });
}
export function formatPII(value: string, excludePII: boolean, args: { [key: string]: string }): string {
......
......@@ -108,16 +108,14 @@ export class ReplEvaluationResult extends ExpressionContainer implements IReplEl
export class ReplModel {
private replElements: IReplElement[] = [];
constructor(private session: IDebugSession) { }
getReplElements(): IReplElement[] {
return this.replElements;
}
async addReplExpression(stackFrame: IStackFrame | undefined, name: string): Promise<void> {
async addReplExpression(session: IDebugSession, stackFrame: IStackFrame | undefined, name: string): Promise<void> {
this.addReplElement(new ReplEvaluationInput(name));
const result = new ReplEvaluationResult();
await result.evaluateExpression(name, this.session, stackFrame, 'repl');
await result.evaluateExpression(name, session, stackFrame, 'repl');
this.addReplElement(result);
}
......@@ -153,14 +151,14 @@ export class ReplModel {
}
}
logToRepl(sev: severity, args: any[], frame?: { uri: URI, line: number, column: number }) {
logToRepl(session: IDebugSession, sev: severity, args: any[], frame?: { uri: URI, line: number, column: number }) {
let source: IReplElementSource | undefined;
if (frame) {
source = {
column: frame.column,
lineNumber: frame.line,
source: this.session.getSource({
source: session.getSource({
name: basenameOrAuthority(frame.uri),
path: frame.uri.fsPath
})
......
......@@ -12,11 +12,11 @@ import { MockRawSession } from 'vs/workbench/contrib/debug/test/common/mockDebug
import { Source } from 'vs/workbench/contrib/debug/common/debugSource';
import { DebugSession } from 'vs/workbench/contrib/debug/browser/debugSession';
import { SimpleReplElement, RawObjectReplElement, ReplEvaluationInput, ReplModel } from 'vs/workbench/contrib/debug/common/replModel';
import { IBreakpointUpdateData } from 'vs/workbench/contrib/debug/common/debug';
import { IBreakpointUpdateData, IDebugSessionOptions } from 'vs/workbench/contrib/debug/common/debug';
import { NullOpenerService } from 'vs/platform/opener/common/opener';
function createMockSession(model: DebugModel, name = 'mockSession', parentSession?: DebugSession | undefined): DebugSession {
return new DebugSession({ resolved: { name, type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, parentSession, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, NullOpenerService);
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);
}
suite('Debug - Model', () => {
......@@ -341,10 +341,10 @@ suite('Debug - Model', () => {
session['raw'] = <any>rawSession;
const thread = new Thread(session, 'mockthread', 1);
const stackFrame = new StackFrame(thread, 1, <any>undefined, 'app.js', 'normal', { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 10 }, 1);
const replModel = new ReplModel(session);
replModel.addReplExpression(stackFrame, 'myVariable').then();
replModel.addReplExpression(stackFrame, 'myVariable').then();
replModel.addReplExpression(stackFrame, 'myVariable').then();
const replModel = new ReplModel();
replModel.addReplExpression(session, stackFrame, 'myVariable').then();
replModel.addReplExpression(session, stackFrame, 'myVariable').then();
replModel.addReplExpression(session, stackFrame, 'myVariable').then();
assert.equal(replModel.getReplElements().length, 3);
replModel.getReplElements().forEach(re => {
......@@ -405,13 +405,13 @@ suite('Debug - Model', () => {
model.addSession(session);
const secondSession = createMockSession(model, 'mockSession2');
model.addSession(secondSession);
const firstChild = createMockSession(model, 'firstChild', session);
const firstChild = createMockSession(model, 'firstChild', { parentSession: session });
model.addSession(firstChild);
const secondChild = createMockSession(model, 'secondChild', session);
const secondChild = createMockSession(model, 'secondChild', { parentSession: session });
model.addSession(secondChild);
const thirdSession = createMockSession(model, 'mockSession3');
model.addSession(thirdSession);
const anotherChild = createMockSession(model, 'secondChild', secondSession);
const anotherChild = createMockSession(model, 'secondChild', { parentSession: secondSession });
model.addSession(anotherChild);
const sessions = model.getSessions();
......@@ -426,8 +426,7 @@ suite('Debug - Model', () => {
// Repl output
test('repl output', () => {
const session = new DebugSession({ resolved: { name: 'mockSession', type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, undefined, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, NullOpenerService);
const repl = new ReplModel(session);
const repl = new ReplModel();
repl.appendToRepl('first line\n', severity.Error);
repl.appendToRepl('second line ', severity.Error);
repl.appendToRepl('third line ', severity.Error);
......@@ -466,4 +465,41 @@ suite('Debug - Model', () => {
assert.equal(elements[1], '23\n45\n');
assert.equal(elements[2], '6');
});
test('repl merging', () => {
// 'mergeWithParent' should be ignored when there is no parent.
const parent = createMockSession(model, 'parent', { repl: 'mergeWithParent' });
const child1 = createMockSession(model, 'child1', { parentSession: parent, repl: 'separate' });
const child2 = createMockSession(model, 'child2', { parentSession: parent, repl: 'mergeWithParent' });
const grandChild = createMockSession(model, 'grandChild', { parentSession: child2, repl: 'mergeWithParent' });
const child3 = createMockSession(model, 'child3', { parentSession: parent });
parent.appendToRepl('1\n', severity.Info);
assert.equal(parent.getReplElements().length, 1);
assert.equal(child1.getReplElements().length, 0);
assert.equal(child2.getReplElements().length, 1);
assert.equal(grandChild.getReplElements().length, 1);
assert.equal(child3.getReplElements().length, 0);
grandChild.appendToRepl('1\n', severity.Info);
assert.equal(parent.getReplElements().length, 2);
assert.equal(child1.getReplElements().length, 0);
assert.equal(child2.getReplElements().length, 2);
assert.equal(grandChild.getReplElements().length, 2);
assert.equal(child3.getReplElements().length, 0);
child3.appendToRepl('1\n', severity.Info);
assert.equal(parent.getReplElements().length, 2);
assert.equal(child1.getReplElements().length, 0);
assert.equal(child2.getReplElements().length, 2);
assert.equal(grandChild.getReplElements().length, 2);
assert.equal(child3.getReplElements().length, 1);
child1.appendToRepl('1\n', severity.Info);
assert.equal(parent.getReplElements().length, 2);
assert.equal(child1.getReplElements().length, 1);
assert.equal(child2.getReplElements().length, 2);
assert.equal(grandChild.getReplElements().length, 2);
assert.equal(child3.getReplElements().length, 1);
});
});
......@@ -7,7 +7,7 @@ import { URI as uri } from 'vs/base/common/uri';
import { Event } from 'vs/base/common/event';
import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { Position, IPosition } from 'vs/editor/common/core/position';
import { ILaunch, IDebugService, State, IDebugSession, IConfigurationManager, IStackFrame, IBreakpointData, IBreakpointUpdateData, IConfig, IDebugModel, IViewModel, IBreakpoint, LoadedSourceEvent, IThread, IRawModelUpdate, IFunctionBreakpoint, IExceptionBreakpoint, IDebugger, IExceptionInfo, AdapterEndEvent, IReplElement, IExpression, IReplElementSource, IDataBreakpoint } from 'vs/workbench/contrib/debug/common/debug';
import { ILaunch, IDebugService, State, IDebugSession, IConfigurationManager, IStackFrame, IBreakpointData, IBreakpointUpdateData, IConfig, IDebugModel, IViewModel, IBreakpoint, LoadedSourceEvent, IThread, IRawModelUpdate, IFunctionBreakpoint, IExceptionBreakpoint, IDebugger, IExceptionInfo, AdapterEndEvent, IReplElement, IExpression, IReplElementSource, IDataBreakpoint, IDebugSessionOptions } from 'vs/workbench/contrib/debug/common/debug';
import { Source } from 'vs/workbench/contrib/debug/common/debugSource';
import { CompletionItem } from 'vs/editor/common/modes';
import Severity from 'vs/base/common/severity';
......@@ -102,7 +102,7 @@ export class MockDebugService implements IDebugService {
public removeWatchExpressions(id?: string): void { }
public startDebugging(launch: ILaunch, configOrName?: IConfig | string, noDebug?: boolean): Promise<boolean> {
public startDebugging(launch: ILaunch, configOrName?: IConfig | string, options?: IDebugSessionOptions): Promise<boolean> {
return Promise.resolve(true);
}
......@@ -159,6 +159,10 @@ export class MockSession implements IDebugSession {
return [];
}
hasSeparateRepl(): boolean {
return true;
}
removeReplExpressions(): void { }
get onDidChangeReplElements(): Event<void> {
throw new Error('not implemented');
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册