Skip to content

Commit 434217f

Browse files
committed
fix: wire up TUI and make it update
1 parent 5df4a17 commit 434217f

File tree

8 files changed

+139
-73
lines changed

8 files changed

+139
-73
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

devenv-tui/replays/quick-test

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
2025-01-14T10:00:00.000Z @nix {"action":"start","id":1,"level":3,"type":105,"text":"building /nix/store/abc123-hello-2.12.drv","parent":0,"fields":["/nix/store/abc123-hello-2.12.drv"]}
2+
2025-01-14T10:00:00.100Z @nix {"action":"result","id":1,"type":104,"fields":["unpacking sources"]}
3+
2025-01-14T10:00:01.000Z @nix {"action":"result","id":1,"type":104,"fields":["configuring"]}
4+
2025-01-14T10:00:02.000Z @nix {"action":"result","id":1,"type":104,"fields":["building"]}
5+
2025-01-14T10:00:02.500Z @nix {"action":"start","id":2,"level":3,"type":100,"text":"copying path","parent":0,"fields":["/nix/store/xyz789-nodejs-18.19.0","https://cache.nixos.org"]}
6+
2025-01-14T10:00:02.600Z @nix {"action":"result","id":2,"type":105,"fields":[2621440,10485760]}
7+
2025-01-14T10:00:02.800Z @nix {"action":"result","id":2,"type":105,"fields":[7340032,10485760]}
8+
2025-01-14T10:00:03.000Z @nix {"action":"result","id":2,"type":105,"fields":[10485760,10485760]}
9+
2025-01-14T10:00:03.200Z @nix {"action":"stop","id":2}
10+
2025-01-14T10:00:03.500Z @nix {"action":"start","id":3,"level":3,"type":109,"text":"querying path info","parent":0,"fields":["/nix/store/def456-python3-3.11.7","https://cache.nixos.org"]}
11+
2025-01-14T10:00:04.000Z @nix {"action":"stop","id":3}
12+
2025-01-14T10:00:04.500Z @nix {"action":"result","id":1,"type":104,"fields":["installing"]}
13+
2025-01-14T10:00:06.000Z @nix {"action":"stop","id":1}

devenv-tui/src/app.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ fn TuiApp(mut hooks: Hooks) -> impl Into<AnyElement<'static>> {
1212
let model = hooks.use_context::<Arc<Mutex<Model>>>();
1313
let (terminal_width, _terminal_height) = hooks.use_terminal_size();
1414

15+
// Use state to trigger re-renders
16+
let tick = hooks.use_state(|| 0u64);
17+
1518
// Handle keyboard events directly
1619
hooks.use_terminal_events({
1720
let model = model.clone();
@@ -44,10 +47,12 @@ fn TuiApp(mut hooks: Hooks) -> impl Into<AnyElement<'static>> {
4447
}
4548
});
4649

47-
// Spinner animation update loop - also triggers re-renders to pick up model changes
50+
// Spinner animation update loop - triggers re-renders via state updates
4851
hooks.use_future({
4952
let model = model.clone();
53+
let mut tick = tick.clone();
5054
async move {
55+
let mut counter = 0u64;
5156
loop {
5257
tokio::time::sleep(Duration::from_millis(50)).await;
5358

@@ -68,7 +73,10 @@ fn TuiApp(mut hooks: Hooks) -> impl Into<AnyElement<'static>> {
6873
break;
6974
}
7075
}
71-
// The model mutation above triggers a re-render, which picks up all changes
76+
77+
// Trigger re-render by updating state
78+
counter += 1;
79+
tick.set(counter);
7280
}
7381
}
7482
});

devenv-tui/src/bin/tui-replay.rs

Lines changed: 80 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use anyhow::{Context, Result};
22
use chrono::{DateTime, Utc};
33
use devenv_eval_cache::internal_log::{ActivityType, Field, InternalLog};
44
use devenv_tui::{OperationId, init_tui};
5+
use std::collections::HashMap;
56
use std::fs::File;
67
use std::io::{BufRead, BufReader};
78
use std::path::PathBuf;
@@ -11,16 +12,19 @@ use std::sync::{Arc, Mutex};
1112
use tokio::time::sleep;
1213
use tokio_shutdown::Shutdown;
1314
use tracing::{debug_span, info, warn};
15+
use tracing::span::EnteredSpan;
1416

