Skip to content
This repository has been archived by the owner on Oct 2, 2021. It is now read-only.

Commit

Permalink
Editing variables - Fix #58
Browse files Browse the repository at this point in the history
  • Loading branch information
roblourens committed Sep 14, 2016
1 parent a3e2717 commit 8b99f42
Show file tree
Hide file tree
Showing 8 changed files with 177 additions and 20 deletions.
8 changes: 6 additions & 2 deletions src/chrome/chromeConnection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -233,8 +233,8 @@ export class ChromeConnection {
return this.sendMessage('Debugger.pause');
}

public debugger_evaluateOnCallFrame(callFrameId: string, expression: string, objectGroup = 'dummyObjectGroup', returnByValue?: boolean): Promise<Chrome.Debugger.EvaluateOnCallFrameResponse> {
return this.sendMessage('Debugger.evaluateOnCallFrame', <Chrome.Debugger.EvaluateOnCallFrameParams>{ callFrameId, expression, objectGroup, returnByValue });
public debugger_evaluateOnCallFrame(callFrameId: string, expression: string, objectGroup = 'dummyObjectGroup', returnByValue?: boolean, silent?: boolean): Promise<Chrome.Debugger.EvaluateOnCallFrameResponse> {
return this.sendMessage('Debugger.evaluateOnCallFrame', <Chrome.Debugger.EvaluateOnCallFrameParams>{ callFrameId, expression, objectGroup, returnByValue, silent });
}

public debugger_setPauseOnExceptions(state: string): Promise<Chrome.Response> {
Expand All @@ -245,6 +245,10 @@ export class ChromeConnection {
return this.sendMessage('Debugger.getScriptSource', <Chrome.Debugger.GetScriptSourceParams>{ scriptId });
}

public debugger_setVariableValue(callFrameId: string, scopeNumber: number, variableName: string, newValue: Chrome.Runtime.CallArgument): Promise<Chrome.Debugger.SetVariableResponse> {
return this.sendMessage('Debugger.setVariableValue', <Chrome.Debugger.SetVariableParams>{ callFrameId, scopeNumber, variableName, newValue });
}

public runtime_getProperties(objectId: string, ownProperties: boolean, accessorPropertiesOnly: boolean): Promise<Chrome.Runtime.GetPropertiesResponse> {
return this.sendMessage('Runtime.getProperties', <Chrome.Runtime.GetPropertiesParams>{ objectId, ownProperties, accessorPropertiesOnly, generatePreview: true });
}
Expand Down
71 changes: 54 additions & 17 deletions src/chrome/chromeDebugAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ import {StoppedEvent, InitializedEvent, TerminatedEvent, OutputEvent, Handles, C

import {ILaunchRequestArgs, ISetBreakpointsArgs, ISetBreakpointsResponseBody, IStackTraceResponseBody,
IAttachRequestArgs, IScopesResponseBody, IVariablesResponseBody,
ISourceResponseBody, IThreadsResponseBody, IEvaluateResponseBody} from '../debugAdapterInterfaces';
ISourceResponseBody, IThreadsResponseBody, IEvaluateResponseBody, ISetVariableResponseBody} from '../debugAdapterInterfaces';
import {ChromeConnection} from './chromeConnection';
import * as ChromeUtils from './chromeUtils';
import {formatConsoleMessage} from './consoleHelper';
import * as Chrome from './chromeDebugProtocol';
import {PropertyContainer, ScopeContainer, IVariableContainer} from './variables';

import * as errors from '../errors';
import * as utils from '../utils';
import * as logger from '../logger';
import {BaseDebugAdapter} from '../baseDebugAdapter';
Expand All @@ -24,19 +26,14 @@ import {LazySourceMapTransformer} from '../transformers/lazySourceMapTransformer

import * as path from 'path';

interface IScopeVarHandle {
objectId: string;
thisObj?: Chrome.Runtime.RemoteObject;
}

export abstract class ChromeDebugAdapter extends BaseDebugAdapter {
private static THREAD_ID = 1;
private static PAGE_PAUSE_MESSAGE = 'Paused in Visual Studio Code';
private static EXCEPTION_VALUE_ID = 'EXCEPTION_VALUE_ID';
private static PLACEHOLDER_URL_PROTOCOL = 'debugadapter://';

private _clientAttached: boolean;
private _variableHandles: Handles<IScopeVarHandle>;
private _variableHandles: Handles<IVariableContainer>;
private _currentStack: Chrome.Debugger.CallFrame[];
private _committedBreakpointsByUrl: Map<string, Chrome.Debugger.BreakpointId[]>;
private _overlayHelper: utils.DebounceHelper;
Expand All @@ -61,7 +58,7 @@ export abstract class ChromeDebugAdapter extends BaseDebugAdapter {
super();

this._chromeConnection = chromeConnection || new ChromeConnection();
this._variableHandles = new Handles<IScopeVarHandle>();
this._variableHandles = new Handles<IVariableContainer>();
this._overlayHelper = new utils.DebounceHelper(/*timeoutMs=*/200);

this._lineNumberTransformer = lineNumberTransformer || new LineNumberTransformer(/*targetLinesStartAt1=*/false);
Expand Down Expand Up @@ -113,7 +110,8 @@ export abstract class ChromeDebugAdapter extends BaseDebugAdapter {
default: true
}
],
supportsConfigurationDoneRequest: true
supportsConfigurationDoneRequest: true,
supportsSetVariable: true
};
}

Expand Down Expand Up @@ -556,16 +554,15 @@ export abstract class ChromeDebugAdapter extends BaseDebugAdapter {
}

public scopes(args: DebugProtocol.ScopesArguments): IScopesResponseBody {
const scopes = this._currentStack[args.frameId].scopeChain.map((scope: Chrome.Debugger.Scope, i: number) => {
const scopeHandle: IScopeVarHandle = { objectId: scope.object.objectId };
if (i === 0) {
// The first scope should include 'this'. Keep the RemoteObject reference for use by the variables request
scopeHandle.thisObj = this._currentStack[args.frameId]['this'];
}
const currentFrame = this._currentStack[args.frameId];
const scopes = currentFrame.scopeChain.map((scope: Chrome.Debugger.Scope, i: number) => {
// The first scope should include 'this'. Keep the RemoteObject reference for use by the variables request
const thisObj = i === 0 ? currentFrame['this'] : undefined;
const variablesReference = this._variableHandles.create(new ScopeContainer(currentFrame.callFrameId, i, scope.object.objectId, thisObj));

return <DebugProtocol.Scope>{
name: scope.type.substr(0, 1).toUpperCase() + scope.type.substr(1), // Take Chrome's scope, uppercase the first letter
variablesReference: this._variableHandles.create(scopeHandle),
variablesReference,
expensive: scope.type === 'global'
};
});
Expand Down Expand Up @@ -705,6 +702,46 @@ export abstract class ChromeDebugAdapter extends BaseDebugAdapter {
});
}

public setVariable(args: DebugProtocol.SetVariableArguments): Promise<ISetVariableResponseBody> {
const handle = this._variableHandles.get(args.variablesReference);
if (!handle) {
return Promise.reject(errors.setValueNotSupported());
}

return handle.setValue(this, args.name, args.value)
.then(value => ({ value }));
}

public _setVariableValue(frameId: string, scopeIndex: number, name: string, value: string): Promise<string> {
let evalResultObject: Chrome.Runtime.RemoteObject;
return this._chromeConnection.debugger_evaluateOnCallFrame(frameId, value, undefined, undefined, /*silent=*/true).then(evalResponse => {
if (evalResponse.error) {
return Promise.reject(errors.errorFromEvaluate(evalResponse.error.message));
} else if (evalResponse.result.exceptionDetails) {
const errMsg = ChromeUtils.errorMessageFromExceptionDetails(evalResponse.result.exceptionDetails);
return Promise.reject(errors.errorFromEvaluate(errMsg));
} else {
evalResultObject = evalResponse.result.result;
const newVal = ChromeUtils.remoteObjectToCallArgument(evalResultObject);
return this._chromeConnection.debugger_setVariableValue(frameId, scopeIndex, name, newVal);
}
})
.then(setVarResponse => ChromeUtils.remoteObjectToValue(evalResultObject).value);
}

public _setPropertyValue(objectId: string, propName: string, value: string): Promise<string> {
return this._chromeConnection.runtime_callFunctionOn(objectId, `function() { return this["${propName}"] = ${value} }`, undefined, /*silent=*/true).then(response => {
if (response.error) {
return Promise.reject(errors.errorFromEvaluate(response.error.message));
} else if (response.result.exceptionDetails) {
const errMsg = ChromeUtils.errorMessageFromExceptionDetails(response.result.exceptionDetails);
return Promise.reject(errors.errorFromEvaluate(errMsg));
} else {
return ChromeUtils.remoteObjectToValue(response.result.result).value;
}
});
}

/**
* Run the object through ChromeUtilities.remoteObjectToValue, and if it returns a variableHandle reference,
* use it with this instance's variableHandles to create a variable handle.
Expand All @@ -713,7 +750,7 @@ export abstract class ChromeDebugAdapter extends BaseDebugAdapter {
const { value, variableHandleRef } = ChromeUtils.remoteObjectToValue(object);
const result = { value, variablesReference: 0 };
if (variableHandleRef) {
result.variablesReference = this._variableHandles.create({ objectId: variableHandleRef });
result.variablesReference = this._variableHandles.create(new PropertyContainer(variableHandleRef));
}

return result;
Expand Down
9 changes: 9 additions & 0 deletions src/chrome/chromeDebugProtocol.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,15 @@ export namespace Debugger {
scriptSource: string;
};
}

export interface SetVariableParams {
scopeNumber: number;
variableName: string;
newValue: Runtime.CallArgument;
callFrameId: string;
}

export type SetVariableResponse = Response;
}

export namespace Runtime {
Expand Down
32 changes: 32 additions & 0 deletions src/chrome/chromeUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,3 +154,35 @@ export function compareVariableNames(var1: string, var2: string): number {
// Compare strings as strings
return var1.localeCompare(var2);
}

/**
* Maybe this can be merged with remoteObjectToValue, they are somewhat similar
*/
export function remoteObjectToCallArgument(object: Chrome.Runtime.RemoteObject): Chrome.Runtime.CallArgument {
if (object) {
if (object.type === 'object') {
if (object.subtype === 'null') {
return { value: null };
} else {
// It's a non-null object, create a variable reference so the client can ask for its props
return { objectId: object.objectId };
}
} else if (object.type === 'undefined') {
return { value: undefined }; // ?
} else if (object.type === 'function') {
return { objectId: object.objectId };
} else {
// The value is a primitive value, or something that has a description (not object, primitive, or undefined). And force to be string
if (typeof object.value === 'undefined') {
return { value: undefined }; // uh
} else {
return { value: object.value }; // ??
}
}
}
}

export function errorMessageFromExceptionDetails(exceptionDetails: any): string {
const description: string = exceptionDetails.exception.description;
return description.substr(0, description.indexOf('\n'));
}
57 changes: 57 additions & 0 deletions src/chrome/variables.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/

import * as DebugProtocol from 'vscode-debugadapter';

import {ChromeDebugAdapter} from './chromeDebugAdapter';
import * as Chrome from './chromeDebugProtocol.d';

import * as utils from '../utils';

export interface IVariableContainer {
objectId: string;
thisObj?: Chrome.Runtime.RemoteObject;
expand(adapter: ChromeDebugAdapter, filter: string, start: number, count: number): Promise<DebugProtocol.Variable[]>;
setValue(adapter: ChromeDebugAdapter, name: string, value: string): Promise<string>;
}

export abstract class BaseVariableContainer implements IVariableContainer {
constructor(public objectId: string) {
}

public abstract expand(adapter: ChromeDebugAdapter, filter: string, start: number, count: number): Promise<DebugProtocol.Variable[]>;
public abstract setValue(adapter: ChromeDebugAdapter, name: string, value: string): Promise<string>;
}

export class PropertyContainer extends BaseVariableContainer {
public expand(adapter: ChromeDebugAdapter, filter: string, start: number, count: number): Promise<DebugProtocol.Variable[]> {
return utils.errP('Not implemented');
}

public setValue(adapter: ChromeDebugAdapter, name: string, value: string): Promise<string> {
return adapter._setPropertyValue(this.objectId, name, value);
}
}

export class ScopeContainer extends BaseVariableContainer {
public thisObj: Chrome.Runtime.RemoteObject;

private _frameId: string;
private _scopeIndex: number;

public constructor(frameId: string, scopeIndex: number, objectId: string, thisObj?: Chrome.Runtime.RemoteObject) {
super(objectId);
this.thisObj = thisObj;
this._frameId = frameId;
this._scopeIndex = scopeIndex;
}

public expand(adapter: ChromeDebugAdapter, filter: string, start: number, count: number): Promise<DebugProtocol.Variable[]> {
return utils.errP('Not implemented');
}

public setValue(adapter: ChromeDebugAdapter, name: string, value: string): Promise<string> {
return adapter._setVariableValue(this._frameId, this._scopeIndex, name, value);
}
}
4 changes: 4 additions & 0 deletions src/debugAdapterInterfaces.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,10 @@ export interface IEvaluateResponseBody {
variablesReference: number;
}

export interface ISetVariableResponseBody {
value: string;
}

declare type PromiseOrNot<T> = T | Promise<T>;

/**
Expand Down
14 changes: 14 additions & 0 deletions src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,18 @@ export function withInfoLink(id: number, format: string, variables: any, infoId:
url: 'http://go.microsoft.com/fwlink/?linkID=534832#_' + infoId.toString(),
urlLabel: localize('more.information', "More Information")
};
}

export function setValueNotSupported(): DebugProtocol.Message {
return {
id: 2004,
format: localize('setVariable.error', "Setting value not supported")
};
}

export function errorFromEvaluate(errMsg: string): DebugProtocol.Message {
return {
id: 2025,
format: errMsg
};
}
2 changes: 1 addition & 1 deletion tslint.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"spaces"
],
"align": [
true,
false,
"statements"
],
"ban": false,
Expand Down

0 comments on commit 8b99f42

Please sign in to comment.