chore: added wrapping when creating inside tui
137995f7
1 file(s) · +109 −23
| 5 | 5 | layout::{Alignment, Constraint, Layout}, |
|
| 6 | 6 | style::{Color, Modifier, Style}, |
|
| 7 | 7 | text::{Line, Span, Text}, |
|
| 8 | - | widgets::{Block, Borders, Clear, List, ListItem, ListState, Paragraph, Widget}, |
|
| 8 | + | widgets::{Block, Borders, Clear, List, ListItem, ListState, Paragraph, Widget, Wrap}, |
|
| 9 | 9 | }; |
|
| 10 | 10 | use crate::backend::Backend; |
|
| 11 | 11 | use crate::config; |
|
| 46 | 46 | filtered_indices: Option<Vec<usize>>, |
|
| 47 | 47 | is_remote: bool, |
|
| 48 | 48 | remote_url: Option<String>, |
|
| 49 | + | wrap_content: bool, |
|
| 50 | + | edit_scroll: u16, |
|
| 49 | 51 | } |
|
| 50 | 52 | ||
| 51 | 53 | impl App { |
|
| 77 | 79 | filtered_indices: None, |
|
| 78 | 80 | is_remote, |
|
| 79 | 81 | remote_url, |
|
| 82 | + | wrap_content: true, |
|
| 83 | + | edit_scroll: 0, |
|
| 80 | 84 | } |
|
| 81 | 85 | } |
|
| 82 | 86 | ||
| 246 | 250 | } |
|
| 247 | 251 | } |
|
| 248 | 252 | ||
| 253 | + | fn cursor_position_wrapped(&self, width: u16) -> (u16, u16) { |
|
| 254 | + | let w = width as usize; |
|
| 255 | + | if w == 0 { |
|
| 256 | + | return (0, 0); |
|
| 257 | + | } |
|
| 258 | + | let text = &self.create_content; |
|
| 259 | + | let mut visual_row: usize = 0; |
|
| 260 | + | let lines: Vec<&str> = if text.is_empty() { |
|
| 261 | + | vec![""] |
|
| 262 | + | } else if text.ends_with('\n') { |
|
| 263 | + | text.split('\n').collect() |
|
| 264 | + | } else { |
|
| 265 | + | text.split('\n').collect() |
|
| 266 | + | }; |
|
| 267 | + | let last_idx = lines.len() - 1; |
|
| 268 | + | for (i, line) in lines.iter().enumerate() { |
|
| 269 | + | let line_len = line.len(); |
|
| 270 | + | let wrapped_lines = if line_len == 0 { |
|
| 271 | + | 1 |
|
| 272 | + | } else { |
|
| 273 | + | (line_len + w - 1) / w |
|
| 274 | + | }; |
|
| 275 | + | if i < last_idx { |
|
| 276 | + | visual_row += wrapped_lines; |
|
| 277 | + | } else { |
|
| 278 | + | // cursor is at end of this last line |
|
| 279 | + | let cursor_col = if text.ends_with('\n') { 0 } else { line_len }; |
|
| 280 | + | let extra_rows = cursor_col / w; |
|
| 281 | + | let col = cursor_col % w; |
|
| 282 | + | visual_row += extra_rows; |
|
| 283 | + | return (col as u16, visual_row as u16); |
|
| 284 | + | } |
|
| 285 | + | } |
|
| 286 | + | (0, visual_row as u16) |
|
| 287 | + | } |
|
| 288 | + | ||
| 289 | + | fn auto_scroll_edit(&mut self, cursor_visual_row: u16, visible_height: u16) { |
|
| 290 | + | if visible_height == 0 { |
|
| 291 | + | return; |
|
| 292 | + | } |
|
| 293 | + | if cursor_visual_row < self.edit_scroll { |
|
| 294 | + | self.edit_scroll = cursor_visual_row; |
|
| 295 | + | } else if cursor_visual_row >= self.edit_scroll + visible_height { |
|
| 296 | + | self.edit_scroll = cursor_visual_row - visible_height + 1; |
|
| 297 | + | } |
|
| 298 | + | } |
|
| 299 | + | ||
| 249 | 300 | fn start_create(&mut self) { |
|
| 250 | 301 | self.create_name.clear(); |
|
| 251 | 302 | self.create_content.clear(); |
|
| 303 | + | self.edit_scroll = 0; |
|
| 252 | 304 | self.focus = Focus::CreateName; |
|
| 253 | 305 | } |
|
| 254 | 306 | ||
| 288 | 340 | self.create_name = name; |
|
| 289 | 341 | self.create_content = content; |
|
| 290 | 342 | self.edit_short_id = Some(short_id); |
|
| 343 | + | self.edit_scroll = 0; |
|
| 291 | 344 | self.focus = Focus::EditName; |
|
| 292 | 345 | } |
|
| 293 | 346 | } |
|
| 649 | 702 | Focus::CreateContent | Focus::EditContent => Style::default().fg(Color::Yellow), |
|
| 650 | 703 | _ => Style::default().fg(Color::DarkGray), |
|
| 651 | 704 | }; |
|
| 652 | - | let content_input = Paragraph::new(app.create_content.as_str()).block( |
|
| 705 | + | let mut content_input = Paragraph::new(app.create_content.as_str()).block( |
|
| 653 | 706 | Block::default() |
|
| 654 | 707 | .title(" Content ") |
|
| 655 | 708 | .borders(Borders::ALL) |
|
| 656 | 709 | .border_style(content_style), |
|
| 657 | 710 | ); |
|
| 711 | + | if app.wrap_content { |
|
| 712 | + | content_input = content_input.wrap(Wrap { trim: false }); |
|
| 713 | + | } |
|
| 714 | + | content_input = content_input.scroll((app.edit_scroll, 0)); |
|
| 658 | 715 | frame.render_widget(content_input, form_layout[1]); |
|
| 659 | 716 | ||
| 717 | + | let content_inner = Block::default() |
|
| 718 | + | .borders(Borders::ALL) |
|
| 719 | + | .inner(form_layout[1]); |
|
| 720 | + | let inner_width = content_inner.width; |
|
| 721 | + | let inner_height = content_inner.height; |
|
| 722 | + | ||
| 660 | 723 | match app.focus { |
|
| 661 | 724 | Focus::CreateName | Focus::EditName => { |
|
| 662 | 725 | let x = form_layout[0].x + 1 + app.create_name.len() as u16; |
|
| 664 | 727 | frame.set_cursor_position((x, y)); |
|
| 665 | 728 | } |
|
| 666 | 729 | Focus::CreateContent | Focus::EditContent => { |
|
| 667 | - | let last_line = app.create_content.lines().last().unwrap_or(""); |
|
| 668 | - | let line_count = app.create_content.lines().count() |
|
| 669 | - | + if app.create_content.ends_with('\n') { |
|
| 670 | - | 1 |
|
| 671 | - | } else { |
|
| 672 | - | 0 |
|
| 673 | - | }; |
|
| 674 | - | let y_offset = if line_count == 0 { 0 } else { line_count - 1 }; |
|
| 675 | - | let x = form_layout[1].x |
|
| 676 | - | + 1 |
|
| 677 | - | + if app.create_content.ends_with('\n') { |
|
| 730 | + | let (cx, cy) = if app.wrap_content { |
|
| 731 | + | app.cursor_position_wrapped(inner_width) |
|
| 732 | + | } else { |
|
| 733 | + | let last_line = app.create_content.lines().last().unwrap_or(""); |
|
| 734 | + | let line_count = app.create_content.lines().count() |
|
| 735 | + | + if app.create_content.ends_with('\n') { 1 } else { 0 }; |
|
| 736 | + | let y_offset = if line_count == 0 { 0 } else { line_count - 1 }; |
|
| 737 | + | let col = if app.create_content.ends_with('\n') { |
|
| 678 | 738 | 0 |
|
| 679 | 739 | } else { |
|
| 680 | 740 | last_line.len() as u16 |
|
| 681 | 741 | }; |
|
| 682 | - | let y = form_layout[1].y + 1 + y_offset as u16; |
|
| 742 | + | (col, y_offset as u16) |
|
| 743 | + | }; |
|
| 744 | + | app.auto_scroll_edit(cy, inner_height); |
|
| 745 | + | let screen_y = cy.saturating_sub(app.edit_scroll); |
|
| 746 | + | let x = content_inner.x + cx; |
|
| 747 | + | let y = content_inner.y + screen_y; |
|
| 683 | 748 | frame.set_cursor_position((x, y)); |
|
| 684 | 749 | } |
|
| 685 | 750 | _ => {} |
|
| 744 | 809 | Span::raw(": Switch field "), |
|
| 745 | 810 | Span::styled("Ctrl+S", Style::default().fg(Color::Yellow)), |
|
| 746 | 811 | Span::raw(": Save "), |
|
| 812 | + | Span::styled("Ctrl+W", Style::default().fg(Color::Yellow)), |
|
| 813 | + | Span::raw(": Wrap "), |
|
| 747 | 814 | Span::styled("Esc", Style::default().fg(Color::Yellow)), |
|
| 748 | 815 | Span::raw(": Cancel"), |
|
| 749 | 816 | ]), |
|
| 807 | 874 | if app.show_help { |
|
| 808 | 875 | let area = frame.area(); |
|
| 809 | 876 | let popup_width = 34u16.min(area.width.saturating_sub(4)); |
|
| 810 | - | let popup_height = 20u16.min(area.height.saturating_sub(4)); |
|
| 877 | + | let popup_height = 21u16.min(area.height.saturating_sub(4)); |
|
| 811 | 878 | let popup_area = ratatui::layout::Rect { |
|
| 812 | 879 | x: (area.width.saturating_sub(popup_width)) / 2, |
|
| 813 | 880 | y: (area.height.saturating_sub(popup_height)) / 2, |
|
| 916 | 983 | ), |
|
| 917 | 984 | Span::raw("Search snippets"), |
|
| 918 | 985 | ]), |
|
| 986 | + | Line::from(vec![ |
|
| 987 | + | Span::styled( |
|
| 988 | + | " ^W ", |
|
| 989 | + | Style::default() |
|
| 990 | + | .fg(Color::Yellow) |
|
| 991 | + | .add_modifier(Modifier::BOLD), |
|
| 992 | + | ), |
|
| 993 | + | Span::raw("Toggle word wrap (edit)"), |
|
| 994 | + | ]), |
|
| 919 | 995 | ]; |
|
| 920 | 996 | ||
| 921 | 997 | if app.is_remote { |
|
| 1037 | 1113 | } |
|
| 1038 | 1114 | } |
|
| 1039 | 1115 | Focus::CreateContent => { |
|
| 1040 | - | if key.modifiers.contains(KeyModifiers::CONTROL) |
|
| 1041 | - | && key.code == KeyCode::Char('s') |
|
| 1042 | - | { |
|
| 1043 | - | app.save_create(backend); |
|
| 1116 | + | if key.modifiers.contains(KeyModifiers::CONTROL) { |
|
| 1117 | + | match key.code { |
|
| 1118 | + | KeyCode::Char('s') => app.save_create(backend), |
|
| 1119 | + | KeyCode::Char('w') => { |
|
| 1120 | + | app.wrap_content = !app.wrap_content; |
|
| 1121 | + | app.edit_scroll = 0; |
|
| 1122 | + | } |
|
| 1123 | + | _ => {} |
|
| 1124 | + | } |
|
| 1044 | 1125 | } else { |
|
| 1045 | 1126 | match key.code { |
|
| 1046 | 1127 | KeyCode::Esc => app.cancel_create(), |
|
| 1074 | 1155 | } |
|
| 1075 | 1156 | } |
|
| 1076 | 1157 | Focus::EditContent => { |
|
| 1077 | - | if key.modifiers.contains(KeyModifiers::CONTROL) |
|
| 1078 | - | && key.code == KeyCode::Char('s') |
|
| 1079 | - | { |
|
| 1080 | - | app.save_edit(backend); |
|
| 1158 | + | if key.modifiers.contains(KeyModifiers::CONTROL) { |
|
| 1159 | + | match key.code { |
|
| 1160 | + | KeyCode::Char('s') => app.save_edit(backend), |
|
| 1161 | + | KeyCode::Char('w') => { |
|
| 1162 | + | app.wrap_content = !app.wrap_content; |
|
| 1163 | + | app.edit_scroll = 0; |
|
| 1164 | + | } |
|
| 1165 | + | _ => {} |
|
| 1166 | + | } |
|
| 1081 | 1167 | } else { |
|
| 1082 | 1168 | match key.code { |
|
| 1083 | 1169 | KeyCode::Esc => app.cancel_edit(), |
|