1517
/// Simple replay processor that emits tracing events for Nix logs
1618
struct NixLogReplayProcessor {
1719
current_operation_id: Arc<Mutex<Option<OperationId>>>,
20+
active_spans: Arc<Mutex<HashMap<u64, EnteredSpan>>>,
1821
}
1922

2023
impl NixLogReplayProcessor {
2124
fn new() -> Self {
2225
Self {
2326
current_operation_id: Arc::new(Mutex::new(None)),
27+
active_spans: Arc::new(Mutex::new(HashMap::new())),
2428
}
2529
}
2630

@@ -99,9 +103,10 @@ impl NixLogReplayProcessor {
99103
machine = ?machine
100104
);
101105

102-
span.in_scope(|| {
103-
info!(devenv.log = true, "Building {}", derivation_name);
104-
});
106+
let entered_span = span.entered();
107+
if let Ok(mut spans) = self.active_spans.lock() {
108+
spans.insert(activity_id, entered_span);
109+
}
105110
}
106111
ActivityType::CopyPath => {
107112
if let (Some(Field::String(store_path)), Some(Field::String(substituter))) =
@@ -121,9 +126,10 @@ impl NixLogReplayProcessor {
121126
substituter = %substituter
122127
);
123128

124-
span.in_scope(|| {
125-
info!(devenv.log = true, "Downloading {}", package_name);
126-
});
129+
let entered_span = span.entered();
130+
if let Ok(mut spans) = self.active_spans.lock() {
131+
spans.insert(activity_id, entered_span);
132+
}
127133
}
128134
}
129135
ActivityType::QueryPathInfo => {
@@ -145,9 +151,10 @@ impl NixLogReplayProcessor {
145151
substituter = %substituter
146152
);
147153

148-
span.in_scope(|| {
149-
info!(devenv.log = true, "Querying {}", package_name);
150-
});
154+
let entered_span = span.entered();
155+
if let Ok(mut spans) = self.active_spans.lock() {
156+
spans.insert(activity_id, entered_span);
157+
}
151158
}
152159
}
153160
ActivityType::FetchTree => {
@@ -162,9 +169,10 @@ impl NixLogReplayProcessor {
162169
message = %text
163170
);
164171

165-
span.in_scope(|| {
166-
info!(devenv.log = true, "Fetching {}", text);
167-
});
172+
let entered_span = span.entered();
173+
if let Ok(mut spans) = self.active_spans.lock() {
174+
spans.insert(activity_id, entered_span);
175+
}
168176
}
169177
_ => {
170178
// For other activity types, emit a debug event
@@ -173,9 +181,11 @@ impl NixLogReplayProcessor {
173181
}
174182
}
175183

