From 2551dba72531d089235af0f58ad9fbd9e1537b13 Mon Sep 17 00:00:00 2001 From: gentoo90 Date: Thu, 25 May 2017 23:49:19 +0300 Subject: [PATCH] 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. --- src/backend/backend.ts | 63 ++++++++++++++++++++++++- src/backend/mi2/mi2.ts | 26 +++++++++-- src/mibase.ts | 102 ++++++++++++++++++++++------------------- 3 files changed, 137 insertions(+), 54 deletions(-) diff --git a/src/backend/backend.ts b/src/backend/backend.ts index d2738a7..ada4232 100644 --- a/src/backend/backend.ts +++ b/src/backend/backend.ts @@ -1,3 +1,6 @@ +import { MINode } from "./mi_parse"; +import { DebugProtocol } from "vscode-debugprotocol/lib/debugProtocol"; + export interface Breakpoint { file?: string; line?: number; @@ -59,4 +62,62 @@ export interface IBackend { isReady(): boolean; changeVariable(name: string, rawValue: string): Thenable; examineMemory(from: number, to: number): Thenable; -} \ No newline at end of file +} + +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) ? "" : this.value, + type: this.type, + // kind: this.displayhint, + variablesReference: this.id + }; + if (this.displayhint) { + res.kind = this.displayhint; + } + return res; + } +} diff --git a/src/backend/mi2/mi2.ts b/src/backend/mi2/mi2.ts index f9b0a26..0bcc6bd 100644 --- a/src/backend/mi2/mi2.ts +++ b/src/backend/mi2/mi2.ts @@ -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 { EventEmitter } from "events" import { parseMI, MINode } from '../mi_parse'; @@ -661,17 +661,33 @@ export class MI2 extends EventEmitter implements IBackend { }); } - async varCreate(expression: string): Promise { + async varCreate(expression: string, name: string = "-"): Promise { if (trace) 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 { + async varEvalExpression(name: string): Promise < MINode > { + if (trace) + this.log("stderr", "varEvalExpression"); + return this.sendCommand(`var-evaluate-expression ${name}`); + } + + async varListChildren(name: string): Promise { if (trace) this.log("stderr", "varListChildren"); //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 { + if (trace) + this.log("stderr", "varUpdate"); + return this.sendCommand(`var-update --all-values ${name}`) } logNoNewLine(type: string, msg: string) { diff --git a/src/mibase.ts b/src/mibase.ts index e9500fb..16785e2 100644 --- a/src/mibase.ts +++ b/src/mibase.ts @@ -1,6 +1,6 @@ import { DebugSession, InitializedEvent, TerminatedEvent, StoppedEvent, OutputEvent, Thread, StackFrame, Scope, Source, Handles } from 'vscode-debugadapter'; 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 { expandValue, isExpandable } from './backend/gdb_expansion'; import { MI2 } from './backend/mi2/mi2'; @@ -22,7 +22,8 @@ const STACK_HANDLES_START = 1000; const VAR_HANDLES_START = 2000; export class MI2DebugSession extends DebugSession { - protected variableHandles = new Handles(VAR_HANDLES_START); + protected variableHandles = new Handles(VAR_HANDLES_START); + protected variableHandlesReverse: { [id: string]: number } = {}; protected quit: boolean; protected attached: boolean; protected needContinue: boolean; @@ -266,7 +267,7 @@ export class MI2DebugSession extends DebugSession { protected async variablesRequest(response: DebugProtocol.VariablesResponse, args: DebugProtocol.VariablesArguments): Promise { const variables: DebugProtocol.Variable[] = []; - let id: number | string | ExtendedVariable; + let id: number | string | VariableObject | ExtendedVariable; if (args.variablesReference < VAR_HANDLES_START) { id = args.variablesReference - STACK_HANDLES_START; } @@ -281,28 +282,16 @@ export class MI2DebugSession extends DebugSession { return this.variableHandles.create(arg); }; - let miVarObjToVariable = (varObj: any): DebugProtocol.Variable => { - const evaluateName = MINode.valueOf(varObj, "name"); - const value = MINode.valueOf(varObj, "value"); - const numChild = parseInt(MINode.valueOf(varObj, "numchild")); - const dynamic = MINode.valueOf(varObj, "dynamic") || 0; - let displayHint, hasMore; - if (dynamic) { - displayHint = MINode.valueOf(varObj, "displayhint"); - hasMore = parseInt(MINode.valueOf(varObj, "has_more")); + let findOrCreateVariable = (varObj: VariableObject): number => { + let id: number; + if (this.variableHandlesReverse.hasOwnProperty(varObj.name)) { + id = this.variableHandlesReverse[varObj.name]; } - const isCompound = numChild > 0 || - value === "{...}" || - (dynamic > 0 && (displayHint === "array" || displayHint === "map")); - - let res = { - name: MINode.valueOf(varObj, "exp"), - evaluateName, - type: MINode.valueOf(varObj, "type"), - value: value || "", - variablesReference: isCompound ? createVariable(evaluateName) : 0 - } as DebugProtocol.Variable; - return res; + else { + id = createVariable(varObj); + this.variableHandlesReverse[varObj.name] = id; + } + return varObj.isCompound() ? id : 0; }; if (typeof id == "number") { @@ -311,10 +300,26 @@ export class MI2DebugSession extends DebugSession { stack = await this.miDebugger.getStackVariables(this.threadID, id); for (const variable of stack) { try { - const varObj = await this.miDebugger.varCreate(variable.name); - let v = miVarObjToVariable(varObj.resultRecords.results); - v.name = variable.name; - variables.push(v); + let varObj: VariableObject; + try { + const changes = await this.miDebugger.varUpdate(variable.name); + 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) { variables.push({ @@ -333,27 +338,28 @@ export class MI2DebugSession extends DebugSession { 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") { - 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; if (varReq.options.arg) { let strArr = [];