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

182 lines
6.1 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.
**DOs**
- Bold keyword: Integrate tokio EventStream for input; multiplex with app events using `tokio::select!`.
```rust
use crossterm::event::EventStream;
use tokio::select;
use tokio_stream::StreamExt;
let mut crossterm_events = EventStream::new();
while let Some(app_ev) = {
select! {
maybe = app_event_rx.recv() => maybe, // App events (our channel)
Some(Ok(ev)) = crossterm_events.next() => match ev {
crossterm::event::Event::Key(k) => Some(AppEvent::KeyEvent(k)),
crossterm::event::Event::Resize(..) => Some(AppEvent::Redraw),
crossterm::event::Event::Paste(p) => Some(AppEvent::Paste(p.replace("\r", "\n"))),
_ => None,
},
}
} { handle_event(terminal, app_ev)?; }
```
- Bold keyword: Use `tokio::sync::mpsc::unbounded_channel` for app events (and tests).
```rust
use tokio::sync::mpsc::{unbounded_channel, UnboundedSender, UnboundedReceiver};
let (tx, rx): (UnboundedSender<AppEvent>, UnboundedReceiver<AppEvent>) = unbounded_channel();
#[derive(Clone, Debug)]
pub(crate) struct AppEventSender { pub app_event_tx: UnboundedSender<AppEvent> }
impl AppEventSender {
pub fn send(&self, ev: AppEvent) { let _ = self.app_event_tx.send(ev); }
}
```
- Bold keyword: Factor event logic into a reusable handler that returns “keep running”.
```rust
fn handle_event(&mut self, terminal: &mut tui::Tui, ev: AppEvent) -> Result<bool> {
match ev {
AppEvent::RequestRedraw => self.schedule_frame_in(REDRAW_DEBOUNCE),
AppEvent::Redraw => std::io::stdout().sync_update(|_| self.draw_next_frame(terminal))??,
AppEvent::ExitRequest | AppEvent::DispatchCommand(SlashCommand::Quit) => return Ok(false),
_ => { /* other cases... */ }
}
Ok(true)
}
```
- Bold keyword: Normalize pasted text to LF; many terminals paste CR.
```rust
match event {
crossterm::event::Event::Paste(p) => Some(AppEvent::Paste(p.replace("\r", "\n"))),
_ => None,
}
```
- Bold keyword: Debounce redraws to coalesce frames.
```rust
const REDRAW_DEBOUNCE: Duration = Duration::from_millis(1);
self.app_event_tx.send(AppEvent::RequestRedraw);
// ...
AppEvent::RequestRedraw => self.schedule_frame_in(REDRAW_DEBOUNCE),
```
- Bold keyword: Restore cursor using tracked coordinates, not a live query (avoids event-lock issues).
```rust
use crossterm::cursor::MoveTo;
use crossterm::queue;
queue!(
writer,
MoveTo(terminal.last_known_cursor_pos.x, terminal.last_known_cursor_pos.y)
).ok();
```
- Bold keyword: Keep key semantics consistent and predictable.
```rust
use crossterm::event::{KeyCode, KeyEvent, KeyEventKind, KeyModifiers};
match key_event {
KeyEvent { code: KeyCode::Char('c'), modifiers: KeyModifiers::CONTROL, kind: KeyEventKind::Press, .. } => {
self.app_event_tx.send(AppEvent::ExitRequest);
}
KeyEvent { code: KeyCode::Char('d'), modifiers: KeyModifiers::CONTROL, kind: KeyEventKind::Press, .. } => {
if widget.composer_is_empty() { self.app_event_tx.send(AppEvent::ExitRequest); }
else { self.dispatch_key_event(key_event); }
}
KeyEvent { kind: KeyEventKind::Press | KeyEventKind::Repeat, .. } => self.dispatch_key_event(key_event),
_ => {} // Ignore KeyEventKind::Release
}
```
- Bold keyword: Make entry points async and await the run loop.
```rust
pub async fn run_main(cli: Cli, config: Config, show_trust: bool) -> std::io::Result<()> {
run_ratatui_app(cli, config, show_trust).await
.map_err(|e| std::io::Error::other(e.to_string()))
}
async fn run_ratatui_app(cli: Cli, config: Config, show_trust: bool) -> eyre::Result<()> {
let mut app = App::new(config.clone(), cli.prompt, cli.images, show_trust);
app.run(&mut terminal).await?;
Ok(())
}
```
- Bold keyword: Explain non-obvious changes that are related but not obvious (e.g., cursor restore).
```text
PR note: Switched to EventStream; querying cursor position can contend with crossterms event lock.
Use last_known_cursor_pos to restore cursor without locking; fixes resize-time failures.
```
- Bold keyword: Map terminal resize → redraw, not a full rerender loop.
```rust
crossterm::event::Event::Resize(..) => Some(AppEvent::Redraw)
```
**DONTs**
- Bold keyword: Dont block on `crossterm::event::read()` or hold the event lock.
```rust
// ❌ Blocking pattern (avoids async multiplexing and can deadlock):
if crossterm::event::poll(Duration::from_millis(100))? {
let ev = crossterm::event::read()?; // holds event lock
}
```
- Bold keyword: Dont use `std::sync::mpsc` in async code and tests.
```rust
// ❌
let (tx, rx) = std::sync::mpsc::channel::<AppEvent>();
// ✅
let (tx, rx) = tokio::sync::mpsc::unbounded_channel::<AppEvent>();
```
- Bold keyword: Dont call `cursor::position()` or `terminal.get_cursor_position()` during rendering.
```rust
// ❌ May contend with crossterms event lock:
let cursor_pos = terminal.get_cursor_position()?;
// ✅ Use tracked position:
MoveTo(terminal.last_known_cursor_pos.x, terminal.last_known_cursor_pos.y)
```
- Bold keyword: Dont exit on Ctrl+D if the composer has content.
```rust
// ❌ Always exits:
if ctrl_d { return Ok(false); }
// ✅ Only exit when input is empty:
if ctrl_d && widget.composer_is_empty() { return Ok(false); }
```
- Bold keyword: Dont handle `KeyEventKind::Release`; it causes duplicate actions.
```rust
match key_event.kind {
KeyEventKind::Press | KeyEventKind::Repeat => self.dispatch_key_event(key_event),
_ => {} // ignore Release
}
```
- Bold keyword: Dont forget Cargo feature flags for crossterms event stream.
```toml
# ✅ Cargo.toml
crossterm = { version = "0.28.1", features = ["bracketed-paste", "event-stream"] }
tokio-stream = "0.1.17"
```
- Bold keyword: Dont leave tests using `try_iter()` from std mpsc; use `try_recv()` in a loop.
```rust
// ✅ With tokio channel:
let mut events = Vec::new();
while let Ok(ev) = rx.try_recv() { events.push(ev); }
```
- Bold keyword: Dont mix unrelated changes without context; add a brief rationale.
```text
Commit message: “Use tracked cursor pos after EventStream refactor”
Rationale: Avoids event-lock contention introduced by async event handling.
```