From 5d5176d9844738bdf428d309881b544e7026ef27 Mon Sep 17 00:00:00 2001 From: Sergei Zharinov Date: Sun, 4 Jan 2026 18:35:10 -0300 Subject: [PATCH] refactor: improve trace text formatting - Display source text dimmed+green without quotes - Dynamic truncation to fill up to successors column - Minimum 12-char text budget for readability --- crates/plotnik-lib/src/engine/trace.rs | 54 +++++++++++++++----------- docs/binary-format/08-trace-format.md | 40 +++++++++---------- 2 files changed, 52 insertions(+), 42 deletions(-) diff --git a/crates/plotnik-lib/src/engine/trace.rs b/crates/plotnik-lib/src/engine/trace.rs index f6c1c721..9faf46a8 100644 --- a/crates/plotnik-lib/src/engine/trace.rs +++ b/crates/plotnik-lib/src/engine/trace.rs @@ -24,7 +24,7 @@ use std::num::NonZeroU16; use arborium_tree_sitter::Node; use crate::bytecode::{ - format_effect, nav_symbol, trace, truncate_text, width_for_count, EffectOpcode, + cols, format_effect, nav_symbol, trace, truncate_text, width_for_count, EffectOpcode, InstructionView, LineBuilder, MatchView, Module, Nav, Symbol, }; use crate::Colors; @@ -252,6 +252,25 @@ impl<'s> PrintTracer<'s> { self.entrypoint_by_ip.get(&ip).map_or("?", |s| s.as_str()) } + /// Format kind with source text, dynamically truncated to fit content width. + /// Text is displayed dimmed and green, no quotes. + fn format_kind_with_text(&self, kind: &str, text: &str) -> String { + let c = &self.colors; + + // Available content width = TOTAL_WIDTH - prefix_width + step_width + // prefix_width = INDENT + step_width + GAP + SYMBOL + GAP = 9 + step_width + // +step_width because ellipsis can extend into the successors column + // (sub-lines have no successors, so we use that space) + // This simplifies to: TOTAL_WIDTH - 9 = 35 + let available = cols::TOTAL_WIDTH - 9; + + // Text budget = available - kind.len() - 1 (space), minimum 12 + let text_budget = available.saturating_sub(kind.len() + 1).max(12); + + let truncated = truncate_text(text, text_budget); + format!("{} {}{}{}{}", kind, c.dim, c.green, truncated, c.reset) + } + /// Format a runtime effect for display. fn format_effect(&self, effect: &RuntimeEffect<'_>) -> String { use RuntimeEffect::*; @@ -398,7 +417,6 @@ impl Tracer for PrintTracer<'_> { return; } - let c = &self.colors; let kind = node.kind(); let symbol = match nav { Nav::Down | Nav::DownSkip | Nav::DownExact => trace::NAV_DOWN, @@ -407,45 +425,37 @@ impl Tracer for PrintTracer<'_> { Nav::Stay | Nav::StayExact => Symbol::EMPTY, }; - // Text only in VeryVerbose (text is dim) + // Text only in VeryVerbose if self.verbosity == Verbosity::VeryVerbose { - let text = truncate_text(node.utf8_text(self.source).unwrap_or("?"), 20); - self.add_subline( - symbol, - &format!("{} {}\"{}\"{}", kind, c.dim, text, c.reset), - ); + let text = node.utf8_text(self.source).unwrap_or("?"); + let content = self.format_kind_with_text(kind, text); + self.add_subline(symbol, &content); } else { self.add_subline(symbol, kind); } } fn trace_match_success(&mut self, node: Node<'_>) { - let c = &self.colors; let kind = node.kind(); - // Text on match/failure in Verbose+ (text is dim) + // Text on match/failure in Verbose+ if self.verbosity != Verbosity::Default { - let text = truncate_text(node.utf8_text(self.source).unwrap_or("?"), 20); - self.add_subline( - trace::MATCH_SUCCESS, - &format!("{} {}\"{}\"{}", kind, c.dim, text, c.reset), - ); + let text = node.utf8_text(self.source).unwrap_or("?"); + let content = self.format_kind_with_text(kind, text); + self.add_subline(trace::MATCH_SUCCESS, &content); } else { self.add_subline(trace::MATCH_SUCCESS, kind); } } fn trace_match_failure(&mut self, node: Node<'_>) { - let c = &self.colors; let kind = node.kind(); - // Text on match/failure in Verbose+ (text is dim) + // Text on match/failure in Verbose+ if self.verbosity != Verbosity::Default { - let text = truncate_text(node.utf8_text(self.source).unwrap_or("?"), 20); - self.add_subline( - trace::MATCH_FAILURE, - &format!("{} {}\"{}\"{}", kind, c.dim, text, c.reset), - ); + let text = node.utf8_text(self.source).unwrap_or("?"); + let content = self.format_kind_with_text(kind, text); + self.add_subline(trace::MATCH_FAILURE, &content); } else { self.add_subline(trace::MATCH_FAILURE, kind); } diff --git a/docs/binary-format/08-trace-format.md b/docs/binary-format/08-trace-format.md index 564ba4da..c06edff8 100644 --- a/docs/binary-format/08-trace-format.md +++ b/docs/binary-format/08-trace-format.md @@ -20,7 +20,7 @@ plotnik trace query.ptk source.js --fuel 10000 | `-v` | all | on match/failure | Developer | | `-vv` | all | on all (incl. nav) | Deep debugging | -**Text budget**: Node text is truncated to 20 characters with `…` when displayed. +**Text budget**: Node text fills the line up to the successors column (minimum 12 characters). Truncated text ends with `…`. --- @@ -156,11 +156,11 @@ Assignment: Assignment: 08 ε 09 09 (assignment_expression) 10 - ● assignment_expression "x = y" + ● assignment_expression x = y 10 ▽ left: (identifier) [Node Set(M6)] 12 ▽ identifier ● left: - ● identifier "x" + ● identifier x ⬥ Node ⬥ Set "target" 12 ▷ right: (Expression) 13 ⯇ @@ -174,12 +174,12 @@ Expression: 22 ε [Enum(M3)] 20 ⬥ Enum "Literal" 20 (number) [Node Set(M1)] 18 - ○ identifier "y" + ○ identifier y 06 ❮❮❮ 28 ε [Enum(M4)] 26 ⬥ Enum "Variable" 26 (identifier) [Node Set(M2)] 24 - ● identifier "y" + ● identifier y ⬥ Node ⬥ Set "name" 24 ε [EndEnum] 17 @@ -231,11 +231,11 @@ Assignment: Assignment: 08 ε 09 09 (assignment_expression) 10 - ● assignment_expression "x = 1" + ● assignment_expression x = 1 10 ▽ left: (identifier) [Node Set(M6)] 12 ▽ identifier ● left: - ● identifier "x" + ● identifier x ⬥ Node ⬥ Set "target" 12 ▷ right: (Expression) 13 ⯇ @@ -249,7 +249,7 @@ Expression: 22 ε [Enum(M3)] 20 ⬥ Enum "Literal" 20 (number) [Node Set(M1)] 18 - ● number "1" + ● number 1 ⬥ Node ⬥ Set "value" 18 ε [EndEnum] 17 @@ -288,12 +288,12 @@ Expression: 22 ε [Enum(M3)] 20 ⬥ Enum "Literal" 20 (number) [Node Set(M1)] 18 - ○ string "hello" + ○ string hello 06 ❮❮❮ 28 ε [Enum(M4)] 26 ⬥ Enum "Variable" 26 (identifier) [Node Set(M2)] 24 - ○ string "hello" + ○ string hello ``` Both branches fail. No more checkpoints—query does not match. The CLI exits with code 1. @@ -315,7 +315,7 @@ Both branches fail. No more checkpoints—query does not match. The CLI exits wi Ident: 01 ε 02 02 (identifier) [Text Set(M0)] 04 - ● identifier "foo" + ● identifier foo ⬥ Text ⬥ Set "name" 04 ▶ @@ -359,12 +359,12 @@ ReturnVal: ReturnVal: 01 ε 02 02 (statement_block) 03 - ● statement_block "{ x; return 1; }" + ● statement_block { x; return 1; } 03 ▽ (return_statement) [Node Set(M0)] 04 ▽ expression_statement - ○ expression_statement "x;" + ○ expression_statement x; ▷ return_statement - ● return_statement "return 1;" + ● return_statement return 1; ⬥ Node ⬥ Set "ret" 04 △ 05 @@ -391,7 +391,7 @@ The `▽` lands on `(expression_statement)`, type mismatch, skip `▷` to next s Assignment: 08 ε 09 09 (assignment_expression) 10 - ○ number "42" + ○ number 42 ``` Type check fails at root—no navigation occurs. The CLI exits with code 1. @@ -429,11 +429,11 @@ Pair: 02 ε [Obj] 04 ⬥ Obj 04 (pair) 05 - ● pair "\"x\": 1" + ● pair "x": 1 05 ▽ key: (string) [SuppressBegin] 06 ▽ string ● key: - ● string "\"x\"" + ● string "x" ⬥ SuppressBegin 06 ε [SuppressEnd] 08 ⬦ Node @@ -442,7 +442,7 @@ Pair: 08 ▷ value: (number) [Node Set(M0)] 10 ▷ number ● value: - ● number "1" + ● number 1 ⬥ Node ⬥ Set "value" 10 △ 12 @@ -511,11 +511,11 @@ Hidden: | ------- | ------------------- | ---------------------------- | | (blank) | ` kind` | ` identifier` | | ` ▽ ` | `▽ kind` | `▽ identifier` | -| ` ▽ ` | `▽ kind "text"` | `▽ identifier "foo"` | +| ` ▽ ` | `▽ kind text` | `▽ identifier foo` | | ` ▷ ` | `▷ kind` | `▷ return_statement` | | ` △ ` | `△ kind` | `△ assignment_expression` | | ` ● ` | `● kind` | `● identifier` | -| ` ● ` | `● kind "text"` | `● identifier "foo"` | +| ` ● ` | `● kind text` | `● identifier foo` | | ` ● ` | `● field:` | `● left:` | | ` ○ ` | `○ kind` | `○ string` | | ` ⬥ ` | `⬥ Effect` | `⬥ Node` |