Reuse variable objects, utilize var-update

Same variables now have same ids in each VariablesResponse.
This allows vscode to keep track of changes properly and prevents
collapsing of all variables after every step.
This commit is contained in:
gentoo90 2017-05-25 23:49:19 +03:00
commit 2551dba725
3 changed files with 137 additions and 54 deletions

View file

@ -1,3 +1,6 @@
import { MINode } from "./mi_parse";
import { DebugProtocol } from "vscode-debugprotocol/lib/debugProtocol";
export interface Breakpoint { export interface Breakpoint {
file?: string; file?: string;
line?: number; line?: number;
@ -59,4 +62,62 @@ export interface IBackend {
isReady(): boolean; isReady(): boolean;
changeVariable(name: string, rawValue: string): Thenable<any>; changeVariable(name: string, rawValue: string): Thenable<any>;
examineMemory(from: number, to: number): Thenable<any>; examineMemory(from: number, to: number): Thenable<any>;
} }
export class VariableObject {
name: string;
exp: string;
numchild: number;
type: string;
value: string;
threadId: string;
frozen: boolean;
dynamic: boolean;
displayhint: string;
has_more: boolean;
id: number;
constructor(node: any) {
this.name = MINode.valueOf(node, "name");
this.exp = MINode.valueOf(node, "exp");
this.numchild = parseInt(MINode.valueOf(node, "numchild"));
this.type = MINode.valueOf(node, "type");
this.value = MINode.valueOf(node, "value");
this.threadId = MINode.valueOf(node, "thread-id");
this.frozen = !!MINode.valueOf(node, "frozen");
this.dynamic = !!MINode.valueOf(node, "dynamic");
this.displayhint = MINode.valueOf(node, "displayhint");
// TODO: use has_more when it's > 0
this.has_more = !!MINode.valueOf(node, "has_more");
}
public applyChanges(node: MINode) {
this.value = MINode.valueOf(node, "value");
if (!!MINode.valueOf(node, "type_changed")) {
this.type = MINode.valueOf(node, "new_type");
}
this.dynamic = !!MINode.valueOf(node, "dynamic");
this.displayhint = MINode.valueOf(node, "displayhint");
this.has_more = !!MINode.valueOf(node, "has_more");
}
public isCompound(): boolean {
return this.numchild > 0 ||
this.value === "{...}" ||
(this.dynamic && (this.displayhint === "array" || this.displayhint === "map"));
}
public toProtocolVariable(): DebugProtocol.Variable {
let res: DebugProtocol.Variable = {
name: this.exp,
evaluateName: this.name,
value: (this.value === void 0) ? "<unknown>" : this.value,
type: this.type,
// kind: this.displayhint,
variablesReference: this.id
};
if (this.displayhint) {
res.kind = this.displayhint;
}
return res;
}
}

View file

@ -1,4 +1,4 @@
import { Breakpoint, IBackend, Stack, SSHArguments, Variable } from "../backend" import { Breakpoint, IBackend, Stack, SSHArguments, Variable, VariableObject } from "../backend"
import * as ChildProcess from "child_process" import * as ChildProcess from "child_process"
import { EventEmitter } from "events" import { EventEmitter } from "events"
import { parseMI, MINode } from '../mi_parse'; import { parseMI, MINode } from '../mi_parse';
@ -661,17 +661,33 @@ export class MI2 extends EventEmitter implements IBackend {
}); });
} }
async varCreate(expression: string): Promise<MINode> { async varCreate(expression: string, name: string = "-"): Promise<VariableObject> {
if (trace) if (trace)
this.log("stderr", "varCreate"); this.log("stderr", "varCreate");
return this.sendCommand(`var-create - * "${expression}"`); const res = await this.sendCommand(`var-create ${name} @ "${expression}"`);
return new VariableObject(res.result(""));
} }
async varListChildren(name: string): Promise<MINode> { async varEvalExpression(name: string): Promise < MINode > {
if (trace)
this.log("stderr", "varEvalExpression");
return this.sendCommand(`var-evaluate-expression ${name}`);
}
async varListChildren(name: string): Promise<VariableObject[]> {
if (trace) if (trace)
this.log("stderr", "varListChildren"); this.log("stderr", "varListChildren");
//TODO: add `from` and `to` arguments //TODO: add `from` and `to` arguments
return this.sendCommand(`var-list-children --simple-values ${name}`); const res = await this.sendCommand(`var-list-children --all-values ${name}`);
const children = res.result("children");
let omg: VariableObject[] = children.map(child => new VariableObject(child[1]));
return omg;
}
async varUpdate(name: string = "*"): Promise<MINode> {
if (trace)
this.log("stderr", "varUpdate");
return this.sendCommand(`var-update --all-values ${name}`)
} }
logNoNewLine(type: string, msg: string) { logNoNewLine(type: string, msg: string) {

View file

@ -1,6 +1,6 @@
import { DebugSession, InitializedEvent, TerminatedEvent, StoppedEvent, OutputEvent, Thread, StackFrame, Scope, Source, Handles } from 'vscode-debugadapter'; import { DebugSession, InitializedEvent, TerminatedEvent, StoppedEvent, OutputEvent, Thread, StackFrame, Scope, Source, Handles } from 'vscode-debugadapter';
import { DebugProtocol } from 'vscode-debugprotocol'; import { DebugProtocol } from 'vscode-debugprotocol';
import { Breakpoint, IBackend, Variable } from './backend/backend'; import { Breakpoint, IBackend, Variable, VariableObject } from './backend/backend';
import { MINode } from './backend/mi_parse'; import { MINode } from './backend/mi_parse';
import { expandValue, isExpandable } from './backend/gdb_expansion'; import { expandValue, isExpandable } from './backend/gdb_expansion';
import { MI2 } from './backend/mi2/mi2'; import { MI2 } from './backend/mi2/mi2';
@ -22,7 +22,8 @@ const STACK_HANDLES_START = 1000;
const VAR_HANDLES_START = 2000; const VAR_HANDLES_START = 2000;
export class MI2DebugSession extends DebugSession { export class MI2DebugSession extends DebugSession {
protected variableHandles = new Handles<string | ExtendedVariable>(VAR_HANDLES_START); protected variableHandles = new Handles<string | VariableObject | ExtendedVariable>(VAR_HANDLES_START);
protected variableHandlesReverse: { [id: string]: number } = {};
protected quit: boolean; protected quit: boolean;
protected attached: boolean; protected attached: boolean;
protected needContinue: boolean; protected needContinue: boolean;
@ -266,7 +267,7 @@ export class MI2DebugSession extends DebugSession {
protected async variablesRequest(response: DebugProtocol.VariablesResponse, args: DebugProtocol.VariablesArguments): Promise<void> { protected async variablesRequest(response: DebugProtocol.VariablesResponse, args: DebugProtocol.VariablesArguments): Promise<void> {
const variables: DebugProtocol.Variable[] = []; const variables: DebugProtocol.Variable[] = [];
let id: number | string | ExtendedVariable; let id: number | string | VariableObject | ExtendedVariable;
if (args.variablesReference < VAR_HANDLES_START) { if (args.variablesReference < VAR_HANDLES_START) {
id = args.variablesReference - STACK_HANDLES_START; id = args.variablesReference - STACK_HANDLES_START;
} }
@ -281,28 +282,16 @@ export class MI2DebugSession extends DebugSession {
return this.variableHandles.create(arg); return this.variableHandles.create(arg);
}; };
let miVarObjToVariable = (varObj: any): DebugProtocol.Variable => { let findOrCreateVariable = (varObj: VariableObject): number => {
const evaluateName = MINode.valueOf(varObj, "name"); let id: number;
const value = MINode.valueOf(varObj, "value"); if (this.variableHandlesReverse.hasOwnProperty(varObj.name)) {
const numChild = parseInt(MINode.valueOf(varObj, "numchild")); id = this.variableHandlesReverse[varObj.name];
const dynamic = MINode.valueOf(varObj, "dynamic") || 0;
let displayHint, hasMore;
if (dynamic) {
displayHint = MINode.valueOf(varObj, "displayhint");
hasMore = parseInt(MINode.valueOf(varObj, "has_more"));
} }
const isCompound = numChild > 0 || else {
value === "{...}" || id = createVariable(varObj);
(dynamic > 0 && (displayHint === "array" || displayHint === "map")); this.variableHandlesReverse[varObj.name] = id;
}
let res = { return varObj.isCompound() ? id : 0;
name: MINode.valueOf(varObj, "exp"),
evaluateName,
type: MINode.valueOf(varObj, "type"),
value: value || "<value>",
variablesReference: isCompound ? createVariable(evaluateName) : 0
} as DebugProtocol.Variable;
return res;
}; };
if (typeof id == "number") { if (typeof id == "number") {
@ -311,10 +300,26 @@ export class MI2DebugSession extends DebugSession {
stack = await this.miDebugger.getStackVariables(this.threadID, id); stack = await this.miDebugger.getStackVariables(this.threadID, id);
for (const variable of stack) { for (const variable of stack) {
try { try {
const varObj = await this.miDebugger.varCreate(variable.name); let varObj: VariableObject;
let v = miVarObjToVariable(varObj.resultRecords.results); try {
v.name = variable.name; const changes = await this.miDebugger.varUpdate(variable.name);
variables.push(v); const changelist = changes.result("changelist");
changelist.forEach((change) => {
const name = MINode.valueOf(change, "name");
const vId = this.variableHandlesReverse[variable.name];
const v = this.variableHandles.get(vId) as any;
v.applyChanges(change);
});
const varId = this.variableHandlesReverse[variable.name];
varObj = this.variableHandles.get(varId) as any;
}
catch (err) {
varObj = await this.miDebugger.varCreate(variable.name, variable.name);
const varId = findOrCreateVariable(varObj);
varObj.exp = variable.name;
varObj.id = varId;
}
variables.push(varObj.toProtocolVariable());
} }
catch (err) { catch (err) {
variables.push({ variables.push({
@ -333,27 +338,28 @@ export class MI2DebugSession extends DebugSession {
this.sendErrorResponse(response, 1, `Could not expand variable: ${err}`); this.sendErrorResponse(response, 1, `Could not expand variable: ${err}`);
} }
} }
else if (typeof id == "string") {
// Variable members
let listChildren;
try {
listChildren = await this.miDebugger.varListChildren(id);
const children: any[] = listChildren.result("children");
// TODO: use hasMore when it's > 0
// const hasMore = parseInt(listChildren.result("has_more"));
const vars = children.map(child => miVarObjToVariable(child[1]));
response.body = {
variables: vars
}
this.sendResponse(response);
}
catch (err) {
this.sendErrorResponse(response, 1, `Could not expand variable: ${err}`);
}
}
else if (typeof id == "object") { else if (typeof id == "object") {
if (id instanceof ExtendedVariable) { if (id instanceof VariableObject) {
// Variable members
let children: VariableObject[];
try {
children = await this.miDebugger.varListChildren(id.name);
const vars = children.map(child => {
const varId = findOrCreateVariable(child);
child.id = varId;
return child.toProtocolVariable();
});
response.body = {
variables: vars
}
this.sendResponse(response);
}
catch (err) {
this.sendErrorResponse(response, 1, `Could not expand variable: ${err}`);
}
}
else if (id instanceof ExtendedVariable) {
let varReq = id; let varReq = id;
if (varReq.options.arg) { if (varReq.options.arg) {
let strArr = []; let strArr = [];