14.5. Variables

The Variables collapsible section shows three roots:

Globals resolves against _G first and then against the paused frame’s _ENV upvalue. The _ENV fallback matters for scripts loaded with a file environment (for example via dofile) — a top-level my_proto = Proto.new(…​) in such a script lands in the file’s _ENV rather than _G, and the debugger still lists it under Globals.

The tree is populated lazily: a row is only inspected when you expand it. Sub-trees stop at the same path-depth cap as Watch (see Section 14.6.2, “Path-watch syntax”).

The tree has two columns, Name and Value; the underlying type is folded into the row tooltip rather than shown in its own column. For userdata values (Wireshark class instances such as Pinfo, Tvb, ProtoField), the tooltip reads userdata (ClassName) when the instance metatable exposes a __name, otherwise just userdata.

Functions are listed alongside other values (rendered as function: 0xADDR, the standard Lua tostring shape) so callbacks, locally-bound helpers, methods on userdata, and stdlib namespaces like string and table all show up in their respective scopes. Function rows are not expandable; to inspect what a function does, view its source via Stack Trace once it is on the call stack.

Selecting a different frame in Stack Trace rebuilds the Variables tree for that frame.

A context menu on any row offers Copy Name, Copy Value (the full, untruncated value — the tree itself truncates long values for display), Copy Name & Value (name = value), Copy Path (the canonical Variables-tree path, ready to paste into the Watch panel or the Evaluate panel), and Add Watch (prefilled with the row’s variable path; the menu label includes the path so you can see exactly what would be added).

14.5.1. Changed-value cue

Both the Variables tree and the Watch tree highlight values that changed since the previous pause: the row is drawn in a bold accent foreground from the current theme and briefly flashes a softer highlight tint. New rows (a local that did not exist last pause, a key added to a table, a freshly appearing Watch child) receive the same treatment. Watch roots deliberately do not flash on first sighting — a new root is usually a spec you just typed.

The cue is anchored to the stack frame that was active when the pause started, because "the value changed" only makes sense against a comparable earlier reading:

  • Selecting a different frame in the Stack Trace mid-pause shows an unrelated scope, so the cue is suppressed on Locals, Upvalues, and any unqualified or Locals.* / Upvalues.* watch rows while you are in a non-entry frame. Globals.* rows stay comparable across frame switches and keep their highlight.
  • A pause that landed inside a different function than the previous pause also suppresses the cue on Locals and Upvalues, to avoid spuriously lighting up "every local is new" after stepping across a call or return. Pauses that hit the same function again — for example the next iteration of a loop, or a recurring dissector callback — still show the cue.

The change baseline is cleared, so the next pause shows no cue, after editing a watch’s spec, removing watches, reloading Lua plugins, or disabling the debugger. A failed watch lookup invalidates that one watch’s baseline only.

Empty strings count as real values for the comparison: a variable whose value was "" last pause and is still "" now does not flash. Single-stepping never flickers the Watch Value column through its not-paused em-dash placeholder, even though a step technically resumes and re-pauses; the tree repaints straight from the new pause’s values.