提交 53042122 编写于 作者: I Isidor Nikolic 提交者: GitHub

Merge pull request #14264 from Microsoft/isidorn/multipleDebugSessions

Multiple debug sessions
......@@ -8,8 +8,8 @@ import errors = require('vs/base/common/errors');
import { TPromise } from 'vs/base/common/winjs.base';
import { IAction } from 'vs/base/common/actions';
import { SelectActionItem } from 'vs/base/browser/ui/actionbar/actionbar';
import { IDebugService, State } from 'vs/workbench/parts/debug/common/debug';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IDebugService, State } from 'vs/workbench/parts/debug/common/debug';
export class DebugSelectActionItem extends SelectActionItem {
......@@ -23,11 +23,11 @@ export class DebugSelectActionItem extends SelectActionItem {
this.toDispose.push(configurationService.onDidUpdateConfiguration(e => {
this.updateOptions(true).done(null, errors.onUnexpectedError);
}));
this.toDispose.push(this.debugService.getConfigurationManager().onDidConfigurationChange(name => {
this.toDispose.push(this.debugService.getViewModel().onDidSelectConfigurationName(name => {
this.updateOptions(false).done(null, errors.onUnexpectedError);
}));
this.toDispose.push(this.debugService.onDidChangeState(state => {
this.enabled = state === State.Inactive;
this.toDispose.push(this.debugService.onDidChangeState(() => {
this.enabled = this.debugService.state === State.Inactive;
}));
}
......@@ -46,10 +46,9 @@ export class DebugSelectActionItem extends SelectActionItem {
}
const configurationNames = config.configurations.filter(cfg => !!cfg.name).map(cfg => cfg.name);
const configurationName = configurationManager.configuration ? configurationManager.configuration.name : null;
let selected = configurationNames.indexOf(configurationName);
const selected = configurationNames.indexOf(this.debugService.getViewModel().selectedConfigurationName);
this.setOptions(configurationNames, selected);
if (changeDebugConfiguration) {
return this.actionRunner.run(this._action, this.getSelected());
}
......
......@@ -39,10 +39,10 @@ export class AbstractDebugAction extends Action {
super(id, label, cssClass, false);
this.debugService = debugService;
this.toDispose = [];
this.toDispose.push(this.debugService.onDidChangeState((state) => this.updateEnablement(state)));
this.toDispose.push(this.debugService.onDidChangeState(() => this.updateEnablement()));
this.updateLabel(label);
this.updateEnablement(this.debugService.state);
this.updateEnablement();
}
public run(e?: any): TPromise<any> {
......@@ -53,8 +53,8 @@ export class AbstractDebugAction extends Action {
this.label = newLabel;
}
protected updateEnablement(state: debug.State): void {
this.enabled = this.isEnabled(state);
protected updateEnablement(): void {
this.enabled = this.isEnabled(this.debugService.state);
}
protected isEnabled(state: debug.State): boolean {
......@@ -75,7 +75,7 @@ export class ConfigureAction extends AbstractDebugAction {
constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) {
super(id, label, 'debug-action configure', debugService, keybindingService);
this.toDispose.push(debugService.getConfigurationManager().onDidConfigurationChange((configurationName) => {
this.toDispose.push(debugService.getViewModel().onDidSelectConfigurationName(configurationName => {
if (configurationName) {
this.class = 'debug-action configure';
this.tooltip = ConfigureAction.LABEL;
......@@ -101,7 +101,8 @@ export class SelectConfigAction extends AbstractDebugAction {
}
public run(configName: string): TPromise<any> {
return this.debugService.getConfigurationManager().setConfiguration(configName);
this.debugService.getViewModel().setSelectedConfigurationName(configName);
return TPromise.as(null);
}
protected isEnabled(state: debug.State): boolean {
......@@ -118,7 +119,7 @@ export class StartAction extends AbstractDebugAction {
}
public run(): TPromise<any> {
return this.commandService.executeCommand('_workbench.startDebug');
return this.commandService.executeCommand('_workbench.startDebug', this.debugService.getViewModel().selectedConfigurationName);
}
protected isEnabled(state: debug.State): boolean {
......@@ -133,16 +134,22 @@ export class RestartAction extends AbstractDebugAction {
constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) {
super(id, label, 'debug-action restart', debugService, keybindingService);
this.setLabel(this.debugService.getConfigurationManager().configuration);
this.toDispose.push(this.debugService.getConfigurationManager().onDidConfigurationChange(config => this.setLabel(config)));
this.setLabel(this.debugService.getViewModel().focusedProcess);
this.toDispose.push(this.debugService.getViewModel().onDidFocusStackFrame(() => this.setLabel(this.debugService.getViewModel().focusedProcess)));
}
private setLabel(config: debug.IConfig): void {
this.updateLabel(config && config.request === 'attach' ? RestartAction.RECONNECT_LABEL : RestartAction.LABEL);
private setLabel(process: debug.IProcess): void {
this.updateLabel(process && process.session.requestType === debug.SessionRequestType.ATTACH ? RestartAction.RECONNECT_LABEL : RestartAction.LABEL);
}
public run(): TPromise<any> {
return this.debugService.restartSession();
let process = this.debugService.getViewModel().focusedProcess;
if (!process) {
const processes = this.debugService.getModel().getProcesses();
process = processes.length > 0 ? processes[0] : null;
}
return this.debugService.restartProcess(process);
}
protected isEnabled(state: debug.State): boolean {
......@@ -159,10 +166,11 @@ export class StepOverAction extends AbstractDebugAction {
}
public run(thread: debug.IThread): TPromise<any> {
const threadId = thread && thread instanceof model.Thread ? thread.threadId
: this.debugService.getViewModel().getFocusedThreadId();
if (!(thread instanceof model.Thread)) {
thread = this.debugService.getViewModel().focusedThread;
}
return this.debugService.next(threadId);
return thread ? thread.next() : TPromise.as(null);
}
protected isEnabled(state: debug.State): boolean {
......@@ -179,10 +187,11 @@ export class StepIntoAction extends AbstractDebugAction {
}
public run(thread: debug.IThread): TPromise<any> {
const threadId = thread && thread instanceof model.Thread ? thread.threadId
: this.debugService.getViewModel().getFocusedThreadId();
if (!(thread instanceof model.Thread)) {
thread = this.debugService.getViewModel().focusedThread;
}
return this.debugService.stepIn(threadId);
return thread ? thread.stepIn() : TPromise.as(null);
}
protected isEnabled(state: debug.State): boolean {
......@@ -199,10 +208,11 @@ export class StepOutAction extends AbstractDebugAction {
}
public run(thread: debug.IThread): TPromise<any> {
const threadId = thread && thread instanceof model.Thread ? thread.threadId
: this.debugService.getViewModel().getFocusedThreadId();
if (!(thread instanceof model.Thread)) {
thread = this.debugService.getViewModel().focusedThread;
}
return this.debugService.stepOut(threadId);
return thread ? thread.stepOut() : TPromise.as(null);
}
protected isEnabled(state: debug.State): boolean {
......@@ -219,16 +229,17 @@ export class StepBackAction extends AbstractDebugAction {
}
public run(thread: debug.IThread): TPromise<any> {
const threadId = thread && thread instanceof model.Thread ? thread.threadId
: this.debugService.getViewModel().getFocusedThreadId();
if (!(thread instanceof model.Thread)) {
thread = this.debugService.getViewModel().focusedThread;
}
return this.debugService.stepBack(threadId);
return thread.stepBack();
}
protected isEnabled(state: debug.State): boolean {
const activeSession = this.debugService.activeSession;
const process = this.debugService.getViewModel().focusedProcess;
return super.isEnabled(state) && state === debug.State.Stopped &&
activeSession && activeSession.configuration.capabilities.supportsStepBack;
process && process.session.configuration.capabilities.supportsStepBack;
}
}
......@@ -241,8 +252,13 @@ export class StopAction extends AbstractDebugAction {
}
public run(): TPromise<any> {
var session = this.debugService.activeSession;
return session ? session.disconnect(false, true) : TPromise.as(null);
let process = this.debugService.getViewModel().focusedProcess;
if (!process) {
const processes = this.debugService.getModel().getProcesses();
process = processes.length > 0 ? processes[0] : null;
}
return process ? process.session.disconnect(false, true) : TPromise.as(null);
}
protected isEnabled(state: debug.State): boolean {
......@@ -259,8 +275,12 @@ export class DisconnectAction extends AbstractDebugAction {
}
public run(): TPromise<any> {
const session = this.debugService.activeSession;
return session ? session.disconnect(false, true) : TPromise.as(null);
let process = this.debugService.getViewModel().focusedProcess;
if (!process) {
process = this.debugService.getModel().getProcesses().pop();
}
return process ? process.session.disconnect(false, true) : TPromise.as(null);
}
protected isEnabled(state: debug.State): boolean {
......@@ -277,10 +297,11 @@ export class ContinueAction extends AbstractDebugAction {
}
public run(thread: debug.IThread): TPromise<any> {
const threadId = thread && thread instanceof model.Thread ? thread.threadId
: this.debugService.getViewModel().getFocusedThreadId();
if (!(thread instanceof model.Thread)) {
thread = this.debugService.getViewModel().focusedThread;
}
return this.debugService.continue(threadId);
return thread ? thread.continue() : TPromise.as(null);
}
protected isEnabled(state: debug.State): boolean {
......@@ -297,10 +318,11 @@ export class PauseAction extends AbstractDebugAction {
}
public run(thread: debug.IThread): TPromise<any> {
const threadId = thread && thread instanceof model.Thread ? thread.threadId
: this.debugService.getViewModel().getFocusedThreadId();
if (!(thread instanceof model.Thread)) {
thread = this.debugService.getViewModel().focusedThread;
}
return this.debugService.pause(threadId);
return thread ? thread.pause() : TPromise.as(null);
}
protected isEnabled(state: debug.State): boolean {
......@@ -317,12 +339,11 @@ export class RestartFrameAction extends AbstractDebugAction {
}
public run(frame: debug.IStackFrame): TPromise<any> {
if (!frame) {
frame = this.debugService.getViewModel().focusedStackFrame;
}
const frameId = (frame && frame instanceof model.StackFrame)
? frame.frameId
: this.debugService.getViewModel().getFocusedStackFrame().frameId;
return this.debugService.restartFrame(frameId);
return frame.restart();
}
}
......@@ -346,7 +367,7 @@ export class RemoveAllBreakpointsAction extends AbstractDebugAction {
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.debugService.state)));
this.toDispose.push(this.debugService.getModel().onDidChangeBreakpoints(() => this.updateEnablement()));
}
public run(): TPromise<any> {
......@@ -378,7 +399,7 @@ export class EnableAllBreakpointsAction extends AbstractDebugAction {
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.debugService.state)));
this.toDispose.push(this.debugService.getModel().onDidChangeBreakpoints(() => this.updateEnablement()));
}
public run(): TPromise<any> {
......@@ -397,7 +418,7 @@ export class DisableAllBreakpointsAction extends AbstractDebugAction {
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.debugService.state)));
this.toDispose.push(this.debugService.getModel().onDidChangeBreakpoints(() => this.updateEnablement()));
}
public run(): TPromise<any> {
......@@ -421,7 +442,7 @@ export class ToggleBreakpointsActivatedAction extends AbstractDebugAction {
this.toDispose.push(this.debugService.getModel().onDidChangeBreakpoints(() => {
this.updateLabel(this.debugService.getModel().areBreakpointsActivated() ? ToggleBreakpointsActivatedAction.DEACTIVATE_LABEL : ToggleBreakpointsActivatedAction.ACTIVATE_LABEL);
this.updateEnablement(this.debugService.state);
this.updateEnablement();
}));
}
......@@ -440,7 +461,7 @@ export class ReapplyBreakpointsAction extends AbstractDebugAction {
constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) {
super(id, label, null, debugService, keybindingService);
this.toDispose.push(this.debugService.getModel().onDidChangeBreakpoints(() => this.updateEnablement(this.debugService.state)));
this.toDispose.push(this.debugService.getModel().onDidChangeBreakpoints(() => this.updateEnablement()));
}
public run(): TPromise<any> {
......@@ -592,8 +613,8 @@ export class SetValueAction extends AbstractDebugAction {
}
protected isEnabled(state: debug.State): boolean {
const session = this.debugService.activeSession;
return super.isEnabled(state) && state === debug.State.Stopped && session && session.configuration.capabilities.supportsSetVariable;
const process = this.debugService.getViewModel().focusedProcess;
return super.isEnabled(state) && state === debug.State.Stopped && process && process.session.configuration.capabilities.supportsSetVariable;
}
}
......@@ -622,7 +643,7 @@ class RunToCursorAction extends EditorAction {
const lineNumber = editor.getPosition().lineNumber;
const uri = editor.getModel().uri;
const oneTimeListener = debugService.activeSession.onDidEvent(event => {
const oneTimeListener = debugService.getViewModel().focusedProcess.session.onDidEvent(event => {
if (event.event === 'stopped' || event.event === 'exit') {
const toRemove = debugService.getModel().getBreakpoints()
.filter(bp => bp.desiredLineNumber === lineNumber && bp.source.uri.toString() === uri.toString()).pop();
......@@ -635,7 +656,7 @@ class RunToCursorAction extends EditorAction {
const bpExists = !!(debugService.getModel().getBreakpoints().filter(bp => bp.lineNumber === lineNumber && bp.source.uri.toString() === uri.toString()).pop());
return (bpExists ? TPromise.as(null) : debugService.addBreakpoints([{ uri, lineNumber }])).then(() => {
debugService.continue(debugService.getViewModel().getFocusedThreadId());
debugService.getViewModel().focusedThread.continue();
});
}
}
......@@ -646,7 +667,7 @@ export class AddWatchExpressionAction extends AbstractDebugAction {
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.debugService.state)));
this.toDispose.push(this.debugService.getModel().onDidChangeWatchExpressions(() => this.updateEnablement()));
}
public run(): TPromise<any> {
......@@ -747,7 +768,9 @@ export class AddToWatchExpressionsAction extends AbstractDebugAction {
}
public run(): TPromise<any> {
return this.debugService.addWatchExpression(model.getFullExpressionName(this.expression, this.debugService.activeSession.configuration.type));
const process = this.debugService.getViewModel().focusedProcess;
const type = process ? process.session.configuration.type : null;
return this.debugService.addWatchExpression(model.getFullExpressionName(this.expression, type));
}
}
......@@ -785,7 +808,7 @@ export class RemoveAllWatchExpressionsAction extends AbstractDebugAction {
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.debugService.state)));
this.toDispose.push(this.debugService.getModel().onDidChangeWatchExpressions(() => this.updateEnablement()));
}
public run(): TPromise<any> {
......@@ -885,7 +908,12 @@ export class RunAction extends AbstractDebugAction {
}
public run(): TPromise<any> {
return this.debugService.createSession(true);
return this.debugService.getConfigurationManager().getConfiguration(this.debugService.getViewModel().selectedConfigurationName).then(configuration => {
if (configuration) {
configuration.noDebug = true;
return this.debugService.createProcess(configuration);
}
});
}
protected isEnabled(state: debug.State): boolean {
......
......@@ -70,8 +70,8 @@ export class DebugActionsWidget implements wbext.IWorkbenchContribution {
}
private registerListeners(): void {
this.toDispose.push(this.debugService.onDidChangeState(state => {
this.onDebugStateChange(state);
this.toDispose.push(this.debugService.onDidChangeState(() => {
this.onDebugStateChange();
}));
this.toDispose.push(this.actionBar.actionRunner.addListener2(events.EventType.RUN, (e: any) => {
// check for error
......@@ -131,7 +131,8 @@ export class DebugActionsWidget implements wbext.IWorkbenchContribution {
return DebugActionsWidget.ID;
}
private onDebugStateChange(state: debug.State): void {
private onDebugStateChange(): void {
const state = this.debugService.state;
if (state === debug.State.Disabled || state === debug.State.Inactive) {
return this.hide();
}
......@@ -183,11 +184,10 @@ export class DebugActionsWidget implements wbext.IWorkbenchContribution {
}
this.actions[0] = state === debug.State.Running ? this.pauseAction : this.continueAction;
const session = this.debugService.activeSession;
const configuration = this.debugService.getConfigurationManager().configuration;
this.actions[5] = configuration && configuration.request === 'attach' ? this.disconnectAction : this.stopAction;
const process = this.debugService.getViewModel().focusedProcess;
this.actions[5] = process && process.session.requestType === debug.SessionRequestType.ATTACH ? this.disconnectAction : this.stopAction;
if (session && session.configuration.capabilities.supportsStepBack) {
if (process && process.session.configuration.capabilities.supportsStepBack) {
if (!this.stepBackAction) {
this.stepBackAction = instantiationService.createInstance(StepBackAction, StepBackAction.ID, StepBackAction.LABEL);
this.toDispose.push(this.stepBackAction);
......
......@@ -83,8 +83,8 @@ export class DebugEditorModelManager implements IWorkbenchContribution {
this.toDispose.push(this.debugService.getModel().onDidChangeBreakpoints(() => this.onBreakpointsChange()));
this.toDispose.push(this.debugService.getViewModel().onDidFocusStackFrame(() => this.onFocusStackFrame()));
this.toDispose.push(this.debugService.onDidChangeState(state => {
if (state === State.Inactive) {
this.toDispose.push(this.debugService.onDidChangeState(() => {
if (this.debugService.state === State.Inactive) {
Object.keys(this.modelData).forEach(key => this.modelData[key].dirty = false);
}
}));
......@@ -132,27 +132,24 @@ export class DebugEditorModelManager implements IWorkbenchContribution {
private createCallStackDecorations(modelUrlStr: string): editorcommon.IModelDeltaDecoration[] {
const result: editorcommon.IModelDeltaDecoration[] = [];
const focusedStackFrame = this.debugService.getViewModel().getFocusedStackFrame();
const focusedThreadId = this.debugService.getViewModel().getFocusedThreadId();
const allThreads = this.debugService.getModel().getThreads();
if (!focusedStackFrame || !allThreads[focusedThreadId] || !allThreads[focusedThreadId].getCachedCallStack()) {
const focusedStackFrame = this.debugService.getViewModel().focusedStackFrame;
if (!focusedStackFrame || !focusedStackFrame.thread.getCachedCallStack()) {
return result;
}
// only show decorations for the currently focussed thread.
const thread = allThreads[focusedThreadId];
thread.getCachedCallStack().filter(sf => sf.source.uri.toString() === modelUrlStr).forEach(sf => {
focusedStackFrame.thread.getCachedCallStack().filter(sf => sf.source.uri.toString() === modelUrlStr).forEach(sf => {
const wholeLineRange = createRange(sf.lineNumber, sf.column, sf.lineNumber, Number.MAX_VALUE);
// compute how to decorate the editor. Different decorations are used if this is a top stack frame, focussed stack frame,
// an exception or a stack frame that did not change the line number (we only decorate the columns, not the whole line).
if (sf === thread.getCachedCallStack()[0]) {
if (sf === focusedStackFrame.thread.getCachedCallStack()[0]) {
result.push({
options: DebugEditorModelManager.TOP_STACK_FRAME_MARGIN,
range: createRange(sf.lineNumber, sf.column, sf.lineNumber, sf.column + 1)
});
if (thread.stoppedDetails.reason === 'exception') {
if (focusedStackFrame.thread.stoppedDetails.reason === 'exception') {
result.push({
options: DebugEditorModelManager.TOP_STACK_FRAME_EXCEPTION_DECORATION,
range: wholeLineRange
......@@ -224,7 +221,7 @@ export class DebugEditorModelManager implements IWorkbenchContribution {
});
}
}
modelData.dirty = !!this.debugService.activeSession;
modelData.dirty = this.debugService.state !== State.Inactive && this.debugService.state !== State.Disabled;
const toRemove = this.debugService.getModel().getBreakpoints()
.filter(bp => bp.source.uri.toString() === modelUrl.toString());
......@@ -277,7 +274,6 @@ export class DebugEditorModelManager implements IWorkbenchContribution {
const state = this.debugService.state;
const debugActive = state === State.Running || state === State.Stopped || state === State.Initializing;
const modelData = this.modelData[breakpoint.source.uri.toString()];
const session = this.debugService.activeSession;
let result = (!breakpoint.enabled || !activated) ? DebugEditorModelManager.BREAKPOINT_DISABLED_DECORATION :
debugActive && modelData && modelData.dirty && !breakpoint.verified ? DebugEditorModelManager.BREAKPOINT_DIRTY_DECORATION :
......@@ -293,7 +289,8 @@ export class DebugEditorModelManager implements IWorkbenchContribution {
return result;
}
if (session && !session.configuration.capabilities.supportsConditionalBreakpoints) {
const process = this.debugService.getViewModel().focusedProcess;
if (process && !process.session.configuration.capabilities.supportsConditionalBreakpoints) {
return DebugEditorModelManager.BREAKPOINT_UNSUPPORTED_DECORATION;
}
......
......@@ -53,8 +53,8 @@ export class DebugViewlet extends Viewlet {
this.viewletSettings = this.getMemento(storageService, memento.Scope.WORKSPACE);
this.toDispose = [];
this.views = [];
this.toDispose.push(this.debugService.onDidChangeState((state) => {
this.onDebugServiceStateChange(state);
this.toDispose.push(this.debugService.onDidChangeState(() => {
this.onDebugServiceStateChange();
}));
}
......@@ -146,12 +146,12 @@ export class DebugViewlet extends Viewlet {
return null;
}
private onDebugServiceStateChange(newState: debug.State): void {
private onDebugServiceStateChange(): void {
if (this.progressRunner) {
this.progressRunner.done();
}
if (newState === debug.State.Initializing) {
if (this.debugService.state === debug.State.Initializing) {
this.progressRunner = this.progressService.show(true);
} else {
this.progressRunner = null;
......
......@@ -31,6 +31,7 @@ export const DEBUG_SCHEME = 'debug';
export interface IRawModelUpdate {
threadId: number;
sessionId: string;
thread?: DebugProtocol.Thread;
callStack?: DebugProtocol.StackFrame[];
stoppedDetails?: IRawStoppedDetails;
......@@ -53,6 +54,7 @@ export interface ITreeElement {
export interface IExpressionContainer extends ITreeElement {
reference: number;
stackFrame: IStackFrame;
getChildren(debugService: IDebugService): TPromise<IExpression[]>;
}
......@@ -63,7 +65,51 @@ export interface IExpression extends ITreeElement, IExpressionContainer {
type?: string;
}
export enum SessionRequestType {
LAUNCH,
ATTACH,
LAUNCH_NO_DEBUG
}
export interface ISession {
requestType: SessionRequestType;
stackTrace(args: DebugProtocol.StackTraceArguments): TPromise<DebugProtocol.StackTraceResponse>;
scopes(args: DebugProtocol.ScopesArguments): TPromise<DebugProtocol.ScopesResponse>;
variables(args: DebugProtocol.VariablesArguments): TPromise<DebugProtocol.VariablesResponse>;
evaluate(args: DebugProtocol.EvaluateArguments): TPromise<DebugProtocol.EvaluateResponse>;
configuration: { type: string, capabilities: DebugProtocol.Capabilities };
disconnect(restart?: boolean, force?: boolean): TPromise<DebugProtocol.DisconnectResponse>;
custom(request: string, args: any): TPromise<DebugProtocol.Response>;
onDidEvent: Event<DebugProtocol.Event>;
restartFrame(args: DebugProtocol.RestartFrameArguments): TPromise<DebugProtocol.RestartFrameResponse>;
next(args: DebugProtocol.NextArguments): TPromise<DebugProtocol.NextResponse>;
stepIn(args: DebugProtocol.StepInArguments): TPromise<DebugProtocol.StepInResponse>;
stepOut(args: DebugProtocol.StepOutArguments): TPromise<DebugProtocol.StepOutResponse>;
stepBack(args: DebugProtocol.StepBackArguments): TPromise<DebugProtocol.StepBackResponse>;
continue(args: DebugProtocol.ContinueArguments): TPromise<DebugProtocol.ContinueResponse>;
pause(args: DebugProtocol.PauseArguments): TPromise<DebugProtocol.PauseResponse>;
completions(args: DebugProtocol.CompletionsArguments): TPromise<DebugProtocol.CompletionsResponse>;
setVariable(args: DebugProtocol.SetVariableArguments): TPromise<DebugProtocol.SetVariableResponse>;
source(args: DebugProtocol.SourceArguments): TPromise<DebugProtocol.SourceResponse>;
}
export interface IProcess extends ITreeElement {
name: string;
getThread(threadId: number): IThread;
getAllThreads(): IThread[];
session: ISession;
}
export interface IThread extends ITreeElement {
/**
* Process the thread belongs to
*/
process: IProcess;
/**
* Id of the thread generated by the debug adapter backend.
*/
......@@ -86,7 +132,7 @@ export interface IThread extends ITreeElement {
* Only gets the first 20 stack frames. Calling this method consecutive times
* with getAdditionalStackFrames = true gets the remainder of the call stack.
*/
getCallStack(debugService: IDebugService, getAdditionalStackFrames?: boolean): TPromise<IStackFrame[]>;
getCallStack(getAdditionalStackFrames?: boolean): TPromise<IStackFrame[]>;
/**
* Gets the callstack if it has already been received from the debug
......@@ -104,6 +150,13 @@ export interface IThread extends ITreeElement {
* threads can be retrieved from the debug adapter.
*/
stopped: boolean;
next(): TPromise<any>;
stepIn(): TPromise<any>;
stepOut(): TPromise<any>;
stepBack(): TPromise<any>;
continue(): TPromise<any>;
pause(): TPromise<any>;
}
export interface IScope extends IExpressionContainer {
......@@ -112,13 +165,15 @@ export interface IScope extends IExpressionContainer {
}
export interface IStackFrame extends ITreeElement {
threadId: number;
thread: IThread;
name: string;
lineNumber: number;
column: number;
frameId: number;
source: Source;
getScopes(debugService: IDebugService): TPromise<IScope[]>;
getScopes(): TPromise<IScope[]>;
restart(): TPromise<any>;
completions(text: string, position: Position): TPromise<ISuggestion[]>;
}
export interface IEnablement extends ITreeElement {
......@@ -159,20 +214,39 @@ export interface IExceptionBreakpoint extends IEnablement {
// model interfaces
export interface IViewModel extends ITreeElement {
getFocusedStackFrame(): IStackFrame;
/**
* Returns the focused debug process or null if no process is stopped.
*/
focusedProcess: IProcess;
/**
* Returns the focused thread or null if no thread is stopped.
*/
focusedThread: IThread;
/**
* Returns the focused stack frame or null if there are no stack frames.
*/
focusedStackFrame: IStackFrame;
getSelectedExpression(): IExpression;
getFocusedThreadId(): number;
setSelectedExpression(expression: IExpression);
getSelectedFunctionBreakpoint(): IFunctionBreakpoint;
setSelectedExpression(expression: IExpression);
setSelectedFunctionBreakpoint(functionBreakpoint: IFunctionBreakpoint): void;
selectedConfigurationName: string;
setSelectedConfigurationName(name: string): void;
onDidFocusStackFrame: Event<IStackFrame>;
onDidSelectExpression: Event<IExpression>;
onDidSelectFunctionBreakpoint: Event<IFunctionBreakpoint>;
/**
* Allows to register on change of selected debug configuration.
*/
onDidSelectConfigurationName: Event<string>;
}
export interface IModel extends ITreeElement {
getThreads(): { [threadId: number]: IThread; };
getProcesses(): IProcess[];
getBreakpoints(): IBreakpoint[];
areBreakpointsActivated(): boolean;
getFunctionBreakpoints(): IFunctionBreakpoint[];
......@@ -221,6 +295,7 @@ export interface IEnvConfig {
debugServer?: number;
noDebug?: boolean;
silentlyAbort?: boolean;
configurationNames?: string[];
}
export interface IExtHostConfig extends IEnvConfig {
......@@ -261,32 +336,11 @@ export interface IRawBreakpointContribution {
language: string;
}
export interface IRawDebugSession {
configuration: { type: string, capabilities: DebugProtocol.Capabilities };
disconnect(restart?: boolean, force?: boolean): TPromise<DebugProtocol.DisconnectResponse>;
stackTrace(args: DebugProtocol.StackTraceArguments): TPromise<DebugProtocol.StackTraceResponse>;
scopes(args: DebugProtocol.ScopesArguments): TPromise<DebugProtocol.ScopesResponse>;
variables(args: DebugProtocol.VariablesArguments): TPromise<DebugProtocol.VariablesResponse>;
evaluate(args: DebugProtocol.EvaluateArguments): TPromise<DebugProtocol.EvaluateResponse>;
custom(request: string, args: any): TPromise<DebugProtocol.Response>;
onDidEvent: Event<DebugProtocol.Event>;
}
export interface IConfigurationManager {
configuration: IConfig;
setConfiguration(name: string): TPromise<void>;
getConfiguration(nameOrConfig: string | IConfig): TPromise<IConfig>;
openConfigFile(sideBySide: boolean): TPromise<boolean>;
loadLaunchConfig(): TPromise<IGlobalConfig>;
canSetBreakpointsIn(model: editor.IModel): boolean;
/**
* Allows to register on change of debug configuration.
*/
onDidConfigurationChange: Event<IConfig>;
}
export const IDebugService = createDecorator<IDebugService>(DEBUG_SERVICE_ID);
......@@ -302,7 +356,7 @@ export interface IDebugService {
/**
* Allows to register on debug state changes.
*/
onDidChangeState: Event<State>;
onDidChangeState: Event<void>;
/**
* Gets the current configuration manager.
......@@ -374,11 +428,6 @@ export interface IDebugService {
*/
appendReplOutput(value: string, severity?: severity): void;
/**
* Sets the value for the variable against the debug adapter.
*/
setVariable(variable: IExpression, value: string): TPromise<void>;
/**
* Adds a new watch expression and evaluates it against the debug adapter.
*/
......@@ -395,19 +444,14 @@ export interface IDebugService {
removeWatchExpressions(id?: string): void;
/**
* Creates a new debug session. Depending on the configuration will either 'launch' or 'attach'.
* Creates a new debug process. Depending on the configuration will either 'launch' or 'attach'.
*/
createSession(noDebug: boolean, configuration?: IConfig): TPromise<any>;
createProcess(configurationOrName: IConfig | string): TPromise<any>;
/**
* Restarts an active debug session or creates a new one if there is no active session.
* Restarts a process or creates a new one if there is no active session.
*/
restartSession(): TPromise<any>;
/**
* Returns the active debug session or null if debug is inactive.
*/
activeSession: IRawDebugSession;
restartProcess(process: IProcess): TPromise<any>;
/**
* Gets the current debug model.
......@@ -423,15 +467,6 @@ export interface IDebugService {
* Opens a new or reveals an already visible editor showing the source.
*/
openOrRevealSource(source: Source, lineNumber: number, preserveFocus: boolean, sideBySide: boolean): TPromise<any>;
next(threadId: number): TPromise<void>;
stepIn(threadId: number): TPromise<void>;
stepOut(threadId: number): TPromise<void>;
stepBack(threadId: number): TPromise<void>;
continue(threadId: number): TPromise<void>;
pause(threadId: number): TPromise<any>;
restartFrame(frameId: number): TPromise<any>;
completions(text: string, position: Position): TPromise<ISuggestion[]>;
}
// Editor interfaces
......
......@@ -38,12 +38,13 @@ export class Source {
public static toRawSource(uri: uri, model: IModel): DebugProtocol.Source {
if (model) {
// first try to find the raw source amongst the stack frames - since that represenation has more data (source reference),
const threads = model.getThreads();
for (let threadId in threads) {
if (threads.hasOwnProperty(threadId) && threads[threadId].getCachedCallStack()) {
const found = threads[threadId].getCachedCallStack().filter(sf => sf.source.uri.toString() === uri.toString()).pop();
if (found) {
return found.source.raw;
for (let process of model.getProcesses()) {
for (let thread of process.getAllThreads()) {
if (thread.getCachedCallStack()) {
const found = thread.getCachedCallStack().filter(sf => sf.source.uri.toString() === uri.toString()).pop();
if (found) {
return found.source.raw;
}
}
}
}
......
......@@ -8,19 +8,20 @@ import debug = require('vs/workbench/parts/debug/common/debug');
export class ViewModel implements debug.IViewModel {
private focusedStackFrame: debug.IStackFrame;
private focusedThread: debug.IThread;
private _focusedStackFrame: debug.IStackFrame;
private selectedExpression: debug.IExpression;
private selectedFunctionBreakpoint: debug.IFunctionBreakpoint;
private _onDidFocusStackFrame: Emitter<debug.IStackFrame>;
private _onDidSelectExpression: Emitter<debug.IExpression>;
private _onDidSelectFunctionBreakpoint: Emitter<debug.IFunctionBreakpoint>;
private _onDidSelectConfigurationName: Emitter<string>;
public changedWorkbenchViewState: boolean;
constructor() {
constructor(private _selectedConfigurationName: string) {
this._onDidFocusStackFrame = new Emitter<debug.IStackFrame>();
this._onDidSelectExpression = new Emitter<debug.IExpression>();
this._onDidSelectFunctionBreakpoint = new Emitter<debug.IFunctionBreakpoint>();
this._onDidSelectConfigurationName = new Emitter<string>();
this.changedWorkbenchViewState = false;
}
......@@ -28,13 +29,20 @@ export class ViewModel implements debug.IViewModel {
return 'root';
}
public getFocusedStackFrame(): debug.IStackFrame {
return this.focusedStackFrame;
public get focusedProcess(): debug.IProcess {
return this._focusedStackFrame ? this._focusedStackFrame.thread.process : null;
}
public setFocusedStackFrame(focusedStackFrame: debug.IStackFrame, focusedThread: debug.IThread): void {
this.focusedStackFrame = focusedStackFrame;
this.focusedThread = focusedThread;
public get focusedThread(): debug.IThread {
return this._focusedStackFrame ? this._focusedStackFrame.thread : null;
}
public get focusedStackFrame(): debug.IStackFrame {
return this._focusedStackFrame;
}
public setFocusedStackFrame(focusedStackFrame: debug.IStackFrame): void {
this._focusedStackFrame = focusedStackFrame;
this._onDidFocusStackFrame.fire(focusedStackFrame);
}
......@@ -42,10 +50,6 @@ export class ViewModel implements debug.IViewModel {
return this._onDidFocusStackFrame.event;
}
public getFocusedThreadId(): number {
return this.focusedThread ? this.focusedThread.threadId : 0;
}
public getSelectedExpression(): debug.IExpression {
return this.selectedExpression;
}
......@@ -71,4 +75,17 @@ export class ViewModel implements debug.IViewModel {
public get onDidSelectFunctionBreakpoint(): Event<debug.IFunctionBreakpoint> {
return this._onDidSelectFunctionBreakpoint.event;
}
public get selectedConfigurationName(): string {
return this._selectedConfigurationName;
}
public setSelectedConfigurationName(configurationName: string): void {
this._selectedConfigurationName = configurationName;
this._onDidSelectConfigurationName.fire(configurationName);
}
public get onDidSelectConfigurationName(): Event<string> {
return this._onDidSelectConfigurationName.event;
}
}
......@@ -7,7 +7,6 @@ import 'vs/css!../browser/media/debug.contribution';
import 'vs/css!../browser/media/debugHover';
import nls = require('vs/nls');
import { KeyMod, KeyCode } from 'vs/base/common/keyCodes';
import { TPromise } from 'vs/base/common/winjs.base';
import { SyncActionDescriptor } from 'vs/platform/actions/common/actions';
import platform = require('vs/platform/platform');
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
......@@ -137,19 +136,9 @@ registry.registerWorkbenchAction(new SyncActionDescriptor(FocusReplAction, Focus
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: '_workbench.startDebug',
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(0),
handler(accessor: ServicesAccessor, configuration: any) {
handler(accessor: ServicesAccessor, configurationOrName: any) {
const debugService = accessor.get(debug.IDebugService);
if (typeof configuration === 'string') {
const configurationManager = debugService.getConfigurationManager();
return configurationManager.setConfiguration(configuration)
.then(() => {
return configurationManager.configuration ? debugService.createSession(false)
: TPromise.wrapError(new Error(nls.localize('launchConfigDoesNotExist', "Launch configuration '{0}' does not exist.", configuration)));
});
}
const noDebug = configuration && !!configuration.noDebug;
return debugService.createSession(noDebug, configuration);
return debugService.createProcess(configurationOrName);
},
when: debug.CONTEXT_NOT_IN_DEBUG_MODE,
primary: undefined
......
......@@ -117,7 +117,7 @@ export class DebugEditorContribution implements debug.IDebugEditorContribution {
this.toDispose.push(this.editor.onMouseLeave((e: editorbrowser.IEditorMouseEvent) => {
this.ensureBreakpointHintDecoration(-1);
}));
this.toDispose.push(this.debugService.onDidChangeState(state => this.onDebugStateUpdate(state)));
this.toDispose.push(this.debugService.onDidChangeState(() => this.onDebugStateUpdate()));
// hover listeners & hover widget
this.toDispose.push(this.editor.onMouseDown((e: editorbrowser.IEditorMouseEvent) => this.onEditorMouseDown(e)));
......@@ -159,7 +159,8 @@ export class DebugEditorContribution implements debug.IDebugEditorContribution {
this.breakpointHintDecoration = this.editor.deltaDecorations(this.breakpointHintDecoration, newDecoration);
}
private onDebugStateUpdate(state: debug.State): void {
private onDebugStateUpdate(): void {
const state = this.debugService.state;
if (state !== debug.State.Stopped) {
this.hideHoverWidget();
}
......
......@@ -55,7 +55,7 @@ export class DebugHoverWidget implements editorbrowser.IContentWidget {
this.treeContainer = dom.append(this.complexValueContainer, $('.debug-hover-tree'));
this.treeContainer.setAttribute('role', 'tree');
this.tree = new Tree(this.treeContainer, {
dataSource: new viewer.VariablesDataSource(this.debugService),
dataSource: new viewer.VariablesDataSource(),
renderer: this.instantiationService.createInstance(VariablesHoverRenderer),
controller: new DebugHoverController(editor)
}, debugTreeOptions);
......@@ -148,19 +148,19 @@ export class DebugHoverWidget implements editorbrowser.IContentWidget {
public showAt(range: Range, hoveringOver: string, focus: boolean): TPromise<void> {
const pos = range.getStartPosition();
const focusedStackFrame = this.debugService.getViewModel().getFocusedStackFrame();
const focusedStackFrame = this.debugService.getViewModel().focusedStackFrame;
if (!hoveringOver || !focusedStackFrame || (focusedStackFrame.source.uri.toString() !== this.editor.getModel().uri.toString())) {
return;
}
const session = this.debugService.activeSession;
const process = this.debugService.getViewModel().focusedProcess;
const lineContent = this.editor.getModel().getLineContent(pos.lineNumber);
const expressionRange = this.getExactExpressionRange(lineContent, range);
// use regex to extract the sub-expression #9821
const matchingExpression = lineContent.substring(expressionRange.startColumn - 1, expressionRange.endColumn);
const evaluatedExpression = session.configuration.capabilities.supportsEvaluateForHovers ?
evaluateExpression(session, focusedStackFrame, new Expression(matchingExpression, true), 'hover') :
const evaluatedExpression = process.session.configuration.capabilities.supportsEvaluateForHovers ?
evaluateExpression(focusedStackFrame, new Expression(matchingExpression, true), 'hover') :
this.findExpressionInStackFrame(matchingExpression.split('.').map(word => word.trim()).filter(word => !!word));
return evaluatedExpression.then(expression => {
......@@ -198,7 +198,7 @@ export class DebugHoverWidget implements editorbrowser.IContentWidget {
}
private findExpressionInStackFrame(namesToFind: string[]): TPromise<debug.IExpression> {
return this.debugService.getViewModel().getFocusedStackFrame().getScopes(this.debugService)
return this.debugService.getViewModel().focusedStackFrame.getScopes()
// no expensive scopes
.then(scopes => scopes.filter(scope => !scope.expensive))
.then(scopes => TPromise.join(scopes.map(scope => this.doFindExpression(scope, namesToFind))))
......
......@@ -118,9 +118,9 @@ function renderRenameBox(debugService: debug.IDebugService, contextViewService:
} else if (element instanceof model.FunctionBreakpoint && !element.name) {
debugService.removeFunctionBreakpoints(element.getId()).done(null, errors.onUnexpectedError);
} else if (element instanceof model.Variable) {
(<model.Variable>element).errorMessage = null;
element.errorMessage = null;
if (renamed && element.value !== inputBox.value) {
debugService.setVariable(element, inputBox.value)
element.setVariable(inputBox.value)
// if everything went fine we need to refresh that tree element since his value updated
.done(() => tree.refresh(element, false), errors.onUnexpectedError);
}
......@@ -212,10 +212,14 @@ export class BaseDebugController extends treedefaults.DefaultController {
// call stack
class ThreadAndProcessIds {
constructor(public processId: string, public threadId: number) { }
}
export class CallStackController extends BaseDebugController {
protected onLeftClick(tree: tree.ITree, element: any, event: IMouseEvent): boolean {
if (typeof element === 'number') {
if (element instanceof ThreadAndProcessIds) {
return this.showMoreStackFrames(tree, element);
}
if (element instanceof model.StackFrame) {
......@@ -227,7 +231,7 @@ export class CallStackController extends BaseDebugController {
protected onEnter(tree: tree.ITree, event: IKeyboardEvent): boolean {
const element = tree.getFocus();
if (typeof element === 'number') {
if (element instanceof ThreadAndProcessIds) {
return this.showMoreStackFrames(tree, element);
}
if (element instanceof model.StackFrame) {
......@@ -266,10 +270,11 @@ export class CallStackController extends BaseDebugController {
}
// user clicked / pressed on 'Load More Stack Frames', get those stack frames and refresh the tree.
private showMoreStackFrames(tree: tree.ITree, threadId: number): boolean {
const thread = this.debugService.getModel().getThreads()[threadId];
private showMoreStackFrames(tree: tree.ITree, threadAndProcessIds: ThreadAndProcessIds): boolean {
const process = this.debugService.getModel().getProcesses().filter(p => p.getId() === threadAndProcessIds.processId).pop();
const thread = process && process.getThread(threadAndProcessIds.threadId);
if (thread) {
thread.getCallStack(this.debugService, true)
thread.getCallStack(true)
.done(() => tree.refresh(), errors.onUnexpectedError);
}
......@@ -318,7 +323,7 @@ export class CallStackActionProvider implements renderer.IActionProvider {
actions.push(this.instantiationService.createInstance(debugactions.PauseAction, debugactions.PauseAction.ID, debugactions.PauseAction.LABEL));
}
} else if (element instanceof model.StackFrame) {
const capabilities = this.debugService.activeSession.configuration.capabilities;
const capabilities = this.debugService.getViewModel().focusedProcess.session.configuration.capabilities;
if (typeof capabilities.supportsRestartFrame === 'boolean' && capabilities.supportsRestartFrame) {
actions.push(this.instantiationService.createInstance(debugactions.RestartFrameAction, debugactions.RestartFrameAction.ID, debugactions.RestartFrameAction.LABEL));
}
......@@ -334,10 +339,6 @@ export class CallStackActionProvider implements renderer.IActionProvider {
export class CallStackDataSource implements tree.IDataSource {
constructor( @debug.IDebugService private debugService: debug.IDebugService) {
// noop
}
public getId(tree: tree.ITree, element: any): string {
if (typeof element === 'number') {
return element.toString();
......@@ -350,25 +351,28 @@ export class CallStackDataSource implements tree.IDataSource {
}
public hasChildren(tree: tree.ITree, element: any): boolean {
return element instanceof model.Model || (element instanceof model.Thread && (<model.Thread>element).stopped);
return element instanceof model.Model || element instanceof model.Process || (element instanceof model.Thread && (<model.Thread>element).stopped);
}
public getChildren(tree: tree.ITree, element: any): TPromise<any> {
if (element instanceof model.Thread) {
return this.getThreadChildren(element);
}
if (element instanceof model.Model) {
return TPromise.as(element.getProcesses());
}
const threads = (<model.Model>element).getThreads();
return TPromise.as(Object.keys(threads).map(ref => threads[ref]));
const process = <debug.IProcess>element;
return TPromise.as(process.getAllThreads());
}
private getThreadChildren(thread: debug.IThread): TPromise<any> {
return thread.getCallStack(this.debugService).then((callStack: any[]) => {
return thread.getCallStack().then((callStack: any[]) => {
if (thread.stoppedDetails && thread.stoppedDetails.framesErrorMessage) {
return callStack.concat([thread.stoppedDetails.framesErrorMessage]);
}
if (thread.stoppedDetails && thread.stoppedDetails.totalFrames > callStack.length) {
return callStack.concat([thread.threadId]);
return callStack.concat([new ThreadAndProcessIds(thread.process.getId(), thread.threadId)]);
}
return callStack;
......@@ -387,6 +391,11 @@ interface IThreadTemplateData {
stateLabel: HTMLSpanElement;
}
interface IProcessTemplateData {
process: HTMLElement;
name: HTMLElement;
}
interface IErrorTemplateData {
label: HTMLElement;
}
......@@ -409,6 +418,7 @@ export class CallStackRenderer implements tree.IRenderer {
private static STACK_FRAME_TEMPLATE_ID = 'stackFrame';
private static ERROR_TEMPLATE_ID = 'error';
private static LOAD_MORE_TEMPLATE_ID = 'loadMore';
private static PROCESS_TEMPLATE_ID = 'process';
constructor( @IWorkspaceContextService private contextService: IWorkspaceContextService) {
// noop
......@@ -419,6 +429,9 @@ export class CallStackRenderer implements tree.IRenderer {
}
public getTemplateId(tree: tree.ITree, element: any): string {
if (element instanceof model.Process) {
return CallStackRenderer.PROCESS_TEMPLATE_ID;
}
if (element instanceof model.Thread) {
return CallStackRenderer.THREAD_TEMPLATE_ID;
}
......@@ -433,6 +446,14 @@ export class CallStackRenderer implements tree.IRenderer {
}
public renderTemplate(tree: tree.ITree, templateId: string, container: HTMLElement): any {
if (templateId === CallStackRenderer.PROCESS_TEMPLATE_ID) {
let data: IProcessTemplateData = Object.create(null);
data.process = dom.append(container, $('.process'));
data.name = dom.append(data.process, $('.name'));
return data;
}
if (templateId === CallStackRenderer.LOAD_MORE_TEMPLATE_ID) {
let data: ILoadMoreTemplateData = Object.create(null);
data.label = dom.append(container, $('.load-more'));
......@@ -466,17 +487,24 @@ export class CallStackRenderer implements tree.IRenderer {
}
public renderElement(tree: tree.ITree, element: any, templateId: string, templateData: any): void {
if (templateId === CallStackRenderer.THREAD_TEMPLATE_ID) {
if (templateId === CallStackRenderer.PROCESS_TEMPLATE_ID) {
this.renderProcess(element, templateData);
} else if (templateId === CallStackRenderer.THREAD_TEMPLATE_ID) {
this.renderThread(element, templateData);
} else if (templateId === CallStackRenderer.STACK_FRAME_TEMPLATE_ID) {
this.renderStackFrame(element, templateData);
} else if (templateId === CallStackRenderer.ERROR_TEMPLATE_ID) {
this.renderError(element, templateData);
} else {
} else if (templateId === CallStackRenderer.LOAD_MORE_TEMPLATE_ID) {
this.renderLoadMore(element, templateData);
}
}
private renderProcess(process: debug.IProcess, data: IProcessTemplateData): void {
data.process.title = nls.localize('process', "Process");
data.name.textContent = process.name;
}
private renderThread(thread: debug.IThread, data: IThreadTemplateData): void {
data.thread.title = nls.localize('thread', "Thread");
data.name.textContent = thread.name;
......@@ -570,10 +598,6 @@ export class VariablesActionProvider implements renderer.IActionProvider {
export class VariablesDataSource implements tree.IDataSource {
constructor(private debugService: debug.IDebugService) {
// noop
}
public getId(tree: tree.ITree, element: any): string {
return element.getId();
}
......@@ -589,12 +613,12 @@ export class VariablesDataSource implements tree.IDataSource {
public getChildren(tree: tree.ITree, element: any): TPromise<any> {
if (element instanceof viewmodel.ViewModel) {
let focusedStackFrame = (<viewmodel.ViewModel>element).getFocusedStackFrame();
return focusedStackFrame ? focusedStackFrame.getScopes(this.debugService) : TPromise.as([]);
const focusedStackFrame = (<viewmodel.ViewModel>element).focusedStackFrame;
return focusedStackFrame ? focusedStackFrame.getScopes() : TPromise.as([]);
}
let scope = <model.Scope>element;
return scope.getChildren(this.debugService);
return scope.getChildren();
}
public getParent(tree: tree.ITree, element: any): TPromise<any> {
......@@ -789,10 +813,6 @@ export class WatchExpressionsActionProvider implements renderer.IActionProvider
export class WatchExpressionsDataSource implements tree.IDataSource {
constructor(private debugService: debug.IDebugService) {
// noop
}
public getId(tree: tree.ITree, element: any): string {
return element.getId();
}
......@@ -812,7 +832,7 @@ export class WatchExpressionsDataSource implements tree.IDataSource {
}
let expression = <model.Expression>element;
return expression.getChildren(this.debugService);
return expression.getChildren();
}
public getParent(tree: tree.ITree, element: any): TPromise<any> {
......@@ -1164,10 +1184,10 @@ export class BreakpointsRenderer implements tree.IRenderer {
data.breakpoint.title = functionBreakpoint.name;
// Mark function breakpoints as disabled if deactivated or if debug type does not support them #9099
const session = this.debugService.activeSession;
if ((session && !session.configuration.capabilities.supportsFunctionBreakpoints) || !this.debugService.getModel().areBreakpointsActivated()) {
const process = this.debugService.getViewModel().focusedProcess;
if ((process && !process.session.configuration.capabilities.supportsFunctionBreakpoints) || !this.debugService.getModel().areBreakpointsActivated()) {
tree.addTraits('disabled', [functionBreakpoint]);
if (session && !session.configuration.capabilities.supportsFunctionBreakpoints) {
if (process && !process.session.configuration.capabilities.supportsFunctionBreakpoints) {
data.breakpoint.title = nls.localize('functionBreakpointsNotSupported', "Function breakpoints are not supported by this debug type");
}
} else {
......
......@@ -5,6 +5,7 @@
import nls = require('vs/nls');
import paths = require('vs/base/common/paths');
import { RunOnceScheduler } from 'vs/base/common/async';
import dom = require('vs/base/browser/dom');
import builder = require('vs/base/browser/builder');
import { TPromise } from 'vs/base/common/winjs.base';
......@@ -47,6 +48,7 @@ const $ = builder.$;
export class VariablesView extends viewlet.CollapsibleViewletView {
private static MEMENTO = 'variablesview.memento';
private onFocusStackFrameScheduler: RunOnceScheduler;
constructor(
actionRunner: actions.IActionRunner,
......@@ -59,6 +61,22 @@ export class VariablesView extends viewlet.CollapsibleViewletView {
@IInstantiationService private instantiationService: IInstantiationService
) {
super(actionRunner, !!settings[VariablesView.MEMENTO], nls.localize('variablesSection', "Variables Section"), messageService, keybindingService, contextMenuService);
// Use schedulre to prevent unnecessary flashing
this.onFocusStackFrameScheduler = new RunOnceScheduler(() => {
// Always clear tree highlight to avoid ending up in a broken state #12203
this.tree.clearHighlight();
this.tree.refresh().then(() => {
const stackFrame = this.debugService.getViewModel().focusedStackFrame;
if (stackFrame) {
return stackFrame.getScopes().then(scopes => {
if (scopes.length > 0 && !scopes[0].expensive) {
return this.tree.expand(scopes[0]);
}
});
}
}).done(null, errors.onUnexpectedError);
}, 700);
}
public renderHeader(container: HTMLElement): void {
......@@ -73,7 +91,7 @@ export class VariablesView extends viewlet.CollapsibleViewletView {
this.treeContainer = renderViewTree(container);
this.tree = new treeimpl.Tree(this.treeContainer, {
dataSource: new viewer.VariablesDataSource(this.debugService),
dataSource: new viewer.VariablesDataSource(),
renderer: this.instantiationService.createInstance(viewer.VariablesRenderer),
accessibilityProvider: new viewer.VariablesAccessibilityProvider(),
controller: new viewer.VariablesController(this.debugService, this.contextMenuService, new viewer.VariablesActionProvider(this.instantiationService))
......@@ -86,8 +104,17 @@ export class VariablesView extends viewlet.CollapsibleViewletView {
const collapseAction = this.instantiationService.createInstance(viewlet.CollapseAction, this.tree, false, 'explorer-action collapse-explorer');
this.toolBar.setActions(actionbarregistry.prepareActions([collapseAction]))();
this.toDispose.push(viewModel.onDidFocusStackFrame(sf => this.onFocusStackFrame(sf)));
this.toDispose.push(this.debugService.onDidChangeState(state => {
this.toDispose.push(viewModel.onDidFocusStackFrame(sf => {
// Only delay if the stack frames got cleared and there is no active stack frame
// Otherwise just update immediately
if (sf) {
this.onFocusStackFrameScheduler.schedule(0);
} else if (!this.onFocusStackFrameScheduler.isScheduled()) {
this.onFocusStackFrameScheduler.schedule();
}
}));
this.toDispose.push(this.debugService.onDidChangeState(() => {
const state = this.debugService.state;
collapseAction.enabled = state === debug.State.Running || state === debug.State.Stopped;
}));
......@@ -116,20 +143,6 @@ export class VariablesView extends viewlet.CollapsibleViewletView {
}));
}
private onFocusStackFrame(stackFrame: debug.IStackFrame): void {
// Always clear tree highlight to avoid ending up in a broken state #12203
this.tree.clearHighlight();
this.tree.refresh().then(() => {
if (stackFrame) {
return stackFrame.getScopes(this.debugService).then(scopes => {
if (scopes.length > 0 && !scopes[0].expensive) {
return this.tree.expand(scopes[0]);
}
});
}
}).done(null, errors.onUnexpectedError);
}
public shutdown(): void {
this.settings[VariablesView.MEMENTO] = (this.state === splitview.CollapsibleState.COLLAPSED);
super.shutdown();
......@@ -139,6 +152,8 @@ export class VariablesView extends viewlet.CollapsibleViewletView {
export class WatchExpressionsView extends viewlet.CollapsibleViewletView {
private static MEMENTO = 'watchexpressionsview.memento';
private onWatchExpressionsUpdatedScheduler: RunOnceScheduler;
private toReveal: debug.IExpression;
constructor(
actionRunner: actions.IActionRunner,
......@@ -150,12 +165,19 @@ export class WatchExpressionsView extends viewlet.CollapsibleViewletView {
@IInstantiationService private instantiationService: IInstantiationService
) {
super(actionRunner, !!settings[WatchExpressionsView.MEMENTO], nls.localize('expressionsSection', "Expressions Section"), messageService, keybindingService, contextMenuService);
this.toDispose.push(this.debugService.getModel().onDidChangeWatchExpressions(we => {
// only expand when a new watch expression is added.
if (we instanceof Expression) {
this.expand();
}
}));
this.onWatchExpressionsUpdatedScheduler = new RunOnceScheduler(() => {
this.tree.refresh().done(() => {
return this.toReveal instanceof Expression ? this.tree.reveal(this.toReveal) : TPromise.as(true);
}, errors.onUnexpectedError);
}, 250);
}
public renderHeader(container: HTMLElement): void {
......@@ -171,7 +193,7 @@ export class WatchExpressionsView extends viewlet.CollapsibleViewletView {
const actionProvider = new viewer.WatchExpressionsActionProvider(this.instantiationService);
this.tree = new treeimpl.Tree(this.treeContainer, {
dataSource: new viewer.WatchExpressionsDataSource(this.debugService),
dataSource: new viewer.WatchExpressionsDataSource(),
renderer: this.instantiationService.createInstance(viewer.WatchExpressionsRenderer, actionProvider, this.actionRunner),
accessibilityProvider: new viewer.WatchExpressionsAccessibilityProvider(),
controller: new viewer.WatchExpressionsController(this.debugService, this.contextMenuService, actionProvider)
......@@ -184,7 +206,13 @@ export class WatchExpressionsView extends viewlet.CollapsibleViewletView {
const removeAllWatchExpressionsAction = this.instantiationService.createInstance(debugactions.RemoveAllWatchExpressionsAction, debugactions.RemoveAllWatchExpressionsAction.ID, debugactions.RemoveAllWatchExpressionsAction.LABEL);
this.toolBar.setActions(actionbarregistry.prepareActions([addWatchExpressionAction, collapseAction, removeAllWatchExpressionsAction]))();
this.toDispose.push(this.debugService.getModel().onDidChangeWatchExpressions(we => this.onWatchExpressionsUpdated(we)));
this.toDispose.push(this.debugService.getModel().onDidChangeWatchExpressions(we => {
if (!this.onWatchExpressionsUpdatedScheduler.isScheduled()) {
this.onWatchExpressionsUpdatedScheduler.schedule();
}
this.toReveal = we;
}));
this.toDispose.push(this.debugService.getViewModel().onDidSelectExpression(expression => {
if (!expression || !(expression instanceof Expression)) {
return;
......@@ -201,12 +229,6 @@ export class WatchExpressionsView extends viewlet.CollapsibleViewletView {
}));
}
private onWatchExpressionsUpdated(expression: debug.IExpression): void {
this.tree.refresh().done(() => {
return expression instanceof Expression ? this.tree.reveal(expression) : TPromise.as(true);
}, errors.onUnexpectedError);
}
public shutdown(): void {
this.settings[WatchExpressionsView.MEMENTO] = (this.state === splitview.CollapsibleState.COLLAPSED);
super.shutdown();
......@@ -218,6 +240,8 @@ export class CallStackView extends viewlet.CollapsibleViewletView {
private static MEMENTO = 'callstackview.memento';
private pauseMessage: builder.Builder;
private pauseMessageLabel: builder.Builder;
private onCallStackChangeScheduler: RunOnceScheduler;
private onStackFrameFocusScheduler: RunOnceScheduler;
constructor(
actionRunner: actions.IActionRunner,
......@@ -230,6 +254,49 @@ export class CallStackView extends viewlet.CollapsibleViewletView {
@IInstantiationService private instantiationService: IInstantiationService
) {
super(actionRunner, !!settings[CallStackView.MEMENTO], nls.localize('callstackSection', "Call Stack Section"), messageService, keybindingService, contextMenuService);
// Create schedulers to prevent unnecessary flashing of tree when reacting to changes
this.onStackFrameFocusScheduler = new RunOnceScheduler(() => {
const stackFrame = this.debugService.getViewModel().focusedStackFrame;
if (!stackFrame) {
this.pauseMessage.hide();
return;
}
const thread = stackFrame.thread;
this.tree.expandAll([thread.process, thread]).done(() => {
const focusedStackFrame = this.debugService.getViewModel().focusedStackFrame;
this.tree.setSelection([focusedStackFrame]);
if (thread.stoppedDetails && thread.stoppedDetails.reason) {
this.pauseMessageLabel.text(nls.localize('debugStopped', "Paused on {0}", thread.stoppedDetails.reason));
if (thread.stoppedDetails.text) {
this.pauseMessageLabel.title(thread.stoppedDetails.text);
}
thread.stoppedDetails.reason === 'exception' ? this.pauseMessageLabel.addClass('exception') : this.pauseMessageLabel.removeClass('exception');
this.pauseMessage.show();
} else {
this.pauseMessage.hide();
}
return this.tree.reveal(focusedStackFrame);
}, errors.onUnexpectedError);
}, 100);
this.onCallStackChangeScheduler = new RunOnceScheduler(() => {
let newTreeInput: any = this.debugService.getModel();
const processes = this.debugService.getModel().getProcesses();
if (processes.length === 1) {
const threads = processes[0].getAllThreads();
// Only show the threads in the call stack if there is more than 1 thread.
newTreeInput = threads.length === 1 ? threads[0] : processes[0];
}
if (this.tree.getInput() === newTreeInput) {
this.tree.refresh().done(null, errors.onUnexpectedError);
} else {
this.tree.setInput(newTreeInput).done(null, errors.onUnexpectedError);
}
}, 50);
}
public renderHeader(container: HTMLElement): void {
......@@ -263,42 +330,15 @@ export class CallStackView extends viewlet.CollapsibleViewletView {
}
}));
const model = this.debugService.getModel();
this.toDispose.push(this.debugService.getViewModel().onDidFocusStackFrame(() => {
const focussedThread = model.getThreads()[this.debugService.getViewModel().getFocusedThreadId()];
if (!focussedThread) {
this.pauseMessage.hide();
return;
if (!this.onStackFrameFocusScheduler.isScheduled()) {
this.onStackFrameFocusScheduler.schedule();
}
return this.tree.expand(focussedThread).then(() => {
const focusedStackFrame = this.debugService.getViewModel().getFocusedStackFrame();
this.tree.setSelection([focusedStackFrame]);
if (focussedThread.stoppedDetails && focussedThread.stoppedDetails.reason) {
this.pauseMessageLabel.text(nls.localize('debugStopped', "Paused on {0}", focussedThread.stoppedDetails.reason));
if (focussedThread.stoppedDetails.text) {
this.pauseMessageLabel.title(focussedThread.stoppedDetails.text);
}
focussedThread.stoppedDetails.reason === 'exception' ? this.pauseMessageLabel.addClass('exception') : this.pauseMessageLabel.removeClass('exception');
this.pauseMessage.show();
} else {
this.pauseMessage.hide();
}
return this.tree.reveal(focusedStackFrame);
});
}));
this.toDispose.push(model.onDidChangeCallStack(() => {
const threads = model.getThreads();
const threadsArray = Object.keys(threads).map(ref => threads[ref]);
// Only show the threads in the call stack if there is more than 1 thread.
const newTreeInput = threadsArray.length === 1 ? threadsArray[0] : model;
if (this.tree.getInput() === newTreeInput) {
this.tree.refresh().done(null, errors.onUnexpectedError);
} else {
this.tree.setInput(newTreeInput).done(null, errors.onUnexpectedError);
this.toDispose.push(this.debugService.getModel().onDidChangeCallStack(() => {
if (!this.onCallStackChangeScheduler.isScheduled()) {
this.onCallStackChangeScheduler.schedule();
}
}));
}
......
......@@ -20,9 +20,9 @@ export class CopyValueAction extends actions.Action {
public run(): TPromise<any> {
if (this.value instanceof Variable) {
const frameId = this.debugService.getViewModel().getFocusedStackFrame().frameId;
const session = this.debugService.activeSession;
return session.evaluate({ expression: getFullExpressionName(this.value, session.configuration.type), frameId }).then(result => {
const frameId = this.debugService.getViewModel().focusedStackFrame.frameId;
const process = this.debugService.getViewModel().focusedProcess;
return process.session.evaluate({ expression: getFullExpressionName(this.value, process.session.configuration.type), frameId }).then(result => {
clipboard.writeText(result.body.result);
}, err => clipboard.writeText(this.value.value));
}
......
......@@ -27,7 +27,6 @@ import { ExtensionsChannelId } from 'vs/platform/extensionManagement/common/exte
import { TerminalSupport } from 'vs/workbench/parts/debug/electron-browser/terminalSupport';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { shell } from 'electron';
export interface SessionExitedEvent extends DebugProtocol.ExitedEvent {
......@@ -44,7 +43,7 @@ export interface SessionTerminatedEvent extends DebugProtocol.TerminatedEvent {
};
}
export class RawDebugSession extends v8.V8Protocol implements debug.IRawDebugSession {
export class RawDebugSession extends v8.V8Protocol implements debug.ISession {
public restarted: boolean;
public emittedStopped: boolean;
......@@ -54,7 +53,8 @@ export class RawDebugSession extends v8.V8Protocol implements debug.IRawDebugSes
private socket: net.Socket = null;
private cachedInitServer: TPromise<void>;
private startTime: number;
private stopServerPending: boolean;
public requestType: debug.SessionRequestType;
public disconnected: boolean;
private sentPromises: TPromise<DebugProtocol.Response>[];
private capabilities: DebugProtocol.Capabilities;
......@@ -69,6 +69,7 @@ export class RawDebugSession extends v8.V8Protocol implements debug.IRawDebugSes
private _onDidEvent: Emitter<DebugProtocol.Event>;
constructor(
id: string,
private debugServerPort: number,
private adapter: Adapter,
private customTelemetryService: ITelemetryService,
......@@ -79,7 +80,7 @@ export class RawDebugSession extends v8.V8Protocol implements debug.IRawDebugSes
@IExternalTerminalService private nativeTerminalService: IExternalTerminalService,
@IConfigurationService private configurationService: IConfigurationService
) {
super();
super(id);
this.emittedStopped = false;
this.readyForBreakpoints = false;
this.sentPromises = [];
......@@ -240,31 +241,48 @@ export class RawDebugSession extends v8.V8Protocol implements debug.IRawDebugSes
}
public launch(args: DebugProtocol.LaunchRequestArguments): TPromise<DebugProtocol.LaunchResponse> {
this.requestType = args.noDebug ? debug.SessionRequestType.LAUNCH_NO_DEBUG : debug.SessionRequestType.LAUNCH;
return this.send('launch', args).then(response => this.readCapabilities(response));
}
public attach(args: DebugProtocol.AttachRequestArguments): TPromise<DebugProtocol.AttachResponse> {
this.requestType = debug.SessionRequestType.ATTACH;
return this.send('attach', args).then(response => this.readCapabilities(response));
}
public next(args: DebugProtocol.NextArguments): TPromise<DebugProtocol.NextResponse> {
return this.send('next', args);
return this.send('next', args).then(response => {
this.fireFakeContinued(args.threadId);
return response;
});
}
public stepIn(args: DebugProtocol.StepInArguments): TPromise<DebugProtocol.StepInResponse> {
return this.send('stepIn', args);
return this.send('stepIn', args).then(response => {
this.fireFakeContinued(args.threadId);
return response;
});
}
public stepOut(args: DebugProtocol.StepOutArguments): TPromise<DebugProtocol.StepOutResponse> {
return this.send('stepOut', args);
return this.send('stepOut', args).then(response => {
this.fireFakeContinued(args.threadId);
return response;
});
}
public stepBack(args: DebugProtocol.StepBackArguments): TPromise<DebugProtocol.StepBackResponse> {
return this.send('stepBack', args);
return this.send('stepBack', args).then(response => {
this.fireFakeContinued(args.threadId);
return response;
});
}
public continue(args: DebugProtocol.ContinueArguments): TPromise<DebugProtocol.ContinueResponse> {
return this.send('continue', args);
return this.send('continue', args).then(response => {
this.fireFakeContinued(args.threadId);
return response;
});
}
public pause(args: DebugProtocol.PauseArguments): TPromise<DebugProtocol.PauseResponse> {
......@@ -284,7 +302,7 @@ export class RawDebugSession extends v8.V8Protocol implements debug.IRawDebugSes
}
public disconnect(restart = false, force = false): TPromise<DebugProtocol.DisconnectResponse> {
if (this.stopServerPending && force) {
if (this.disconnected && force) {
return this.stopServer();
}
......@@ -295,9 +313,9 @@ export class RawDebugSession extends v8.V8Protocol implements debug.IRawDebugSes
this.sentPromises = [];
}, 1000);
if ((this.serverProcess || this.socket) && !this.stopServerPending) {
if ((this.serverProcess || this.socket) && !this.disconnected) {
// point of no return: from now on don't report any errors
this.stopServerPending = true;
this.disconnected = true;
this.restarted = restart;
return this.send('disconnect', { restart: restart }, false).then(() => this.stopServer(), () => this.stopServer());
}
......@@ -367,6 +385,17 @@ export class RawDebugSession extends v8.V8Protocol implements debug.IRawDebugSes
}
}
private fireFakeContinued(threadId: number): void {
this._onDidContinued.fire({
type: 'event',
event: 'continued',
body: {
threadId
},
seq: undefined
});
}
private connectServer(port: number): TPromise<void> {
return new TPromise<void>((c, e) => {
this.socket = net.createConnection(port, '127.0.0.1', () => {
......@@ -436,7 +465,7 @@ export class RawDebugSession extends v8.V8Protocol implements debug.IRawDebugSes
return TPromise.as(null);
}
this.stopServerPending = true;
this.disconnected = true;
let ret: TPromise<void>;
// when killing a process in windows its child
......@@ -492,7 +521,7 @@ export class RawDebugSession extends v8.V8Protocol implements debug.IRawDebugSes
private onServerExit(): void {
this.serverProcess = null;
this.cachedInitServer = null;
if (!this.stopServerPending) {
if (!this.disconnected) {
this.messageService.show(severity.Error, nls.localize('debugAdapterCrash', "Debug adapter process has terminated unexpectedly"));
}
this.onEvent({ event: 'exit', type: 'event', seq: 0 });
......
......@@ -183,7 +183,9 @@ export class Repl extends Panel implements IPrivateReplService {
provideCompletionItems: (model: IReadOnlyModel, position: Position, token: CancellationToken): Thenable<modes.ISuggestResult> => {
const word = this.replInput.getModel().getWordAtPosition(position);
const text = this.replInput.getModel().getLineContent(position.lineNumber);
return wireCancellationToken(token, this.debugService.completions(text, position).then(suggestions => ({
const focusedStackFrame = this.debugService.getViewModel().focusedStackFrame;
const completions = focusedStackFrame ? focusedStackFrame.completions(text, position) : TPromise.as([]);
return wireCancellationToken(token, completions.then(suggestions => ({
currentWord: word ? word.word : '',
suggestions
})));
......
......@@ -130,7 +130,7 @@ export class Adapter {
}
const properties = attributes.properties;
properties.type = {
enum: [this.type],
enum: [this.type, 'composite'],
description: nls.localize('debugType', "Type of configuration.")
};
properties.name = {
......@@ -142,6 +142,11 @@ export class Adapter {
enum: [request],
description: nls.localize('debugRequest', "Request type of configuration. Can be \"launch\" or \"attach\"."),
};
properties.configurationNames = {
type: 'array',
default: [],
description: nls.localize('debugConfigurationNames', "Configurations that will be launched as part of this \"composite\" configuration. Only respected if type of this configuration is \"composite\".")
};
properties.preLaunchTask = {
type: ['string', 'null'],
default: null,
......
......@@ -167,13 +167,11 @@ const jsonRegistry = <jsonContributionRegistry.IJSONContributionRegistry>platfor
jsonRegistry.registerSchema(schemaId, schema);
export class ConfigurationManager implements debug.IConfigurationManager {
public configuration: debug.IConfig;
private adapters: Adapter[];
private allModeIdsForBreakpoints: { [key: string]: boolean };
private _onDidConfigurationChange: Emitter<debug.IConfig>;
constructor(
configName: string,
@IWorkspaceContextService private contextService: IWorkspaceContextService,
@IFileService private fileService: IFileService,
@ITelemetryService private telemetryService: ITelemetryService,
......@@ -184,7 +182,6 @@ export class ConfigurationManager implements debug.IConfigurationManager {
@IInstantiationService private instantiationService: IInstantiationService
) {
this._onDidConfigurationChange = new Emitter<debug.IConfig>();
this.setConfiguration(configName);
this.adapters = [];
this.registerListeners();
this.allModeIdsForBreakpoints = {};
......@@ -250,60 +247,58 @@ export class ConfigurationManager implements debug.IConfigurationManager {
return this._onDidConfigurationChange.event;
}
public get configurationName(): string {
return this.configuration ? this.configuration.name : null;
public getAdapter(type: string): Adapter {
return this.adapters.filter(adapter => strings.equalsIgnoreCase(adapter.type, type)).pop();
}
public get adapter(): Adapter {
if (!this.configuration || !this.configuration.type) {
return null;
}
return this.adapters.filter(adapter => strings.equalsIgnoreCase(adapter.type, this.configuration.type)).pop();
}
public setConfiguration(nameOrConfig: string | debug.IConfig): TPromise<void> {
public getConfiguration(nameOrConfig: string | debug.IConfig): TPromise<debug.IConfig> {
return this.loadLaunchConfig().then(config => {
let result: debug.IConfig = null;
if (types.isObject(nameOrConfig)) {
this.configuration = objects.deepClone(nameOrConfig) as debug.IConfig;
result = objects.deepClone(nameOrConfig) as debug.IConfig;
} else {
if (!config || !config.configurations) {
this.configuration = null;
return;
return result;
}
// if the configuration name is not set yet, take the first launch config (can happen if debug viewlet has not been opened yet).
const filtered = nameOrConfig ? config.configurations.filter(cfg => cfg.name === nameOrConfig) : [config.configurations[0]];
this.configuration = filtered.length === 1 ? objects.deepClone(filtered[0]) : null;
if (config && this.configuration) {
this.configuration.debugServer = config.debugServer;
result = filtered.length === 1 ? objects.deepClone(filtered[0]) : null;
if (config && result) {
result.debugServer = config.debugServer;
}
}
if (this.configuration) {
if (result) {
// Set operating system specific properties #1873
if (isWindows && this.configuration.windows) {
Object.keys(this.configuration.windows).forEach(key => {
this.configuration[key] = this.configuration.windows[key];
if (isWindows && result.windows) {
Object.keys(result.windows).forEach(key => {
result[key] = result.windows[key];
});
}
if (isMacintosh && this.configuration.osx) {
Object.keys(this.configuration.osx).forEach(key => {
this.configuration[key] = this.configuration.osx[key];
if (isMacintosh && result.osx) {
Object.keys(result.osx).forEach(key => {
result[key] = result.osx[key];
});
}
if (isLinux && this.configuration.linux) {
Object.keys(this.configuration.linux).forEach(key => {
this.configuration[key] = this.configuration.linux[key];
if (isLinux && result.linux) {
Object.keys(result.linux).forEach(key => {
result[key] = result.linux[key];
});
}
// massage configuration attributes - append workspace path to relatvie paths, substitute variables in paths.
Object.keys(this.configuration).forEach(key => {
this.configuration[key] = this.configurationResolverService.resolveAny(this.configuration[key]);
Object.keys(result).forEach(key => {
result[key] = this.configurationResolverService.resolveAny(result[key]);
});
const adapter = this.getAdapter(result.type);
return this.configurationResolverService.resolveInteractiveVariables(result, adapter ? adapter.variables : null);
}
}).then(() => this._onDidConfigurationChange.fire(this.configuration));
}).then(result => {
this._onDidConfigurationChange.fire(result);
return result;
});
}
public openConfigFile(sideBySide: boolean): TPromise<boolean> {
......@@ -356,8 +351,4 @@ export class ConfigurationManager implements debug.IConfigurationManager {
public loadLaunchConfig(): TPromise<debug.IGlobalConfig> {
return TPromise.as(this.configurationService.getConfiguration<debug.IGlobalConfig>('launch'));
}
public resolveInteractiveVariables(): TPromise<any> {
return this.configurationResolverService.resolveInteractiveVariables(this.configuration, this.adapter ? this.adapter.variables : null);
}
}
......@@ -4,7 +4,6 @@
*--------------------------------------------------------------------------------------------*/
import stream = require('stream');
import uuid = require('vs/base/common/uuid');
import { TPromise } from 'vs/base/common/winjs.base';
import { canceled } from 'vs/base/common/errors';
......@@ -16,15 +15,13 @@ export abstract class V8Protocol {
private sequence: number;
private pendingRequests: { [id: number]: (e: DebugProtocol.Response) => void; };
private rawData: Buffer;
private id: string;
private contentLength: number;
constructor() {
constructor(private id: string) {
this.sequence = 1;
this.contentLength = -1;
this.pendingRequests = {};
this.rawData = new Buffer(0);
this.id = uuid.generateUuid();
}
public getId(): string {
......
......@@ -5,13 +5,14 @@
import assert = require('assert');
import { ViewModel } from 'vs/workbench/parts/debug/common/debugViewModel';
import { StackFrame, Expression, Thread } from 'vs/workbench/parts/debug/common/debugModel';
import { StackFrame, Expression, Thread, Process } from 'vs/workbench/parts/debug/common/debugModel';
import { MockSession } from 'vs/workbench/parts/debug/test/common/mockDebug';
suite('Debug - View Model', () => {
var model: ViewModel;
setup(() => {
model = new ViewModel();
model = new ViewModel('mockconfiguration');
});
teardown(() => {
......@@ -19,13 +20,16 @@ suite('Debug - View Model', () => {
});
test('focused stack frame', () => {
assert.equal(model.getFocusedStackFrame(), null);
assert.equal(model.getFocusedThreadId(), 0);
const frame = new StackFrame(1, 1, null, 'app.js', 1, 1);
model.setFocusedStackFrame(frame, new Thread('myThread', 1));
assert.equal(model.getFocusedStackFrame(), frame);
assert.equal(model.getFocusedThreadId(), 1);
assert.equal(model.focusedStackFrame, null);
assert.equal(model.focusedThread, null);
const mockSession = new MockSession();
const process = new Process('mockProcess', mockSession);
const thread = new Thread(process, 'myThread', 1);
const frame = new StackFrame(thread, 1, null, 'app.js', 1, 1);
model.setFocusedStackFrame(frame);
assert.equal(model.focusedStackFrame, frame);
assert.equal(model.focusedThread.threadId, 1);
});
test('selected expression', () => {
......
......@@ -12,18 +12,13 @@ import debug = require('vs/workbench/parts/debug/common/debug');
import { Source } from 'vs/workbench/parts/debug/common/debugSource';
export class MockDebugService implements debug.IDebugService {
private session: MockRawSession;
public _serviceBrand: any;
constructor() {
this.session = new MockRawSession();
}
public get state(): debug.State {
return null;
}
public get onDidChangeState(): Event<debug.State> {
public get onDidChangeState(): Event<void> {
return null;
}
......@@ -81,18 +76,14 @@ export class MockDebugService implements debug.IDebugService {
public removeWatchExpressions(id?: string): void { }
public createSession(noDebug: boolean): TPromise<any> {
public createProcess(configurationOrName: debug.IConfig | string): TPromise<any> {
return TPromise.as(null);
}
public restartSession(): TPromise<any> {
public restartProcess(): TPromise<any> {
return TPromise.as(null);
}
public get activeSession(): debug.IRawDebugSession {
return this.session;
}
public getModel(): debug.IModel {
return null;
}
......@@ -142,8 +133,45 @@ export class MockDebugService implements debug.IDebugService {
}
}
export class MockSession implements debug.ISession {
public readyForBreakpoints = true;
public emittedStopped = true;
public getId() {
return 'mockrawsession';
}
class MockRawSession implements debug.IRawDebugSession {
public get requestType() {
return debug.SessionRequestType.LAUNCH;
}
public getLengthInSeconds(): number {
return 100;
}
public stackTrace(args: DebugProtocol.StackTraceArguments): TPromise<DebugProtocol.StackTraceResponse> {
return TPromise.as({
body: {
stackFrames: []
}
});
}
public attach(args: DebugProtocol.AttachRequestArguments): TPromise<DebugProtocol.AttachResponse> {
return TPromise.as(null);
}
public scopes(args: DebugProtocol.ScopesArguments): TPromise<DebugProtocol.ScopesResponse> {
return TPromise.as(null);
}
public variables(args: DebugProtocol.VariablesArguments): TPromise<DebugProtocol.VariablesResponse> {
return TPromise.as(null);
}
evaluate(args: DebugProtocol.EvaluateArguments): TPromise<DebugProtocol.EvaluateResponse> {
return TPromise.as(null);
}
public get configuration(): { type: string, capabilities: DebugProtocol.Capabilities } {
return {
......@@ -164,23 +192,61 @@ class MockRawSession implements debug.IRawDebugSession {
return TPromise.as(null);
}
public stackTrace(args: DebugProtocol.StackTraceArguments): TPromise<DebugProtocol.StackTraceResponse> {
return TPromise.as({
body: {
stackFrames: []
}
});
public threads(): TPromise<DebugProtocol.ThreadsResponse> {
return TPromise.as(null);
}
public scopes(args: DebugProtocol.ScopesArguments): TPromise<DebugProtocol.ScopesResponse> {
public stepIn(args: DebugProtocol.StepInArguments): TPromise<DebugProtocol.StepInResponse> {
return TPromise.as(null);
}
public variables(args: DebugProtocol.VariablesArguments): TPromise<DebugProtocol.VariablesResponse> {
public stepOut(args: DebugProtocol.StepOutArguments): TPromise<DebugProtocol.StepOutResponse> {
return TPromise.as(null);
}
evaluate(args: DebugProtocol.EvaluateArguments): TPromise<DebugProtocol.EvaluateResponse> {
public stepBack(args: DebugProtocol.StepBackArguments): TPromise<DebugProtocol.StepBackResponse> {
return TPromise.as(null);
}
public continue(args: DebugProtocol.ContinueArguments): TPromise<DebugProtocol.ContinueResponse> {
return TPromise.as(null);
}
public pause(args: DebugProtocol.PauseArguments): TPromise<DebugProtocol.PauseResponse> {
return TPromise.as(null);
}
public setVariable(args: DebugProtocol.SetVariableArguments): TPromise<DebugProtocol.SetVariableResponse> {
return TPromise.as(null);
}
public restartFrame(args: DebugProtocol.RestartFrameArguments): TPromise<DebugProtocol.RestartFrameResponse> {
return TPromise.as(null);
}
public completions(args: DebugProtocol.CompletionsArguments): TPromise<DebugProtocol.CompletionsResponse> {
return TPromise.as(null);
}
public next(args: DebugProtocol.NextArguments): TPromise<DebugProtocol.NextResponse> {
return TPromise.as(null);
}
public source(args: DebugProtocol.SourceArguments): TPromise<DebugProtocol.SourceResponse> {
return TPromise.as(null);
}
public setBreakpoints(args: DebugProtocol.SetBreakpointsArguments): TPromise<DebugProtocol.SetBreakpointsResponse> {
return TPromise.as(null);
}
public setFunctionBreakpoints(args: DebugProtocol.SetFunctionBreakpointsArguments): TPromise<DebugProtocol.SetFunctionBreakpointsResponse> {
return TPromise.as(null);
}
public setExceptionBreakpoints(args: DebugProtocol.SetExceptionBreakpointsArguments): TPromise<DebugProtocol.SetExceptionBreakpointsResponse> {
return TPromise.as(null);
}
public onDidStop: Event<DebugProtocol.StoppedEvent> = null;
}
......@@ -8,13 +8,15 @@ import uri from 'vs/base/common/uri';
import severity from 'vs/base/common/severity';
import debugmodel = require('vs/workbench/parts/debug/common/debugModel');
import * as sinon from 'sinon';
import { MockDebugService } from 'vs/workbench/parts/debug/test/common/mockDebugService';
import { MockSession } from 'vs/workbench/parts/debug/test/common/mockDebug';
suite('Debug - Model', () => {
var model: debugmodel.Model;
let model: debugmodel.Model;
let rawSession: MockSession;
setup(() => {
model = new debugmodel.Model([], true, [], [], []);
rawSession = new MockSession();
});
teardown(() => {
......@@ -78,24 +80,26 @@ suite('Debug - Model', () => {
test('threads simple', () => {
var threadId = 1;
var threadName = 'firstThread';
model.addProcess('mockProcess', rawSession);
model.rawUpdate({
sessionId: rawSession.getId(),
threadId: threadId,
thread: {
id: threadId,
name: threadName
}
});
const process = model.getProcesses().filter(p => p.getId() === rawSession.getId()).pop();
var threads = model.getThreads();
assert.equal(threads[threadId].name, threadName);
assert.equal(process.getThread(threadId).name, threadName);
model.clearThreads(true);
assert.equal(model.getThreads[threadId], null);
model.clearThreads(process.getId(), true);
assert.equal(process.getThread(threadId), null);
});
test('threads multiple wtih allThreadsStopped', () => {
const mockDebugService = new MockDebugService();
const sessionStub = sinon.spy(mockDebugService.activeSession, 'stackTrace');
const sessionStub = sinon.spy(rawSession, 'stackTrace');
const threadId1 = 1;
const threadName1 = 'firstThread';
......@@ -104,7 +108,9 @@ suite('Debug - Model', () => {
const stoppedReason = 'breakpoint';
// Add the threads
model.addProcess('mockProcess', rawSession);
model.rawUpdate({
sessionId: rawSession.getId(),
threadId: threadId1,
thread: {
id: threadId1,
......@@ -113,6 +119,7 @@ suite('Debug - Model', () => {
});
model.rawUpdate({
sessionId: rawSession.getId(),
threadId: threadId2,
thread: {
id: threadId2,
......@@ -122,6 +129,7 @@ suite('Debug - Model', () => {
// Stopped event with all threads stopped
model.rawUpdate({
sessionId: rawSession.getId(),
threadId: threadId1,
stoppedDetails: {
reason: stoppedReason,
......@@ -129,11 +137,13 @@ suite('Debug - Model', () => {
},
allThreadsStopped: true
});
const process = model.getProcesses().filter(p => p.getId() === rawSession.getId()).pop();
const thread1 = model.getThreads()[threadId1];
const thread2 = model.getThreads()[threadId2];
const thread1 = process.getThread(threadId1);
const thread2 = process.getThread(threadId2);
// at the beginning, callstacks are obtainable but not available
assert.equal(process.getAllThreads().length, 2);
assert.equal(thread1.name, threadName1);
assert.equal(thread1.stopped, true);
assert.equal(thread1.getCachedCallStack(), undefined);
......@@ -145,13 +155,13 @@ suite('Debug - Model', () => {
// after calling getCallStack, the callstack becomes available
// and results in a request for the callstack in the debug adapter
thread1.getCallStack(mockDebugService).then(() => {
thread1.getCallStack().then(() => {
assert.notEqual(thread1.getCachedCallStack(), undefined);
assert.equal(thread2.getCachedCallStack(), undefined);
assert.equal(sessionStub.callCount, 1);
});
thread2.getCallStack(mockDebugService).then(() => {
thread2.getCallStack().then(() => {
assert.notEqual(thread1.getCachedCallStack(), undefined);
assert.notEqual(thread2.getCachedCallStack(), undefined);
assert.equal(sessionStub.callCount, 2);
......@@ -159,8 +169,8 @@ suite('Debug - Model', () => {
// calling multiple times getCallStack doesn't result in multiple calls
// to the debug adapter
thread1.getCallStack(mockDebugService).then(() => {
return thread2.getCallStack(mockDebugService);
thread1.getCallStack().then(() => {
return thread2.getCallStack();
}).then(() => {
assert.equal(sessionStub.callCount, 2);
});
......@@ -174,23 +184,24 @@ suite('Debug - Model', () => {
assert.equal(thread2.stopped, true);
assert.equal(thread2.getCachedCallStack(), undefined);
model.clearThreads(true);
assert.equal(model.getThreads[threadId1], null);
assert.equal(model.getThreads[threadId2], null);
model.clearThreads(process.getId(), true);
assert.equal(process.getThread(threadId1), null);
assert.equal(process.getThread(threadId2), null);
assert.equal(process.getAllThreads().length, 0);
});
test('threads mutltiple without allThreadsStopped', () => {
const mockDebugService = new MockDebugService();
const sessionStub = sinon.spy(mockDebugService.activeSession, 'stackTrace');
const sessionStub = sinon.spy(rawSession, 'stackTrace');
const stoppedThreadId = 1;
const stoppedThreadName = 'stoppedThread';
const runningThreadId = 2;
const runningThreadName = 'runningThread';
const stoppedReason = 'breakpoint';
model.addProcess('mockProcess', rawSession);
// Add the threads
model.rawUpdate({
sessionId: rawSession.getId(),
threadId: stoppedThreadId,
thread: {
id: stoppedThreadId,
......@@ -199,6 +210,7 @@ suite('Debug - Model', () => {
});
model.rawUpdate({
sessionId: rawSession.getId(),
threadId: runningThreadId,
thread: {
id: runningThreadId,
......@@ -208,6 +220,7 @@ suite('Debug - Model', () => {
// Stopped event with only one thread stopped
model.rawUpdate({
sessionId: rawSession.getId(),
threadId: stoppedThreadId,
stoppedDetails: {
reason: stoppedReason,
......@@ -215,14 +228,16 @@ suite('Debug - Model', () => {
},
allThreadsStopped: false
});
const process = model.getProcesses().filter(p => p.getId() === rawSession.getId()).pop();
const stoppedThread = model.getThreads()[stoppedThreadId];
const runningThread = model.getThreads()[runningThreadId];
const stoppedThread = process.getThread(stoppedThreadId);
const runningThread = process.getThread(runningThreadId);
// the callstack for the stopped thread is obtainable but not available
// the callstack for the running thread is not obtainable nor available
assert.equal(stoppedThread.name, stoppedThreadName);
assert.equal(stoppedThread.stopped, true);
assert.equal(process.getAllThreads().length, 2);
assert.equal(stoppedThread.getCachedCallStack(), undefined);
assert.equal(stoppedThread.stoppedDetails.reason, stoppedReason);
assert.equal(runningThread.name, runningThreadName);
......@@ -232,7 +247,7 @@ suite('Debug - Model', () => {
// after calling getCallStack, the callstack becomes available
// and results in a request for the callstack in the debug adapter
stoppedThread.getCallStack(mockDebugService).then(() => {
stoppedThread.getCallStack().then(() => {
assert.notEqual(stoppedThread.getCachedCallStack(), undefined);
assert.equal(runningThread.getCachedCallStack(), undefined);
assert.equal(sessionStub.callCount, 1);
......@@ -241,14 +256,14 @@ suite('Debug - Model', () => {
// calling getCallStack on the running thread returns empty array
// and does not return in a request for the callstack in the debug
// adapter
runningThread.getCallStack(mockDebugService).then(callStack => {
runningThread.getCallStack().then(callStack => {
assert.deepEqual(callStack, []);
assert.equal(sessionStub.callCount, 1);
});
// calling multiple times getCallStack doesn't result in multiple calls
// to the debug adapter
stoppedThread.getCallStack(mockDebugService).then(() => {
stoppedThread.getCallStack().then(() => {
assert.equal(sessionStub.callCount, 1);
});
......@@ -257,9 +272,10 @@ suite('Debug - Model', () => {
assert.equal(stoppedThread.stopped, true);
assert.equal(stoppedThread.getCachedCallStack(), undefined);
model.clearThreads(true);
assert.equal(model.getThreads[stoppedThreadId], null);
assert.equal(model.getThreads[runningThreadId], null);
model.clearThreads(process.getId(), true);
assert.equal(process.getThread(stoppedThreadId), null);
assert.equal(process.getThread(runningThreadId), null);
assert.equal(process.getAllThreads().length, 0 );
});
// Expressions
......@@ -275,14 +291,16 @@ suite('Debug - Model', () => {
test('watch expressions', () => {
assert.equal(model.getWatchExpressions().length, 0);
const stackFrame = new debugmodel.StackFrame(1, 1, null, 'app.js', 1, 1);
model.addWatchExpression(null, stackFrame, 'console').done();
model.addWatchExpression(null, stackFrame, 'console').done();
const process = new debugmodel.Process('mockProcess', rawSession);
const thread = new debugmodel.Thread(process, 'mockthread', 1);
const stackFrame = new debugmodel.StackFrame(thread, 1, null, 'app.js', 1, 1);
model.addWatchExpression(stackFrame, 'console').done();
model.addWatchExpression(stackFrame, 'console').done();
const watchExpressions = model.getWatchExpressions();
assertWatchExpressions(watchExpressions, 'console');
model.renameWatchExpression(null, stackFrame, watchExpressions[0].getId(), 'new_name').done();
model.renameWatchExpression(null, stackFrame, watchExpressions[1].getId(), 'new_name').done();
model.renameWatchExpression(stackFrame, watchExpressions[0].getId(), 'new_name').done();
model.renameWatchExpression(stackFrame, watchExpressions[1].getId(), 'new_name').done();
assertWatchExpressions(model.getWatchExpressions(), 'new_name');
model.clearWatchExpressionValues();
......@@ -294,10 +312,12 @@ suite('Debug - Model', () => {
test('repl expressions', () => {
assert.equal(model.getReplElements().length, 0);
const stackFrame = new debugmodel.StackFrame(1, 1, null, 'app.js', 1, 1);
model.addReplExpression(null, stackFrame, 'myVariable').done();
model.addReplExpression(null, stackFrame, 'myVariable').done();
model.addReplExpression(null, stackFrame, 'myVariable').done();
const process = new debugmodel.Process('mockProcess', rawSession);
const thread = new debugmodel.Thread(process, 'mockthread', 1);
const stackFrame = new debugmodel.StackFrame(thread, 1, null, 'app.js', 1, 1);
model.addReplExpression(stackFrame, 'myVariable').done();
model.addReplExpression(stackFrame, 'myVariable').done();
model.addReplExpression(stackFrame, 'myVariable').done();
assert.equal(model.getReplElements().length, 3);
model.getReplElements().forEach(re => {
......@@ -352,11 +372,14 @@ suite('Debug - Model', () => {
assert.equal(debugmodel.getFullExpressionName(new debugmodel.Expression(null, false), type), null);
assert.equal(debugmodel.getFullExpressionName(new debugmodel.Expression('son', false), type), 'son');
const scope = new debugmodel.Scope(1, 'myscope', 1, false, 1, 0);
const son = new debugmodel.Variable(new debugmodel.Variable(new debugmodel.Variable(scope, 0, 'grandfather', '75', 1, 0), 0, 'father', '45', 1, 0), 0, 'son', '20', 1, 0);
const process = new debugmodel.Process('mockProcess', rawSession);
const thread = new debugmodel.Thread(process, 'mockthread', 1);
const stackFrame = new debugmodel.StackFrame(thread, 1, null, 'app.js', 1, 1);
const scope = new debugmodel.Scope(stackFrame, 'myscope', 1, false, 1, 0);
const son = new debugmodel.Variable(stackFrame, new debugmodel.Variable(stackFrame, new debugmodel.Variable(stackFrame, scope, 0, 'grandfather', '75', 1, 0), 0, 'father', '45', 1, 0), 0, 'son', '20', 1, 0);
assert.equal(debugmodel.getFullExpressionName(son, type), 'grandfather.father.son');
const grandson = new debugmodel.Variable(son, 0, '/weird_name', '1', 0, 0);
const grandson = new debugmodel.Variable(stackFrame, son, 0, '/weird_name', '1', 0, 0);
assert.equal(debugmodel.getFullExpressionName(grandson, type), 'grandfather.father.son[\'/weird_name\']');
});
});
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册