Files
codex/prs/bolinfest/study/PR-2245-study.md
2025-09-02 15:17:45 -07:00

173 lines
5.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
**Asynchronous Diff UI: DOs and DONTs**
**DOs**
- **Use tokio::spawn for work:** Offload long-running tasks to keep the UI responsive.
```rust
let tx = self.app_event_tx.clone();
tokio::spawn(async move {
let text = match get_git_diff().await {
Ok((true, diff)) => diff,
Ok((false, _)) => "`/diff` — _not inside a git repository_".to_string(),
Err(e) => format!("Failed to compute diff: {e}"),
};
let _ = tx.send(AppEvent::DiffResult(text));
});
```
- **Make helpers async:** Switch to `tokio::process::Command` and `.await` process I/O.
```rust
use std::{io, process::Stdio};
use tokio::process::Command;
async fn run_git_capture_diff(args: &[&str]) -> io::Result<String> {
let output = Command::new("git").args(args)
.stdout(Stdio::piped()).stderr(Stdio::null())
.output().await?;
if output.status.success() || output.status.code() == Some(1) {
Ok(String::from_utf8_lossy(&output.stdout).into_owned())
} else {
Err(io::Error::new(io::ErrorKind::Other, format!("git {:?} failed", args)))
}
}
```
- **Report results via AppEvent:** Define and handle a dedicated event for the async result.
```rust
// app_event.rs
pub(crate) enum AppEvent {
// ...
DiffResult(String),
}
// app.rs
AppEvent::DiffResult(text) => {
if let AppState::Chat { widget } = &mut self.app_state {
widget.add_diff_output(text);
}
}
```
- **Show a progress indicator:** Set a running state and update status text while the task runs.
```rust
// chatwidget.rs
pub(crate) fn add_diff_in_progress(&mut self) {
self.bottom_pane.set_task_running(true);
self.bottom_pane.update_status_text("computing diff".to_string());
self.request_redraw();
}
pub(crate) fn add_diff_output(&mut self, diff_output: String) {
self.bottom_pane.set_task_running(false);
self.add_to_history(&history_cell::new_diff_output(diff_output));
self.mark_needs_redraw();
}
```
- **Gate status updates through the view:** Only update when the status view is active.
```rust
// bottom_pane/mod.rs
pub(crate) fn update_status_text(&mut self, text: String) {
if !self.is_task_running || !self.status_view_active {
return;
}
if let Some(mut view) = self.active_view.take() {
view.update_status_text(text);
self.active_view = Some(view);
self.request_redraw();
}
}
```
- **Parallelize independent subprocesses:** Use `tokio::join!` and `JoinSet` for concurrency.
```rust
// run tracked diff + list untracked in parallel
let (tracked_diff_res, untracked_res) = tokio::join!(
run_git_capture_diff(&["diff", "--color"]),
run_git_capture_stdout(&["ls-files", "--others", "--exclude-standard"]),
);
let tracked_diff = tracked_diff_res?;
let untracked_list = untracked_res?;
// diff untracked files concurrently
let null_path = "/dev/null".to_string();
let mut join_set = tokio::task::JoinSet::new();
for f in untracked_list.lines().map(str::trim).filter(|s| !s.is_empty()) {
let f = f.to_string();
let null_path = null_path.clone();
join_set.spawn(async move {
run_git_capture_diff(&["diff","--color","--no-index","--",&null_path,&f]).await
});
}
let mut untracked_diff = String::new();
while let Some(res) = join_set.join_next().await {
match res {
Ok(Ok(diff)) => untracked_diff.push_str(&diff),
Ok(Err(e)) if e.kind() == io::ErrorKind::NotFound => {},
Ok(Err(e)) => return Err(e),
Err(_) => {}
}
}
```
- **Check repo state first:** Short-circuit gracefully when not inside a Git repo.
```rust
async fn inside_git_repo() -> io::Result<bool> {
let status = Command::new("git")
.args(["rev-parse", "--is-inside-work-tree"])
.stdout(Stdio::null()).stderr(Stdio::null())
.status().await;
match status {
Ok(s) if s.success() => Ok(true),
Ok(_) => Ok(false),
Err(e) => Err(e),
}
}
```
**DONTs**
- **Dont block the UI thread:** Avoid synchronous process I/O in the event loop.
```rust
// Bad: blocks the reactor and UI
let out = std::process::Command::new("git").arg("diff").output().unwrap();
// Good: async process
let out = tokio::process::Command::new("git").arg("diff").output().await?;
```
- **Dont spawn std threads in Tokio contexts:** Prefer `tokio::spawn` to integrate with the runtime.
```rust
// Bad
std::thread::spawn(move || { /* ... */ });
// Good
tokio::spawn(async move { /* ... */ });
```
- **Dont mutate UI from background tasks:** Send an event; handle all UI updates on the main thread.
```rust
// Bad
tokio::spawn(async move {
widget.add_diff_output("...".to_string()); // UI from background
});
// Good
tokio::spawn(async move {
let _ = tx.send(AppEvent::DiffResult("...".to_string()));
});
```
- **Dont forget to end the progress state:** Always clear `task_running` when the task completes.
```rust
// Ensure this runs on completion paths
self.bottom_pane.set_task_running(false);
```
- **Dont ignore Gits special exit codes:** Treat diff exit code 1 as success; surface real failures.
```rust
// Correct handling
if output.status.success() || output.status.code() == Some(1) {
// differences present or clean
} else {
return Err(io::Error::new(io::ErrorKind::Other, format!("git {:?} failed", args)));
}
```