chore: removed tuitextarea
4c324c98
9 file(s) · +79 −123
| 67 | 67 | - **`Method::parse`, not `FromStr`** — deliberately not the trait, to dodge |
|
| 68 | 68 | a clippy lint; nothing else depends on `FromStr`. |
|
| 69 | 69 | - **Vim modes are `Normal` / `Insert` / `Command` / `Search`** — no Visual |
|
| 70 | - | mode. Insert mode is reused for both single-line field edits (`LineEdit`) |
|
| 71 | - | and the body `TextArea`; `app.editing` discriminates which. `Search` is the |
|
| 70 | + | mode. Insert mode is only single-line field edits (`LineEdit`), tracked by |
|
| 71 | + | `app.editing`; there is no in-app multi-line editor. `Search` is the |
|
| 72 | 72 | `/` sidebar filter: it re-applies on every keystroke, and `app.filter` (the |
|
| 73 | 73 | committed query) is deliberately separate from `app.search` (the live |
|
| 74 | 74 | prompt buffer) so `Esc` can drop the prompt without touching the filter. |
|
| 111 | 111 | drops input: every character comes back out in some span (there's a test). |
|
| 112 | 112 | A `syntect`-class dependency would be larger than the rest of the binary, |
|
| 113 | 113 | and JSON/XML/plain is all a request client shows. |
|
| 114 | - | - **The body has two renderers.** `tui-textarea` styles whole lines only, so |
|
| 115 | - | the Body tab renders a highlighted `Paragraph` in Normal mode and the raw |
|
| 116 | - | `TextArea` in Insert mode. The textarea stays the source of truth either |
|
| 117 | - | way; the read-only view scrolls by moving *its* cursor, which is why `j`/`k` |
|
| 118 | - | on the Body tab drive `CursorMove`. |
|
| 114 | + | - **The body is read-only in the TUI.** It renders as a highlighted |
|
| 115 | + | `Paragraph`; edits go through `$EDITOR` (`e`). `app.body_text` is the source |
|
| 116 | + | of truth and `app.body_scroll` is a plain offset clamped at render, which is |
|
| 117 | + | why `j`/`k` on the Body tab just move that offset. There is deliberately no |
|
| 118 | + | in-app text editor — that's what dropped the `tui-textarea` dependency. |
|
| 119 | 119 | - **Dynamic variables live in the `{{…}}` namespace**, not a second syntax: |
|
| 120 | 120 | `{{uuid}}`, `{{randomInt(1,10)}}`, `{{isoTimestamp}}`. A collection variable |
|
| 121 | 121 | shadows a dynamic one of the same name (`{{$name}}` forces the dynamic one), |
|
| 202 | 202 | "tempfile", |
|
| 203 | 203 | "thiserror", |
|
| 204 | 204 | "tokio", |
|
| 205 | - | "tui-textarea", |
|
| 206 | 205 | "url", |
|
| 207 | 206 | "uuid", |
|
| 208 | 207 | "wiremock", |
|
| 1990 | 1989 | version = "0.2.5" |
|
| 1991 | 1990 | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 1992 | 1991 | checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" |
|
| 1993 | - | ||
| 1994 | - | [[package]] |
|
| 1995 | - | name = "tui-textarea" |
|
| 1996 | - | version = "0.7.0" |
|
| 1997 | - | source = "registry+https://github.com/rust-lang/crates.io-index" |
|
| 1998 | - | checksum = "0a5318dd619ed73c52a9417ad19046724effc1287fb75cdcc4eca1d6ac1acbae" |
|
| 1999 | - | dependencies = [ |
|
| 2000 | - | "crossterm", |
|
| 2001 | - | "ratatui", |
|
| 2002 | - | "unicode-width 0.2.0", |
|
| 2003 | - | ] |
|
| 2004 | 1992 | ||
| 2005 | 1993 | [[package]] |
|
| 2006 | 1994 | name = "unicode-ident" |
|
| 29 | 29 | serde_yaml = "0.9.34" |
|
| 30 | 30 | thiserror = "2.0.19" |
|
| 31 | 31 | tokio = { version = "1.53.1", features = ["rt-multi-thread", "macros"] } |
|
| 32 | - | tui-textarea = "0.7.0" |
|
| 33 | 32 | url = "2.5.8" |
|
| 34 | 33 | uuid = { version = "1.24.0", features = ["v4", "serde"] } |
|
| 35 | 34 |
| 85 | 85 | ||
| 86 | 86 | Tables: `space` toggle row, `i` edit, `a` add, `d` delete, `m` cycle method, `p` edit URL. |
|
| 87 | 87 | ||
| 88 | - | Body/response: `i` edit inline, `e` open in `$EDITOR`, `j`/`k`/`d`/`u`/`g`/`G` scroll. |
|
| 88 | + | Body/response: `e` open in `$EDITOR`, `j`/`k`/`d`/`u`/`g`/`G` scroll. |
|
| 89 | 89 | ||
| 90 | 90 | Press `?` in the TUI for the full list. |
|
| 91 | 91 |
| 16 | 16 | use ratatui::backend::CrosstermBackend; |
|
| 17 | 17 | use serde::{Deserialize, Serialize}; |
|
| 18 | 18 | use tokio::sync::mpsc; |
|
| 19 | - | use tui_textarea::TextArea; |
|
| 20 | 19 | use uuid::Uuid; |
|
| 21 | 20 | ||
| 22 | 21 | use crate::http::{HttpResponse, OAuthToken, SendOutcome, send_with_auth, split_url_input}; |
|
| 314 | 313 | pub input: LineEdit, |
|
| 315 | 314 | /// After committing a new row's key, continue to its value cell. |
|
| 316 | 315 | pub chain_to_value: bool, |
|
| 317 | - | pub textarea: TextArea<'static>, |
|
| 316 | + | /// Request body text — source of truth. Edited only via `$EDITOR`. |
|
| 317 | + | pub body_text: String, |
|
| 318 | + | /// Scroll offset of the read-only Body view, clamped at render. |
|
| 319 | + | pub body_scroll: usize, |
|
| 318 | 320 | ||
| 319 | 321 | /// Scroll offset of the Docs tab, reset when another request is opened. |
|
| 320 | 322 | pub docs_scroll: usize, |
|
| 367 | 369 | editing: None, |
|
| 368 | 370 | input: LineEdit::default(), |
|
| 369 | 371 | chain_to_value: false, |
|
| 370 | - | textarea: TextArea::default(), |
|
| 372 | + | body_text: String::new(), |
|
| 373 | + | body_scroll: 0, |
|
| 371 | 374 | docs_scroll: 0, |
|
| 372 | 375 | response: None, |
|
| 373 | 376 | response_scroll: 0, |
|
| 562 | 565 | .body |
|
| 563 | 566 | .clone() |
|
| 564 | 567 | .unwrap_or_default(); |
|
| 565 | - | self.set_textarea_text(&body); |
|
| 568 | + | self.set_body_text(&body); |
|
| 566 | 569 | self.focus = Focus::Editor; |
|
| 567 | 570 | } |
|
| 568 | 571 | ||
| 569 | - | pub fn set_textarea_text(&mut self, text: &str) { |
|
| 570 | - | let lines: Vec<String> = if text.is_empty() { |
|
| 571 | - | vec![String::new()] |
|
| 572 | - | } else { |
|
| 573 | - | text.lines().map(String::from).collect() |
|
| 574 | - | }; |
|
| 575 | - | self.textarea = TextArea::from(lines); |
|
| 576 | - | self.textarea |
|
| 577 | - | .set_cursor_line_style(ratatui::style::Style::default()); |
|
| 572 | + | pub fn set_body_text(&mut self, text: &str) { |
|
| 573 | + | self.body_text = text.to_string(); |
|
| 574 | + | self.body_scroll = 0; |
|
| 578 | 575 | } |
|
| 579 | 576 | ||
| 580 | - | /// Write the textarea contents back into the selected request body. |
|
| 577 | + | /// Write the body text back into the selected request body. |
|
| 581 | 578 | pub fn commit_body(&mut self) { |
|
| 582 | 579 | let Some(idx) = self.selected else { return }; |
|
| 583 | - | let text = self.textarea.lines().join("\n"); |
|
| 584 | - | let text = text.trim_end_matches('\n').to_string(); |
|
| 580 | + | let text = self.body_text.trim_end_matches('\n').to_string(); |
|
| 585 | 581 | let req = &mut self.collection.requests[idx]; |
|
| 586 | 582 | let new = if text.trim().is_empty() { |
|
| 587 | 583 | None |
|
| 1463 | 1459 | app.collection.requests[i].body = Some(content.clone()); |
|
| 1464 | 1460 | app.dirty = true; |
|
| 1465 | 1461 | } |
|
| 1466 | - | app.set_textarea_text(&content); |
|
| 1462 | + | app.set_body_text(&content); |
|
| 1467 | 1463 | app.status = "Body updated from editor".into(); |
|
| 1468 | 1464 | } |
|
| 1469 | 1465 | (ExternalEdit::Body, Ok(s)) => { |
|
| 1 | 1 | //! Vim-style key handling. |
|
| 2 | 2 | ||
| 3 | 3 | use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; |
|
| 4 | - | use tui_textarea::CursorMove; |
|
| 5 | 4 | ||
| 6 | 5 | use crate::app::{ |
|
| 7 | 6 | App, CellCol, EditTarget, EditorTab, ExternalEdit, Focus, Mode, Popup, SidebarRow, |
|
| 126 | 125 | app.dirty = true; |
|
| 127 | 126 | if app.selected == Some(idx) { |
|
| 128 | 127 | app.selected = None; |
|
| 129 | - | app.set_textarea_text(""); |
|
| 128 | + | app.set_body_text(""); |
|
| 130 | 129 | } |
|
| 131 | 130 | app.selected = app.selected.map(|s| if s > idx { s - 1 } else { s }); |
|
| 132 | 131 | app.rebuild_sidebar(); |
|
| 177 | 176 | app.toggle_row(t, app.table_row); |
|
| 178 | 177 | } |
|
| 179 | 178 | } |
|
| 179 | + | // Body has no in-app editor — use `e` for `$EDITOR`. Tables edit inline. |
|
| 180 | 180 | KeyCode::Char('i') | KeyCode::Char('a') => { |
|
| 181 | 181 | let is_add = key.code == KeyCode::Char('a'); |
|
| 182 | - | match app.tab { |
|
| 183 | - | EditorTab::Body => { |
|
| 184 | - | app.mode = Mode::Insert; |
|
| 185 | - | app.status = "Editing body — Esc to finish".into(); |
|
| 186 | - | } |
|
| 187 | - | _ => { |
|
| 188 | - | if let Some(t) = app.tab.table() { |
|
| 189 | - | if is_add { |
|
| 190 | - | app.add_row(t); |
|
| 191 | - | } else if app.table_row < app.table_len(t) { |
|
| 192 | - | app.chain_to_value = false; |
|
| 193 | - | app.start_edit(EditTarget::Cell { |
|
| 194 | - | table: t, |
|
| 195 | - | row: app.table_row, |
|
| 196 | - | col: CellCol::Value, |
|
| 197 | - | }); |
|
| 198 | - | } |
|
| 199 | - | } |
|
| 182 | + | if let Some(t) = app.tab.table() { |
|
| 183 | + | if is_add { |
|
| 184 | + | app.add_row(t); |
|
| 185 | + | } else if app.table_row < app.table_len(t) { |
|
| 186 | + | app.chain_to_value = false; |
|
| 187 | + | app.start_edit(EditTarget::Cell { |
|
| 188 | + | table: t, |
|
| 189 | + | row: app.table_row, |
|
| 190 | + | col: CellCol::Value, |
|
| 191 | + | }); |
|
| 200 | 192 | } |
|
| 201 | 193 | } |
|
| 202 | 194 | } |
|
| 220 | 212 | } |
|
| 221 | 213 | } |
|
| 222 | 214 | ||
| 223 | - | /// Body-tab movement. The read-only (highlighted) body view follows the |
|
| 224 | - | /// textarea's cursor, so scrolling it is just moving that cursor. Returns |
|
| 225 | - | /// whether the key was consumed. |
|
| 215 | + | /// Body-tab movement. The read-only body view is a plain scroll offset, |
|
| 216 | + | /// clamped against the rendered height in `ui::draw_body`. Returns whether |
|
| 217 | + | /// the key was consumed. |
|
| 226 | 218 | fn body_scroll(app: &mut App, key: KeyEvent) -> bool { |
|
| 227 | - | let moves: &[CursorMove] = match key.code { |
|
| 228 | - | KeyCode::Char('j') | KeyCode::Down => &[CursorMove::Down], |
|
| 229 | - | KeyCode::Char('k') | KeyCode::Up => &[CursorMove::Up], |
|
| 230 | - | KeyCode::Char('g') => &[CursorMove::Top], |
|
| 231 | - | KeyCode::Char('G') => &[CursorMove::Bottom], |
|
| 232 | - | KeyCode::Char('d') => &[CursorMove::Down; 15], |
|
| 233 | - | KeyCode::Char('u') => &[CursorMove::Up; 15], |
|
| 219 | + | match key.code { |
|
| 220 | + | KeyCode::Char('j') | KeyCode::Down => app.body_scroll += 1, |
|
| 221 | + | KeyCode::Char('k') | KeyCode::Up => app.body_scroll = app.body_scroll.saturating_sub(1), |
|
| 222 | + | KeyCode::Char('g') => app.body_scroll = 0, |
|
| 223 | + | KeyCode::Char('G') => app.body_scroll = usize::MAX / 2, |
|
| 224 | + | KeyCode::Char('d') => app.body_scroll += 15, |
|
| 225 | + | KeyCode::Char('u') => app.body_scroll = app.body_scroll.saturating_sub(15), |
|
| 234 | 226 | _ => return false, |
|
| 235 | - | }; |
|
| 236 | - | for m in moves { |
|
| 237 | - | app.textarea.move_cursor(*m); |
|
| 238 | 227 | } |
|
| 239 | 228 | true |
|
| 240 | 229 | } |
|
| 305 | 294 | } |
|
| 306 | 295 | return; |
|
| 307 | 296 | } |
|
| 308 | - | // Body textarea editing. |
|
| 297 | + | // No in-app body editor: body is edited via `$EDITOR` (`e`). Any stray |
|
| 298 | + | // Insert-mode key just returns to Normal. |
|
| 309 | 299 | if key.code == KeyCode::Esc { |
|
| 310 | - | app.commit_body(); |
|
| 311 | 300 | app.mode = Mode::Normal; |
|
| 312 | - | app.status = "Body updated".into(); |
|
| 313 | - | return; |
|
| 314 | 301 | } |
|
| 315 | - | app.textarea.input(key); |
|
| 316 | 302 | } |
|
| 317 | 303 | ||
| 318 | 304 | // ----- Search mode (sidebar filter) ----- |
|
| 221 | 221 | } |
|
| 222 | 222 | } |
|
| 223 | 223 | ||
| 224 | - | /// The body is syntax-highlighted while read-only and handed to the raw |
|
| 225 | - | /// `TextArea` during editing: `tui-textarea` styles whole lines only, so one |
|
| 226 | - | /// widget cannot do both. The textarea stays the source of truth either way — |
|
| 227 | - | /// the read-only view renders its lines and follows its cursor. |
|
| 224 | + | /// The body is syntax-highlighted and read-only; edits go through `$EDITOR` |
|
| 225 | + | /// (`e`). `body_scroll` is a plain offset, clamped here against the content. |
|
| 228 | 226 | fn draw_body(f: &mut Frame, app: &mut App, area: Rect) { |
|
| 229 | - | // Insert mode with no `editing` target means the textarea has the keys. |
|
| 230 | - | if app.mode == Mode::Insert && app.editing.is_none() { |
|
| 231 | - | app.textarea.set_block( |
|
| 232 | - | Block::default() |
|
| 233 | - | .title(" Body — Esc: done ") |
|
| 234 | - | .borders(Borders::NONE), |
|
| 235 | - | ); |
|
| 236 | - | f.render_widget(&app.textarea, area); |
|
| 237 | - | return; |
|
| 238 | - | } |
|
| 239 | - | ||
| 240 | 227 | let block = Block::default() |
|
| 241 | - | .title(" Body — i: edit · e: $EDITOR · j/k: scroll ") |
|
| 228 | + | .title(" Body — e: $EDITOR · j/k: scroll ") |
|
| 242 | 229 | .borders(Borders::NONE); |
|
| 243 | 230 | let inner = block.inner(area); |
|
| 244 | 231 | f.render_widget(block, area); |
|
| 245 | 232 | ||
| 246 | - | let text = app.textarea.lines().join("\n"); |
|
| 247 | - | let mut lines = highlight::highlight(&text, true); |
|
| 248 | - | // Mark where `i` would drop the cursor. |
|
| 249 | - | let cursor_row = app.textarea.cursor().0; |
|
| 250 | - | if let Some(line) = lines.get_mut(cursor_row) { |
|
| 251 | - | *line = std::mem::take(line).style(Style::default().bg(Color::Rgb(40, 40, 40))); |
|
| 252 | - | } |
|
| 253 | - | let offset = centered_offset(cursor_row, lines.len(), inner.height as usize); |
|
| 254 | - | f.render_widget(Paragraph::new(lines).scroll((offset as u16, 0)), inner); |
|
| 233 | + | let lines = highlight::highlight(&app.body_text, true); |
|
| 234 | + | let max = lines.len().saturating_sub(1); |
|
| 235 | + | app.body_scroll = app.body_scroll.min(max); |
|
| 236 | + | f.render_widget( |
|
| 237 | + | Paragraph::new(lines).scroll((app.body_scroll as u16, 0)), |
|
| 238 | + | inner, |
|
| 239 | + | ); |
|
| 255 | 240 | } |
|
| 256 | 241 | ||
| 257 | 242 | /// Read-only view of what the spec says about this request: the operation |
|
| 601 | 586 | Line::raw(""), |
|
| 602 | 587 | Line::styled("Body tab", Style::default().add_modifier(Modifier::BOLD)), |
|
| 603 | 588 | Line::raw(" j/k, d/u scroll · g/G top/bottom"), |
|
| 604 | - | Line::raw(" i edit inline (Esc to finish)"), |
|
| 605 | - | Line::raw(" e open in $EDITOR"), |
|
| 589 | + | Line::raw(" e edit in $EDITOR"), |
|
| 606 | 590 | Line::raw(""), |
|
| 607 | 591 | Line::styled("Docs tab", Style::default().add_modifier(Modifier::BOLD)), |
|
| 608 | 592 | Line::raw(" types, enums and descriptions from the spec (* = required)"), |
|
| 200 | 200 | handle_key(&mut app, key(KeyCode::Enter)); |
|
| 201 | 201 | assert_eq!(app.selected, Some(1)); // createPet |
|
| 202 | 202 | assert_eq!(app.focus, Focus::Editor); |
|
| 203 | - | // body loaded into textarea |
|
| 204 | - | assert!(app.textarea.lines().join("\n").contains("Fido")); |
|
| 203 | + | // body loaded into the body view |
|
| 204 | + | assert!(app.body_text.contains("Fido")); |
|
| 205 | 205 | } |
|
| 206 | 206 | ||
| 207 | 207 | #[test] |
|
| 417 | 417 | } |
|
| 418 | 418 | ||
| 419 | 419 | #[test] |
|
| 420 | - | fn body_textarea_editing() { |
|
| 420 | + | fn body_has_no_inline_editor() { |
|
| 421 | 421 | let mut app = test_app(); |
|
| 422 | 422 | // select createPet (has a body) |
|
| 423 | 423 | app.select_request(1); |
|
| 424 | 424 | handle_key(&mut app, char_key(']')); |
|
| 425 | 425 | handle_key(&mut app, char_key(']')); // Body tab |
|
| 426 | 426 | assert_eq!(app.tab, EditorTab::Body); |
|
| 427 | + | // `i` no longer opens an in-app editor; the body is edited via `$EDITOR`. |
|
| 427 | 428 | handle_key(&mut app, char_key('i')); |
|
| 428 | - | assert_eq!(app.mode, Mode::Insert); |
|
| 429 | - | handle_key(&mut app, key(KeyCode::Esc)); |
|
| 430 | 429 | assert_eq!(app.mode, Mode::Normal); |
|
| 430 | + | // `e` queues an external edit instead. |
|
| 431 | + | handle_key(&mut app, char_key('e')); |
|
| 432 | + | assert_eq!(app.pending_external, Some(cielago::app::ExternalEdit::Body)); |
|
| 431 | 433 | assert!( |
|
| 432 | 434 | app.collection.requests[1] |
|
| 433 | 435 | .body |
|
| 441 | 443 | fn body_tab_scrolls_the_read_only_view() { |
|
| 442 | 444 | let mut app = test_app(); |
|
| 443 | 445 | app.select_request(1); |
|
| 444 | - | app.set_textarea_text(&(1..=40).map(|i| format!("line {i}\n")).collect::<String>()); |
|
| 446 | + | app.set_body_text(&(1..=40).map(|i| format!("line {i}\n")).collect::<String>()); |
|
| 445 | 447 | app.tab = EditorTab::Body; |
|
| 446 | 448 | ||
| 447 | - | // The highlighted body view follows the textarea cursor. |
|
| 449 | + | // The read-only body view scrolls with a plain offset. |
|
| 448 | 450 | handle_key(&mut app, char_key('j')); |
|
| 449 | 451 | handle_key(&mut app, char_key('j')); |
|
| 450 | - | assert_eq!(app.textarea.cursor().0, 2); |
|
| 452 | + | assert_eq!(app.body_scroll, 2); |
|
| 451 | 453 | handle_key(&mut app, char_key('k')); |
|
| 452 | - | assert_eq!(app.textarea.cursor().0, 1); |
|
| 454 | + | assert_eq!(app.body_scroll, 1); |
|
| 453 | 455 | handle_key(&mut app, char_key('d')); |
|
| 454 | - | assert_eq!(app.textarea.cursor().0, 16); |
|
| 456 | + | assert_eq!(app.body_scroll, 16); |
|
| 455 | 457 | handle_key(&mut app, char_key('u')); |
|
| 456 | - | assert_eq!(app.textarea.cursor().0, 1); |
|
| 457 | - | handle_key(&mut app, char_key('G')); |
|
| 458 | - | assert!(app.textarea.cursor().0 >= 39); |
|
| 458 | + | assert_eq!(app.body_scroll, 1); |
|
| 459 | 459 | handle_key(&mut app, char_key('g')); |
|
| 460 | - | assert_eq!(app.textarea.cursor().0, 0); |
|
| 460 | + | assert_eq!(app.body_scroll, 0); |
|
| 461 | 461 | ||
| 462 | 462 | // `d` scrolls here rather than deleting a row, but the other editor keys |
|
| 463 | 463 | // still reach their handlers. |
|
| 464 | 464 | assert_eq!(app.collection.requests[1].method, Method::Post); |
|
| 465 | 465 | handle_key(&mut app, char_key('m')); |
|
| 466 | 466 | assert_eq!(app.collection.requests[1].method, Method::Put); |
|
| 467 | + | // `i` on the Body tab is a no-op, not an editor. |
|
| 467 | 468 | handle_key(&mut app, char_key('i')); |
|
| 468 | - | assert_eq!(app.mode, Mode::Insert); |
|
| 469 | + | assert_eq!(app.mode, Mode::Normal); |
|
| 469 | 470 | } |
|
| 470 | 471 | ||
| 471 | 472 | #[test] |
|
| 77 | 77 | } |
|
| 78 | 78 | ||
| 79 | 79 | #[test] |
|
| 80 | - | fn body_falls_back_to_the_plain_textarea_while_editing() { |
|
| 80 | + | fn body_stays_highlighted_and_read_only() { |
|
| 81 | + | // There is no in-app body editor — the body is edited via `$EDITOR`, so it |
|
| 82 | + | // renders syntax-highlighted regardless of mode. |
|
| 81 | 83 | let mut app = test_app(); |
|
| 82 | 84 | app.tab = EditorTab::Body; |
|
| 83 | 85 | app.mode = Mode::Insert; |
|
| 84 | 86 | let buf = render(&mut app, 100, 40); |
|
| 85 | 87 | ||
| 86 | 88 | assert!(screen(&buf).contains("\"name\"")); |
|
| 87 | - | assert_eq!(fg_of(&buf, "\"name\""), Some(Color::Reset)); |
|
| 89 | + | assert_eq!(fg_of(&buf, "\"name\""), Some(Color::Cyan)); |
|
| 88 | 90 | } |
|
| 89 | 91 | ||
| 90 | 92 | #[test] |
|
| 181 | 183 | app.tab = EditorTab::Body; |
|
| 182 | 184 | render(&mut app, 20, 8); |
|
| 183 | 185 | ||
| 184 | - | app.set_textarea_text(""); |
|
| 186 | + | app.set_body_text(""); |
|
| 185 | 187 | render(&mut app, 100, 40); |
|
| 186 | 188 | ||
| 187 | - | app.set_textarea_text(&(1..=200).map(|i| format!("[{i}]\n")).collect::<String>()); |
|
| 188 | - | app.textarea.move_cursor(tui_textarea::CursorMove::Bottom); |
|
| 189 | + | app.set_body_text(&(1..=200).map(|i| format!("[{i}]\n")).collect::<String>()); |
|
| 190 | + | app.body_scroll = usize::MAX / 2; |
|
| 189 | 191 | render(&mut app, 100, 40); |
|
| 190 | 192 | ||
| 191 | 193 | app.tab = EditorTab::Docs; |
|