Terminal Markdown previewer — GUI-like experience.
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

chore: split app search

RivoLink 0c2dbdbf a6388376

+189 -183
+8 -183
src/app.rs src/app/mod.rs
··· 20 20 }; 21 21 use syntect::{highlighting::ThemeSet, parsing::SyntaxSet}; 22 22 23 + pub(super) mod search; 24 + pub(crate) use search::SearchState; 25 + 23 26 const MAX_FUZZY_PICKER_DIRS_VISITED: usize = 5_000; 24 27 const MAX_FUZZY_PICKER_FILES_INDEXED: usize = 10_000; 25 28 const MAX_FUZZY_PICKER_INDEX_DURATION: Duration = Duration::from_secs(5); ··· 67 70 pub(crate) struct ThemePreviewCacheEntry { 68 71 lines: Vec<Line<'static>>, 69 72 toc: Vec<TocEntry>, 70 - } 71 - 72 - pub(crate) struct SearchState { 73 - mode: bool, 74 - draft: String, 75 - query: String, 76 - matches: Vec<usize>, 77 - idx: usize, 78 73 } 79 74 80 75 #[derive(Clone, Debug, PartialEq, Eq)] ··· 210 205 211 206 pub(crate) struct App { 212 207 lines: Vec<Line<'static>>, 213 - plain_lines: Vec<String>, 214 - folded_plain_lines: Option<Vec<String>>, 215 - scroll: usize, 208 + pub(super) plain_lines: Vec<String>, 209 + pub(super) folded_plain_lines: Option<Vec<String>>, 210 + pub(super) scroll: usize, 216 211 toc: Vec<TocEntry>, 217 212 toc_visible: bool, 218 - search: SearchState, 219 - debug_input: bool, 213 + pub(super) search: SearchState, 214 + pub(super) debug_input: bool, 220 215 filename: String, 221 216 source: String, 222 217 watch: bool, ··· 717 712 self.refresh_static_caches(); 718 713 } 719 714 720 - pub(crate) fn active_highlight_line(&self) -> Option<usize> { 721 - if self.search.matches.is_empty() { 722 - None 723 - } else { 724 - Some(self.search.matches[self.search.idx]) 725 - } 726 - } 727 - 728 - pub(crate) fn is_search_mode(&self) -> bool { 729 - self.search.mode 730 - } 731 - 732 - pub(crate) fn search_draft(&self) -> &str { 733 - &self.search.draft 734 - } 735 - 736 - pub(crate) fn search_query(&self) -> &str { 737 - &self.search.query 738 - } 739 - 740 - #[cfg(test)] 741 - pub(crate) fn set_search_query(&mut self, query: impl Into<String>) { 742 - self.search.query = query.into(); 743 - } 744 - 745 - pub(crate) fn search_match_count(&self) -> usize { 746 - self.search.matches.len() 747 - } 748 - 749 - pub(crate) fn search_index(&self) -> usize { 750 - self.search.idx 751 - } 752 - 753 - #[cfg(test)] 754 - pub(crate) fn search_matches(&self) -> &[usize] { 755 - &self.search.matches 756 - } 757 - 758 715 #[cfg(test)] 759 716 pub(crate) fn line(&self, idx: usize) -> Option<&Line<'static>> { 760 717 self.lines.get(idx) 761 718 } 762 719 763 - #[cfg(test)] 764 - pub(crate) fn set_search_draft(&mut self, draft: impl Into<String>) { 765 - self.search.draft = draft.into(); 766 - } 767 - 768 - pub(crate) fn pop_search_draft(&mut self) { 769 - self.search.draft.pop(); 770 - } 771 - 772 - pub(crate) fn push_search_draft(&mut self, ch: char) { 773 - self.search.draft.push(ch); 774 - } 775 - 776 720 pub(crate) fn active_toc_index(&self) -> Option<usize> { 777 721 let hide_single_h1 = should_hide_single_h1(&self.toc); 778 722 let mut first_visible = None; ··· 798 742 } else { 799 743 active.or(Some(first_idx)) 800 744 } 801 - } 802 - 803 - pub(crate) fn folded_plain_lines(&mut self) -> &[String] { 804 - if self.folded_plain_lines.is_none() { 805 - self.folded_plain_lines = Some( 806 - self.plain_lines 807 - .iter() 808 - .map(|line| line.to_lowercase()) 809 - .collect(), 810 - ); 811 - } 812 - self.folded_plain_lines.as_deref().unwrap_or(&[]) 813 745 } 814 746 815 747 pub(crate) fn refresh_highlighted_line_cache(&mut self, line_idx: usize) -> Option<()> { ··· 1424 1356 if let Some(e) = self.toc.get(idx) { 1425 1357 self.scroll = e.line; 1426 1358 } 1427 - } 1428 - 1429 - pub(crate) fn run_search(&mut self) { 1430 - let q = self.search.query.to_lowercase(); 1431 - if q.is_empty() { 1432 - return; 1433 - } 1434 - let search_matches = { 1435 - let folded_lines = self.folded_plain_lines(); 1436 - folded_lines 1437 - .iter() 1438 - .enumerate() 1439 - .filter(|(_, line)| line.contains(&q)) 1440 - .map(|(i, _)| i) 1441 - .collect() 1442 - }; 1443 - self.search.matches = search_matches; 1444 - self.search.idx = 0; 1445 - if let Some(&f) = self.search.matches.first() { 1446 - self.scroll = f; 1447 - } 1448 - } 1449 - 1450 - pub(crate) fn begin_search(&mut self) { 1451 - self.search.mode = true; 1452 - self.search.draft = self.search.query.clone(); 1453 - crate::runtime::debug_log( 1454 - self.debug_input, 1455 - &format!( 1456 - "begin_search query={:?} draft={:?} matches={} idx={}", 1457 - self.search.query, 1458 - self.search.draft, 1459 - self.search.matches.len(), 1460 - self.search.idx 1461 - ), 1462 - ); 1463 - } 1464 - 1465 - pub(crate) fn reset_search_state(&mut self) { 1466 - self.search.draft.clear(); 1467 - self.search.query.clear(); 1468 - self.search.matches.clear(); 1469 - self.search.idx = 0; 1470 - } 1471 - 1472 - pub(crate) fn cancel_search(&mut self) { 1473 - self.search.mode = false; 1474 - self.reset_search_state(); 1475 - crate::runtime::debug_log(self.debug_input, "cancel_search cleared query and matches"); 1476 - } 1477 - 1478 - pub(crate) fn confirm_search(&mut self) { 1479 - self.search.mode = false; 1480 - let draft = std::mem::take(&mut self.search.draft); 1481 - self.search.query = draft; 1482 - if self.search.query.is_empty() { 1483 - self.reset_search_state(); 1484 - crate::runtime::debug_log( 1485 - self.debug_input, 1486 - "confirm_search empty query -> cleared matches", 1487 - ); 1488 - return; 1489 - } 1490 - self.run_search(); 1491 - crate::runtime::debug_log( 1492 - self.debug_input, 1493 - &format!( 1494 - "confirm_search query={:?} matches={} idx={} scroll={}", 1495 - self.search.query, 1496 - self.search.matches.len(), 1497 - self.search.idx, 1498 - self.scroll 1499 - ), 1500 - ); 1501 - } 1502 - 1503 - pub(crate) fn clear_active_search(&mut self) { 1504 - self.search.mode = false; 1505 - self.reset_search_state(); 1506 - crate::runtime::debug_log( 1507 - self.debug_input, 1508 - "clear_active_search cleared query and matches", 1509 - ); 1510 - } 1511 - 1512 - pub(crate) fn has_active_search(&self) -> bool { 1513 - !self.search.query.is_empty() || !self.search.matches.is_empty() 1514 - } 1515 - 1516 - pub(crate) fn next_match(&mut self) { 1517 - if self.search.matches.is_empty() { 1518 - return; 1519 - } 1520 - self.search.idx = (self.search.idx + 1) % self.search.matches.len(); 1521 - self.scroll = self.search.matches[self.search.idx]; 1522 - } 1523 - 1524 - pub(crate) fn prev_match(&mut self) { 1525 - if self.search.matches.is_empty() { 1526 - return; 1527 - } 1528 - if self.search.idx == 0 { 1529 - self.search.idx = self.search.matches.len() - 1; 1530 - } else { 1531 - self.search.idx -= 1; 1532 - } 1533 - self.scroll = self.search.matches[self.search.idx]; 1534 1359 } 1535 1360 1536 1361 pub(crate) fn scroll_percent(&self, vh: usize) -> u16 {
+181
src/app/search.rs
··· 1 + use super::App; 2 + 3 + pub(crate) struct SearchState { 4 + pub(super) mode: bool, 5 + pub(super) draft: String, 6 + pub(super) query: String, 7 + pub(super) matches: Vec<usize>, 8 + pub(super) idx: usize, 9 + } 10 + 11 + impl App { 12 + pub(crate) fn active_highlight_line(&self) -> Option<usize> { 13 + if self.search.matches.is_empty() { 14 + None 15 + } else { 16 + Some(self.search.matches[self.search.idx]) 17 + } 18 + } 19 + 20 + pub(crate) fn is_search_mode(&self) -> bool { 21 + self.search.mode 22 + } 23 + 24 + pub(crate) fn search_draft(&self) -> &str { 25 + &self.search.draft 26 + } 27 + 28 + pub(crate) fn search_query(&self) -> &str { 29 + &self.search.query 30 + } 31 + 32 + #[cfg(test)] 33 + pub(crate) fn set_search_query(&mut self, query: impl Into<String>) { 34 + self.search.query = query.into(); 35 + } 36 + 37 + pub(crate) fn search_match_count(&self) -> usize { 38 + self.search.matches.len() 39 + } 40 + 41 + pub(crate) fn search_index(&self) -> usize { 42 + self.search.idx 43 + } 44 + 45 + #[cfg(test)] 46 + pub(crate) fn search_matches(&self) -> &[usize] { 47 + &self.search.matches 48 + } 49 + 50 + #[cfg(test)] 51 + pub(crate) fn set_search_draft(&mut self, draft: impl Into<String>) { 52 + self.search.draft = draft.into(); 53 + } 54 + 55 + pub(crate) fn pop_search_draft(&mut self) { 56 + self.search.draft.pop(); 57 + } 58 + 59 + pub(crate) fn push_search_draft(&mut self, ch: char) { 60 + self.search.draft.push(ch); 61 + } 62 + 63 + pub(crate) fn folded_plain_lines(&mut self) -> &[String] { 64 + if self.folded_plain_lines.is_none() { 65 + self.folded_plain_lines = Some( 66 + self.plain_lines 67 + .iter() 68 + .map(|line| line.to_lowercase()) 69 + .collect(), 70 + ); 71 + } 72 + self.folded_plain_lines.as_deref().unwrap_or(&[]) 73 + } 74 + 75 + pub(crate) fn run_search(&mut self) { 76 + let q = self.search.query.to_lowercase(); 77 + if q.is_empty() { 78 + return; 79 + } 80 + let search_matches = { 81 + let folded_lines = self.folded_plain_lines(); 82 + folded_lines 83 + .iter() 84 + .enumerate() 85 + .filter(|(_, line)| line.contains(&q)) 86 + .map(|(i, _)| i) 87 + .collect() 88 + }; 89 + self.search.matches = search_matches; 90 + self.search.idx = 0; 91 + if let Some(&f) = self.search.matches.first() { 92 + self.scroll = f; 93 + } 94 + } 95 + 96 + pub(crate) fn begin_search(&mut self) { 97 + self.search.mode = true; 98 + self.search.draft = self.search.query.clone(); 99 + crate::runtime::debug_log( 100 + self.debug_input, 101 + &format!( 102 + "begin_search query={:?} draft={:?} matches={} idx={}", 103 + self.search.query, 104 + self.search.draft, 105 + self.search.matches.len(), 106 + self.search.idx 107 + ), 108 + ); 109 + } 110 + 111 + pub(crate) fn reset_search_state(&mut self) { 112 + self.search.draft.clear(); 113 + self.search.query.clear(); 114 + self.search.matches.clear(); 115 + self.search.idx = 0; 116 + } 117 + 118 + pub(crate) fn cancel_search(&mut self) { 119 + self.search.mode = false; 120 + self.reset_search_state(); 121 + crate::runtime::debug_log(self.debug_input, "cancel_search cleared query and matches"); 122 + } 123 + 124 + pub(crate) fn confirm_search(&mut self) { 125 + self.search.mode = false; 126 + let draft = std::mem::take(&mut self.search.draft); 127 + self.search.query = draft; 128 + if self.search.query.is_empty() { 129 + self.reset_search_state(); 130 + crate::runtime::debug_log( 131 + self.debug_input, 132 + "confirm_search empty query -> cleared matches", 133 + ); 134 + return; 135 + } 136 + self.run_search(); 137 + crate::runtime::debug_log( 138 + self.debug_input, 139 + &format!( 140 + "confirm_search query={:?} matches={} idx={} scroll={}", 141 + self.search.query, 142 + self.search.matches.len(), 143 + self.search.idx, 144 + self.scroll 145 + ), 146 + ); 147 + } 148 + 149 + pub(crate) fn clear_active_search(&mut self) { 150 + self.search.mode = false; 151 + self.reset_search_state(); 152 + crate::runtime::debug_log( 153 + self.debug_input, 154 + "clear_active_search cleared query and matches", 155 + ); 156 + } 157 + 158 + pub(crate) fn has_active_search(&self) -> bool { 159 + !self.search.query.is_empty() || !self.search.matches.is_empty() 160 + } 161 + 162 + pub(crate) fn next_match(&mut self) { 163 + if self.search.matches.is_empty() { 164 + return; 165 + } 166 + self.search.idx = (self.search.idx + 1) % self.search.matches.len(); 167 + self.scroll = self.search.matches[self.search.idx]; 168 + } 169 + 170 + pub(crate) fn prev_match(&mut self) { 171 + if self.search.matches.is_empty() { 172 + return; 173 + } 174 + if self.search.idx == 0 { 175 + self.search.idx = self.search.matches.len() - 1; 176 + } else { 177 + self.search.idx -= 1; 178 + } 179 + self.scroll = self.search.matches[self.search.idx]; 180 + } 181 + }