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:
parent
77b689443f
commit
2551dba725
3 changed files with 137 additions and 54 deletions
|
|
@ -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;
|
||||||
|
|
@ -60,3 +63,61 @@ export interface IBackend {
|
||||||
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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,15 +338,17 @@ 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") {
|
else if (typeof id == "object") {
|
||||||
|
if (id instanceof VariableObject) {
|
||||||
// Variable members
|
// Variable members
|
||||||
let listChildren;
|
let children: VariableObject[];
|
||||||
try {
|
try {
|
||||||
listChildren = await this.miDebugger.varListChildren(id);
|
children = await this.miDebugger.varListChildren(id.name);
|
||||||
const children: any[] = listChildren.result("children");
|
const vars = children.map(child => {
|
||||||
// TODO: use hasMore when it's > 0
|
const varId = findOrCreateVariable(child);
|
||||||
// const hasMore = parseInt(listChildren.result("has_more"));
|
child.id = varId;
|
||||||
const vars = children.map(child => miVarObjToVariable(child[1]));
|
return child.toProtocolVariable();
|
||||||
|
});
|
||||||
|
|
||||||
response.body = {
|
response.body = {
|
||||||
variables: vars
|
variables: vars
|
||||||
|
|
@ -352,8 +359,7 @@ 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 == "object") {
|
else if (id instanceof ExtendedVariable) {
|
||||||
if (id instanceof ExtendedVariable) {
|
|
||||||
let varReq = id;
|
let varReq = id;
|
||||||
if (varReq.options.arg) {
|
if (varReq.options.arg) {
|
||||||
let strArr = [];
|
let strArr = [];
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue