mirror of
https://github.com/openai/codex.git
synced 2026-04-28 10:21:06 +03:00
151 lines
5.1 KiB
Markdown
151 lines
5.1 KiB
Markdown
**DOs**
|
||
|
||
- Use a dedicated paste event: route terminal paste directly through the app/event pipeline for a single, efficient update.
|
||
```
|
||
/* app.rs */
|
||
if let Event::Paste(pasted) = event {
|
||
app_event_tx.send(AppEvent::Paste(pasted));
|
||
}
|
||
|
||
/* app_event.rs */
|
||
pub(crate) enum AppEvent {
|
||
KeyEvent(KeyEvent),
|
||
Paste(String),
|
||
Scroll(i32),
|
||
}
|
||
```
|
||
|
||
- Choose a high collapse threshold: start with 1_000 (or at least 500) to avoid replacing everyday pastes and frustrating edits.
|
||
```
|
||
/* chat_composer.rs */
|
||
const LARGE_PASTE_CHAR_THRESHOLD: usize = 1_000;
|
||
```
|
||
|
||
- Keep the UI responsive but preserve real content: insert a short placeholder in the textarea, and store the full text for submission.
|
||
```
|
||
/* chat_composer.rs */
|
||
pub fn handle_paste(&mut self, pasted: String) -> bool {
|
||
let char_count = pasted.chars().count();
|
||
if char_count > LARGE_PASTE_CHAR_THRESHOLD {
|
||
let placeholder = format!("[Pasted Content {char_count} chars]");
|
||
self.textarea.insert_str(&placeholder);
|
||
self.pending_pastes.push((placeholder, pasted));
|
||
} else {
|
||
self.textarea.insert_str(&pasted);
|
||
}
|
||
self.sync_command_popup();
|
||
self.sync_file_search_popup();
|
||
true
|
||
}
|
||
```
|
||
|
||
- Expand placeholders on submit: ensure the model receives the original pasted text, not the placeholder.
|
||
```
|
||
/* chat_composer.rs */
|
||
let mut text = self.textarea.lines().join("\n");
|
||
for (placeholder, actual) in &self.pending_pastes {
|
||
if text.contains(placeholder) {
|
||
text = text.replace(placeholder, actual);
|
||
}
|
||
}
|
||
self.pending_pastes.clear();
|
||
InputResult::Submitted(text)
|
||
```
|
||
|
||
- Make deletion intuitive: if the cursor is at the end of a placeholder, a single Backspace removes the entire placeholder and its tracking entry.
|
||
```
|
||
/* chat_composer.rs */
|
||
fn try_remove_placeholder_at_cursor(&mut self) -> bool {
|
||
let (row, col) = self.textarea.cursor();
|
||
let line = self.textarea.lines().get(row).map(|s| s.as_str()).unwrap_or("");
|
||
if let Some(ph) = self.pending_pastes.iter().find_map(|(ph, _)| {
|
||
(col >= ph.len() && &line[col - ph.len()..col] == ph).then(|| ph.clone())
|
||
}) {
|
||
for _ in 0..ph.len() {
|
||
self.textarea.input(Input { key: Key::Backspace, ctrl: false, alt: false, shift: false });
|
||
}
|
||
self.pending_pastes.retain(|(p, _)| p != &ph);
|
||
return true;
|
||
}
|
||
false
|
||
}
|
||
```
|
||
|
||
- Keep tracking in sync after edits: drop any pending mapping whose placeholder no longer exists in the textarea.
|
||
```
|
||
/* chat_composer.rs */
|
||
let text_after = self.textarea.lines().join("\n");
|
||
self.pending_pastes.retain(|(ph, _)| text_after.contains(ph));
|
||
```
|
||
|
||
- Request a redraw when paste changes the UI: wire paste handling through the BottomPane.
|
||
```
|
||
/* bottom_pane/mod.rs */
|
||
pub fn handle_paste(&mut self, pasted: String) {
|
||
if self.active_view.is_none() && self.composer.handle_paste(pasted) {
|
||
self.request_redraw();
|
||
}
|
||
}
|
||
```
|
||
|
||
- Use `format!` with inlined variables for clarity and consistency.
|
||
```
|
||
let placeholder = format!("[Pasted Content {char_count} chars]");
|
||
```
|
||
|
||
- Keep snapshot tests clean and deterministic: start from a fresh composer per case; don’t leak keystrokes between cases; assert the exact frame.
|
||
```
|
||
#[test]
|
||
fn ui_snapshots() {
|
||
let mut terminal = Terminal::new(TestBackend::new(100, 10)).unwrap();
|
||
for (name, setup) in [("empty", None), ("large", Some("z".repeat(1_005)))] {
|
||
let (tx, _rx) = std::sync::mpsc::channel();
|
||
let sender = AppEventSender::new(tx);
|
||
let mut composer = ChatComposer::new(true, sender);
|
||
if let Some(p) = setup { composer.handle_paste(p); }
|
||
terminal.draw(|f| f.render_widget_ref(&composer, f.area())).unwrap();
|
||
insta::assert_snapshot!(name, terminal.backend());
|
||
}
|
||
}
|
||
```
|
||
|
||
**DON’Ts**
|
||
|
||
- Don’t set a tiny threshold (e.g., 100): it collapses normal pastes and makes editing painful.
|
||
```
|
||
/* Too aggressive */
|
||
const LARGE_PASTE_CHAR_THRESHOLD: usize = 100;
|
||
```
|
||
|
||
- Don’t synthesize per-character key events for paste: it’s slow, lossy for newlines, and bypasses intentional paste semantics.
|
||
```
|
||
/* Avoid this pattern */
|
||
for ch in pasted.chars() {
|
||
let ev = match ch {
|
||
'\n' | '\r' => KeyEvent::new(KeyCode::Enter, KeyModifiers::SHIFT),
|
||
_ => KeyEvent::new(KeyCode::Char(ch), KeyModifiers::empty()),
|
||
};
|
||
app_event_tx.send(AppEvent::KeyEvent(ev));
|
||
}
|
||
```
|
||
|
||
- Don’t ship placeholders to the model: always expand them before submission.
|
||
```
|
||
/* Buggy: submits placeholder text */
|
||
let text = self.textarea.lines().join("\n");
|
||
InputResult::Submitted(text) // <-- missing expansion over pending_pastes
|
||
```
|
||
|
||
- Don’t ignore stray characters in snapshots: a leading “t” (or any unexpected glyph) indicates state leakage or setup error.
|
||
```
|
||
/* Suspicious snapshot: investigate test setup */
|
||
"│t[Pasted Content 105 chars] │"
|
||
```
|
||
|
||
- Don’t keep stale pending mappings after edits: if a placeholder is partially or fully removed, drop its mapping.
|
||
```
|
||
/* Buggy: retains mappings even after placeholder is gone */
|
||
self.pending_pastes = self.pending_pastes; // no-op; should filter based on textarea contents
|
||
```
|
||
|
||
- Don’t add a config knob for the threshold prematurely: start with a sensible constant (e.g., 1_000) and adjust based on real-world feedback. |