176-
fn handle_activity_stop(&self, _activity_id: u64, _success: bool) {
177-
// Activity stop is handled automatically when the span drops
178-
// The DevenvTuiLayer will generate the appropriate end events
184+
fn handle_activity_stop(&self, activity_id: u64, _success: bool) {
185+
// Remove the span from active spans - dropping it will close the span
186+
if let Ok(mut spans) = self.active_spans.lock() {
187+
spans.remove(&activity_id);
188+
}
179189
}
180190

181191
fn handle_activity_result(
@@ -193,51 +203,50 @@ impl NixLogReplayProcessor {
193203
(fields.first(), fields.get(1))
194204
{
195205
let total_bytes = match total_opt {
196-
Some(Field::Int(total)) => Some(total),
206+
Some(Field::Int(total)) => Some(*total),
197207
_ => None,
198208
};
199209

200-
let span = debug_span!(
210+
// Emit progress event (not a span)
211+
tracing::event!(
201212
target: "devenv.nix.download",
202-
"nix_download_progress",
213+
tracing::Level::DEBUG,
203214
devenv.ui.message = "download progress",
204-
devenv.ui.type = "download",
215+
devenv.ui.id = "progress",
216+
activity_id = activity_id,
217+
bytes_downloaded = *downloaded,
218+
total_bytes = ?total_bytes,
219+
"Download progress: {} / {:?} bytes",
220+
downloaded,
221+
total_bytes
222+
);
223+
}
224+
}
225+
ResultType::SetPhase => {
226+
if let Some(Field::String(phase)) = fields.first() {
227+
// Emit phase change event
228+
tracing::event!(
229+
target: "devenv.nix.build",
230+
tracing::Level::DEBUG,
231+
devenv.ui.message = "build phase",
232+
devenv.ui.id = "phase",
205233
activity_id = activity_id,
206-
bytes_downloaded = downloaded,
207-
total_bytes = ?total_bytes
234+
phase = phase.as_str(),
235+
"Build phase: {}", phase
208236
);
209-
span.in_scope(|| {
210-
if let Some(total) = total_bytes {
211-
let percent = (*downloaded as f64 / *total as f64) * 100.0;
212-
tracing::debug!(
213-
"Download progress: {} / {} bytes ({:.1}%)",
214-
downloaded,
215-
total,
216-
percent
217-
);
218-
} else {
219-
tracing::debug!("Download progress: {} bytes", downloaded);
220-
}
221-
});
222237
}
223238
}
224239
ResultType::BuildLogLine => {
225240
if let Some(Field::String(log_line)) = fields.first() {
226-
let span = debug_span!(
241+
// Emit build log event (not a span)
242+
tracing::event!(
227243
target: "devenv.nix.build",
228-
"build_log",
244+
tracing::Level::INFO,
229245
devenv.ui.message = "build log",
230-
devenv.ui.type = "build",
231246
activity_id = activity_id,
232-
line = %log_line
247+
line = %log_line,
248+
"Build log: {}", log_line
233249
);
234-
span.in_scope(|| {
235-
info!(
236-
target: "devenv.ui.log",
237-
stdout = %log_line,
238-
"Build output: {}", log_line
239-
);
240-
});
241250
}
242251
}
243252
_ => {
@@ -317,10 +326,12 @@ fn parse_log_line(line: &str) -> Result<LogEntry> {
317326
async fn main() -> Result<()> {
318327
let shutdown = Shutdown::new();
319328

320-
tokio::select! {
321-
_ = run_replay(shutdown.clone()) => {}
322-
_ = shutdown.wait_for_shutdown() => {}
323-
}
329+
// Run replay and then wait for shutdown
330+
run_replay(shutdown.clone()).await?;
331+
332+
// Keep running until Ctrl+C
333+
eprintln!("Replay complete. Press Ctrl+C to exit.");
334+
shutdown.wait_for_shutdown().await;
324335

325336
Ok(())
326337
}
@@ -372,18 +383,23 @@ async fn run_replay(shutdown: Arc<Shutdown>) -> Result<()> {
372383

373384
// Start TUI in background
374385
let tui_shutdown = shutdown.clone();
375-
tokio::spawn(async move { devenv_tui::app::run_app(model, tui_shutdown).await });
386+
let tui_handle = tokio::spawn(async move {
387+
match devenv_tui::app::run_app(model, tui_shutdown).await {
388+
Ok(_) => eprintln!("TUI exited normally"),
389+
Err(e) => eprintln!("TUI error: {}", e),
390+
}
391+
});
376392

377393
// Create main operation
378394
let main_op_id = OperationId::new("replay");
379395

380-
// Start replay operation via tracing
381-
info!(
382-
target: "devenv.ui",
383-
id = %main_op_id,
384-
message = format!("Replaying {} log entries", entries.len()),
385-
"Starting log replay"
396+
// Start replay operation via tracing - using a span so the TUI layer can capture it
397+
let replay_span = debug_span!(
398+
"replay_operation",
399+
devenv.ui.message = format!("Replaying {} log entries", entries.len()),
400+
devenv.ui.id = %main_op_id,
386401
);
402+
let _guard = replay_span.enter();
387403

388404
// Set current operation for Nix processor
389405
nix_processor.set_current_operation(main_op_id.clone());
@@ -441,9 +457,14 @@ async fn run_replay(shutdown: Arc<Shutdown>) -> Result<()> {
441457
}
442458
}
443459

444-
// Give TUI a moment to display the final state
445-
sleep(Duration::from_millis(100)).await;
460+
// Keep the span open and let activities continue to be displayed
461+
// Don't drop _guard here - keep the replay operation active
462+
info!("Replay finished. Activities are still visible in the TUI.");
463+
464+
// Check if TUI is still running
465+
if tui_handle.is_finished() {
466+
eprintln!("Warning: TUI task has already exited!");
467+
}
446468

447-
// Span will close automatically when _guard is dropped, completing the operation
448469
Ok(())
449470
}

devenv-tui/src/tracing_layer.rs

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -112,8 +112,9 @@ where
112112
}
113113
}
114114

115-
// Handle spans with devenv.ui.message
116-
let has_ui_message = visitor.fields.contains_key("devenv.ui.message");
115+
// Handle spans with devenv.ui.message or devenv.user_message (backward compatibility)
116+
let has_ui_message = visitor.fields.contains_key("devenv.ui.message")
117+
|| visitor.fields.contains_key("devenv.user_message");
117118
if has_ui_message {
118119
let operation_id = visitor
119120
.fields
@@ -125,6 +126,7 @@ where
125126
let message = visitor
126127
.fields
127128
.get("devenv.ui.message")
129+
.or_else(|| visitor.fields.get("devenv.user_message"))
128130
.unwrap_or(&metadata.name().to_string())
129131
.clone();
130132

@@ -626,13 +628,12 @@ where
626628
}
627629
}
628630
}
629-
"devenv.nix.build" if event.metadata().name() == "nix_phase_progress" => {
630-
if let (Some(operation_id_str), Some(activity_id_str), Some(phase)) = (
631-
visitor.fields.get("devenv.ui.id"),
631+
"devenv.nix.build" => {
632+
// Handle phase updates
633+
if let (Some(activity_id_str), Some(phase)) = (
632634
visitor.fields.get("activity_id"),
633635
visitor.fields.get("phase"),
634636
) {
635-
let _operation_id = OperationId::new(operation_id_str.clone());
636637
if let Ok(activity_id) = activity_id_str.parse::<u64>() {
637638
self.update_model(|model| {
638639
if let Some(activity) = model.activities.get_mut(&activity_id)
@@ -644,17 +645,17 @@ where
644645
});
645646
}
646647
}
647-
}
648-
"devenv.nix.build" if event.metadata().name() == "build_log" => {
648+
// Handle build logs
649649
if let (Some(activity_id_str), Some(line)) = (
650650
visitor.fields.get("activity_id"),
651651
visitor.fields.get("line"),
652-
)
653-
&& let Ok(activity_id) = activity_id_str.parse::<u64>() {
652+
) {
653+
if let Ok(activity_id) = activity_id_str.parse::<u64>() {
654654
self.update_model(|model| {
655655
model.add_build_log(activity_id, line.clone());
656656
});
657657
}
658+
}
658659
}
659660
"devenv.nix.eval" if event.metadata().name() == "nix_evaluation_progress" => {
660661
if let (Some(operation_id_str), Some(files_str)) = (

devenv/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,11 @@ default-run = "devenv"
1212
[dependencies]
1313
devenv-eval-cache.workspace = true
1414
devenv-tasks.workspace = true
15+
devenv-tui.workspace = true
1516
devenv-cache-core.workspace = true
1617
http-client-tls.workspace = true
1718
nix-conf-parser.workspace = true
19+
tokio-shutdown.workspace = true
1820

1921
clap.workspace = true
2022
cli-table.workspace = true
@@ -40,7 +42,6 @@ sha2.workspace = true
4042
sqlx.workspace = true
4143
tempfile.workspace = true
4244
tokio.workspace = true
43-
tokio-shutdown.workspace = true
4445
tracing.workspace = true
4546
tracing-subscriber.workspace = true
4647
tracing-indicatif.workspace = true

0 commit comments

Comments
 (0)