1use std::cell::Cell;
15use std::sync::Arc;
16use std::time::Duration;
17
18use rich::cells::cell_len;
19use rich::{Segment, Style};
20
21use crate::clipboard::{self, CopyFormat};
22use crate::component::{Component, Context, Flow, View};
23use crate::components::{PreviewLayout, Select, Theme, fit, pad, plain, shown, text};
24use crate::event::{Event, Key, KeyCode, MouseKind};
25use crate::item::{Actions, Item, TargetKind};
26use crate::keymap::{Keymap, keys};
27use crate::policy::{LineIo, NotInteractive};
28
29macro_rules! select_builders {
31 () => {
32 pub fn height(mut self, rows: usize) -> Self {
34 self.select = self.select.height(rows);
35 self
36 }
37
38 pub fn preview(mut self, layout: PreviewLayout) -> Self {
39 self.select = self.select.preview(layout);
40 self
41 }
42
43 pub fn theme(mut self, theme: Theme) -> Self {
44 self.select = self.select.theme(theme);
45 self
46 }
47
48 pub fn query(mut self, query: impl Into<String>) -> Self {
50 self.select = self.select.query(query);
51 self
52 }
53
54 pub fn default(mut self, index: usize) -> Self {
56 self.select = self.select.default(index);
57 self
58 }
59
60 pub fn actions(mut self, actions: Actions) -> Self {
62 self.select = self.select.actions(actions);
63 self
64 }
65
66 pub fn menu_key(mut self, key: Key) -> Self {
68 self.select = self.select.menu_key(key);
69 self
70 }
71
72 pub fn with_mouse(mut self, on: bool) -> Self {
74 self.select = self.select.with_mouse(on);
75 self
76 }
77
78 pub fn action(&self) -> Option<&str> {
80 self.select.action()
81 }
82
83 pub fn items(&self) -> &[Item<T>] {
84 self.select.items()
85 }
86
87 pub fn select(&self) -> &Select<T> {
89 &self.select
90 }
91
92 pub fn set_icons(&mut self, icons: Vec<Option<rich::Text>>) {
95 self.select.set_icons(icons);
96 }
97 };
98}
99
100pub fn table_keymap() -> Keymap {
102 Keymap::new("table")
103 .bind("copy-row", keys("ctrl+y"), "copy the row")
104 .bind("copy-cell", keys("alt+y"), "copy the focused cell")
105 .bind("next-column", keys("ctrl+right"), "focus the next column")
106 .bind(
107 "previous-column",
108 keys("ctrl+left"),
109 "focus the previous column",
110 )
111 .bind("copy-format", keys("alt+f"), "copy as text, CSV or JSON")
112 .bind("scroll-left", keys("left"), "scroll a column left")
113 .bind("scroll-right", keys("right"), "scroll a column right")
114 .bind(
115 "scroll-start",
116 keys("shift+home"),
117 "scroll to the first column",
118 )
119 .bind("scroll-end", keys("shift+end"), "scroll to the last column")
120}
121
122const SCROLLING: [&str; 4] = ["scroll-left", "scroll-right", "scroll-start", "scroll-end"];
125
126#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
131pub(crate) struct Pan {
132 frozen: usize,
133 offset: usize,
134 left: bool,
136 right: bool,
137}
138
139impl Pan {
140 pub(crate) fn window(&self, line: &[Segment]) -> Vec<Segment> {
142 if self.offset == 0 {
143 return line.to_vec();
144 }
145 let mut out = crate::kit::slice(line, 0, self.frozen);
146 out.extend(crate::kit::slice(
147 line,
148 self.frozen + self.offset,
149 usize::MAX,
150 ));
151 out
152 }
153
154 pub(crate) fn heading(&self, indent: &str, heading: &[Segment], width: usize) -> Vec<Segment> {
158 let indent = if self.left {
159 format!("‹{}", &indent[1..])
160 } else {
161 indent.to_string()
162 };
163 let mut line = vec![plain(indent)];
164 line.extend(self.window(heading));
165 if self.right && width > 1 {
166 let mut line = pad(line, width - 1);
167 line.push(plain("›"));
168 return line;
169 }
170 fit(line, width)
171 }
172}
173
174#[must_use = "a component does nothing until it is run or mounted"]
191pub struct TableSelect<T> {
192 select: Select<T>,
193 headers: Vec<String>,
195 raw_headers: Vec<String>,
197 cells: Vec<Vec<String>>,
198 widths: Vec<usize>,
199 column: Option<usize>,
201 format: CopyFormat,
202 keymap: Keymap,
203 scroll: bool,
205 freeze: usize,
207 left: usize,
209 span: Cell<usize>,
211}
212
213impl<T> TableSelect<T> {
214 pub fn new<H, S, R, V>(prompt: impl Into<String>, headers: H, rows: R) -> TableSelect<T>
217 where
218 H: IntoIterator<Item = S>,
219 S: Into<String>,
220 R: IntoIterator<Item = (V, Vec<String>)>,
221 V: Into<Item<T>>,
222 {
223 let raw_headers: Vec<String> = headers.into_iter().map(Into::into).collect();
225 let headers: Vec<String> = raw_headers.iter().map(|h| shown(h)).collect();
226 let rows: Vec<(Item<T>, Vec<String>)> = rows
227 .into_iter()
228 .map(|(item, cells)| (item.into(), cells))
229 .collect();
230 let columns = rows
231 .iter()
232 .map(|(_, cells)| cells.len())
233 .chain([headers.len()])
234 .max()
235 .unwrap_or(0);
236 let mut widths = vec![0; columns];
237 for (column, cell) in headers.iter().enumerate() {
238 widths[column] = widths[column].max(cell_len(cell));
239 }
240 for (_, cells) in &rows {
241 for (column, cell) in cells.iter().enumerate() {
242 widths[column] = widths[column].max(cell_len(&shown(cell)));
243 }
244 }
245 let line = |cells: &[String]| -> String {
246 let mut out = String::new();
247 for (column, width) in widths.iter().enumerate() {
248 let cell = cells.get(column).map_or("", String::as_str);
249 out.push_str(cell);
250 if column + 1 < widths.len() {
251 out.push_str(&" ".repeat(width - cell_len(cell) + 2));
252 }
253 }
254 out.trim_end().to_string()
255 };
256 let mut values = Vec::with_capacity(rows.len());
257 let mut table = Vec::with_capacity(rows.len());
258 let items: Vec<Item<T>> = rows
259 .into_iter()
260 .map(|(mut item, cells)| {
261 let drawn: Vec<String> = cells.iter().map(|cell| shown(cell)).collect();
262 item.label = line(&drawn);
263 values.push(cells.join("\t"));
264 table.push(cells);
265 item
266 })
267 .collect();
268 let mut select = Select::new(prompt, items);
269 select.set_kind(TargetKind::Row);
270 select.set_values(values);
271 let mut view = TableSelect {
272 select,
273 headers,
274 raw_headers,
275 cells: table,
276 widths,
277 column: None,
278 format: CopyFormat::Text,
279 keymap: table_keymap(),
280 scroll: false,
281 freeze: 0,
282 left: 0,
283 span: Cell::new(usize::MAX),
284 };
285 view.heading();
286 view
287 }
288
289 select_builders!();
290
291 pub fn copy_format(mut self, format: CopyFormat) -> Self {
293 self.format = format;
294 self
295 }
296
297 pub fn column(&self) -> Option<usize> {
299 self.column
300 }
301
302 pub fn scroll_x(mut self, on: bool) -> Self {
305 self.scroll = on;
306 self.select.set_hints(on.then(|| "←→ scroll".to_string()));
307 self
308 }
309
310 pub fn freeze_columns(mut self, columns: usize) -> Self {
313 self.freeze = columns;
314 self.scroll_x(true)
315 }
316
317 pub fn first_column(&self) -> usize {
320 self.left
321 .clamp(self.frozen_columns(), self.last_left(self.span.get()))
322 }
323
324 fn starts(&self) -> Vec<usize> {
326 let mut at = 0;
327 self.widths
328 .iter()
329 .map(|width| {
330 let start = at;
331 at += width + 2;
332 start
333 })
334 .collect()
335 }
336
337 fn total(&self) -> usize {
339 let starts = self.starts();
340 match (starts.last(), self.widths.last()) {
341 (Some(start), Some(width)) => start + width,
342 _ => 0,
343 }
344 }
345
346 fn frozen_columns(&self) -> usize {
348 self.freeze.min(self.widths.len())
349 }
350
351 fn frozen_cells(&self) -> usize {
353 match self.frozen_columns() {
354 0 => 0,
355 n if n == self.widths.len() => self.total(),
356 n => self.starts()[n],
357 }
358 }
359
360 fn last_left(&self, span: usize) -> usize {
363 let first = self.frozen_columns();
364 let (starts, total, frozen) = (self.starts(), self.total(), self.frozen_cells());
365 (first..self.widths.len())
366 .find(|&left| total - (starts[left] - frozen) <= span)
367 .unwrap_or_else(|| self.widths.len().saturating_sub(1).max(first))
368 }
369
370 fn pan(&self, span: usize) -> Option<Pan> {
372 if !self.scroll {
373 return None;
374 }
375 let first = self.frozen_columns();
376 let left = self.left.clamp(first, self.last_left(span));
377 let frozen = self.frozen_cells();
378 let offset = match self.starts().get(left) {
379 Some(start) if left > first => start - frozen,
380 _ => 0,
381 };
382 Some(Pan {
383 frozen,
384 offset,
385 left: offset > 0,
386 right: self.total() - offset > span,
387 })
388 }
389
390 fn follow(&mut self, column: usize, span: usize) {
392 let first = self.frozen_columns();
393 if column < first {
394 return;
395 }
396 let (starts, frozen) = (self.starts(), self.frozen_cells());
397 let mut left = self.left.clamp(first, self.last_left(span));
398 if column < left {
399 left = column;
400 }
401 while left < column && starts[column] + self.widths[column] - (starts[left] - frozen) > span
402 {
403 left += 1;
404 }
405 self.left = left;
406 }
407
408 fn scroll_by(&mut self, action: &str, span: usize) {
410 let first = self.frozen_columns();
411 let last = self.last_left(span);
412 let left = self.left.clamp(first, last);
413 self.left = match action {
414 "scroll-left" => left.saturating_sub(1).max(first),
415 "scroll-right" => (left + 1).min(last),
416 "scroll-start" => first,
417 _ => last,
418 };
419 }
420
421 pub fn rebind(mut self, action: &str, keys: impl IntoIterator<Item = Key>) -> Self {
423 self.keymap.rebind(action, keys);
424 self
425 }
426
427 fn heading(&mut self) {
429 let style = Theme::default().prompt;
430 let Some(focused_column) = self.column else {
431 let mut heading = String::new();
433 for (column, width) in self.widths.iter().enumerate() {
434 let cell = self.headers.get(column).map_or("", String::as_str);
435 heading.push_str(cell);
436 if column + 1 < self.widths.len() {
437 heading.push_str(&" ".repeat(width - cell_len(cell) + 2));
438 }
439 }
440 self.select.heading = Some(vec![text(heading.trim_end(), &style)]);
441 return;
442 };
443 let focused = style.combine(&Style::parse("underline").expect("a style"));
444 let mut line: Vec<Segment> = Vec::new();
445 let last = self.widths.len().saturating_sub(1);
446 for (column, width) in self.widths.iter().enumerate() {
447 let cell = self.headers.get(column).map_or("", String::as_str);
448 let style = if column == focused_column {
449 &focused
450 } else {
451 &style
452 };
453 if !cell.is_empty() {
454 line.push(text(cell, style));
455 }
456 if column < last {
457 line.push(plain(" ".repeat(width - cell_len(cell) + 2)));
458 }
459 }
460 while line
462 .last()
463 .is_some_and(|segment| segment.style.is_none() && segment.text.trim().is_empty())
464 {
465 line.pop();
466 }
467 self.select.heading = Some(line);
468 }
469
470 fn copy(&mut self, cell: bool) {
472 let Some(index) = self.select.focused() else {
473 return;
474 };
475 let row = &self.cells[index];
476 let (what, copied) = if cell {
477 let column = self.column.unwrap_or(0);
478 let value = row.get(column).map_or("", String::as_str);
479 ("cell", self.format.cell(value))
480 } else {
481 ("row", self.format.row(&self.raw_headers, row))
482 };
483 let result = clipboard::copy(copied);
484 let what = format!("{what} as {}", self.format.name());
485 self.select
486 .set_status(Some(clipboard::report(&what, &result)));
487 }
488
489 fn table_key(&mut self, key: Key, span: usize) -> bool {
492 match self.keymap.action(key) {
493 Some("copy-row") => self.copy(false),
494 Some("copy-cell") => self.copy(true),
495 Some(step @ ("next-column" | "previous-column")) => {
496 let columns = self.widths.len().max(1);
497 let at = self.column.unwrap_or(0);
498 let column = if step == "next-column" {
499 (at + 1).min(columns - 1)
500 } else {
501 at.saturating_sub(1)
502 };
503 self.column = Some(column);
504 if self.scroll {
505 self.follow(column, span);
506 }
507 self.heading();
508 }
509 Some(action) if self.scroll && SCROLLING.contains(&action) => {
510 let action = action.to_string();
511 self.scroll_by(&action, span);
512 }
513 Some("copy-format") => {
514 self.format = self.format.next();
515 self.select
516 .set_status(Some(format!("copy as {}", self.format.name())));
517 }
518 _ => return false,
519 }
520 true
521 }
522}
523
524impl<T: Clone> Component for TableSelect<T> {
525 type Output = T;
526
527 fn keymap(&self) -> Keymap {
528 let mut keymap = if self.select.menu_open() {
529 Keymap::default()
530 } else if self.scroll {
531 self.keymap.clone()
532 } else {
533 let mut keymap = Keymap::new("table");
534 for binding in self.keymap.bindings() {
535 if !SCROLLING.contains(&binding.action.as_str()) {
536 keymap.add(binding);
537 }
538 }
539 keymap
540 };
541 keymap.extend(self.select.visible_keymap());
542 keymap
543 }
544
545 fn handle(&mut self, event: &Event, context: &Context<'_>) -> Flow<T> {
546 let span = self.select.list_width(context.width).saturating_sub(2);
548 self.span.set(span);
549 if let Some(key) = event.key()
550 && !self.select.menu_open()
551 && self.table_key(key, span)
552 {
553 return Flow::Continue;
554 }
555 if let Event::Mouse(mouse) = event {
557 let across = match mouse.kind {
558 MouseKind::ScrollUp => Some("scroll-left"),
559 MouseKind::ScrollDown => Some("scroll-right"),
560 _ => None,
561 };
562 if let Some(action) =
563 across.filter(|_| self.scroll && mouse.modifiers.shift && !self.select.menu_open())
564 {
565 self.scroll_by(action, span);
566 return Flow::Continue;
567 }
568 }
569 self.select.handle(event, context)
570 }
571
572 fn render(&self, context: &Context<'_>) -> View {
573 let span = self.select.list_width(context.width).saturating_sub(2);
574 self.span.set(span);
575 self.select.pan.set(self.pan(span));
576 self.select.render(context)
577 }
578
579 fn tick(&self) -> Option<Duration> {
580 self.select.tick()
581 }
582
583 fn mouse(&self) -> bool {
584 Component::mouse(&self.select)
585 }
586
587 fn default_value(&self) -> Option<T> {
588 self.select.default_value()
589 }
590
591 fn prompt(&mut self, io: &mut dyn LineIo) -> Result<Option<T>, NotInteractive> {
592 self.select.prompt(io)
593 }
594}
595
596pub fn tree_keymap() -> Keymap {
598 Keymap::new("tree")
599 .bind("expand", keys("right"), "expand")
600 .bind("collapse", keys("left"), "collapse, or go to the parent")
601 .bind("copy-path", keys("ctrl+y"), "copy the path")
602}
603
604#[must_use = "a component does nothing until it is run or mounted"]
616pub struct TreeSelect<T> {
617 select: Select<T>,
618 parents: Arc<[Option<usize>]>,
619 last: Vec<bool>,
621 parent_of_any: Vec<bool>,
622 collapsed: Vec<bool>,
623 crumbs: Vec<String>,
626 breadcrumbs: bool,
627 width: usize,
629 filtered: bool,
631 keymap: Keymap,
632}
633
634impl<T> TreeSelect<T> {
635 pub fn new<I, V>(prompt: impl Into<String>, nodes: I) -> TreeSelect<T>
641 where
642 I: IntoIterator<Item = (usize, V)>,
643 V: Into<Item<T>>,
644 {
645 let mut items: Vec<Item<T>> = Vec::new();
646 let mut parents: Vec<Option<usize>> = Vec::new();
647 let mut open: Vec<usize> = Vec::new();
650 for (depth, node) in nodes {
651 let depth = depth.min(open.len());
653 open.truncate(depth);
654 let index = items.len();
655 parents.push(open.last().copied());
656 open.push(index);
657 let mut item: Item<T> = node.into();
658 item.label = shown(&item.label);
659 items.push(item);
660 }
661 let count = items.len();
662 let mut parent_of_any = vec![false; count];
663 for parent in parents.iter().flatten() {
664 parent_of_any[*parent] = true;
665 }
666 let last = last_children(&parents, None);
667 let parents: Arc<[Option<usize>]> = parents.into();
668 let mut select = Select::new(prompt, items);
669 select.set_kind(TargetKind::Node);
670 select.hints = Some("←→ fold".into());
671 select.set_tree(Some(parents.to_vec()));
672 let mut tree = TreeSelect {
673 select,
674 parents,
675 last,
676 parent_of_any,
677 collapsed: vec![false; count],
678 crumbs: Vec::new(),
679 breadcrumbs: false,
680 width: usize::MAX,
681 filtered: false,
682 keymap: tree_keymap(),
683 };
684 tree.update();
685 tree
686 }
687
688 pub fn breadcrumbs(mut self, on: bool) -> Self {
692 self.breadcrumbs = on;
693 self.crumb_line();
694 self
695 }
696
697 pub fn crumbs(mut self, crumbs: Vec<String>) -> Self {
700 let count = self.parents.len();
701 self.crumbs = crumbs
702 .into_iter()
703 .take(count)
704 .map(|crumb| shown(&crumb))
705 .collect();
706 self.crumb_line();
707 self
708 }
709
710 pub fn paths(mut self, paths: Vec<String>) -> Self {
714 self.select.set_values(paths);
715 self
716 }
717
718 pub fn paths_with(mut self, path: impl Fn(usize) -> String + Send + Sync + 'static) -> Self {
722 self.select.set_values(Vec::new());
723 self.select.value_of = Some(Arc::new(path));
724 self
725 }
726
727 pub fn fold_below(mut self, depth: usize) -> Self {
730 let mut depths = vec![0usize; self.parents.len()];
733 for index in 0..self.parents.len() {
734 depths[index] = self.parents[index].map_or(0, |parent| depths[parent] + 1);
735 self.collapsed[index] = depths[index] >= depth && self.parent_of_any[index];
736 }
737 self.update();
738 self
739 }
740
741 pub fn rebind(mut self, action: &str, keys: impl IntoIterator<Item = Key>) -> Self {
743 self.keymap.rebind(action, keys);
744 self
745 }
746
747 pub fn parents(&self) -> &[Option<usize>] {
749 &self.parents
750 }
751
752 pub fn focused(&self) -> Option<usize> {
754 self.select.focused()
755 }
756
757 pub fn path_to(&self, index: usize) -> Vec<&str> {
759 let mut path: Vec<&str> = crate::kit::ancestors(&self.parents, index)
760 .map(|ancestor| self.crumb(ancestor))
761 .collect();
762 path.reverse();
763 path.push(self.crumb(index));
764 path
765 }
766
767 fn crumb(&self, index: usize) -> &str {
769 match self.crumbs.get(index) {
770 Some(crumb) => crumb,
771 None => &self.select.items()[index].label,
772 }
773 }
774
775 pub fn set_status(&mut self, status: Option<String>) {
777 self.select.set_status(status);
778 }
779
780 pub fn set_hints(&mut self, hints: Option<String>) {
782 self.select.set_hints(hints);
783 }
784
785 fn crumb_line(&mut self) {
787 if !self.breadcrumbs {
788 return;
789 }
790 let path = self.select.focused().map(|index| self.path_to(index));
791 let line = breadcrumb_line(path.as_deref().unwrap_or(&[]), self.width);
792 self.select.heading = Some(line);
793 }
794
795 fn copy_path(&mut self) {
797 let Some(index) = self.select.focused() else {
798 return;
799 };
800 let path = self.select.target(index).value;
801 let result = clipboard::copy(path);
802 self.select
803 .set_status(Some(clipboard::report("path", &result)));
804 }
805
806 pub fn collapsed(mut self, collapsed: bool) -> Self {
809 for (index, parent) in self.parent_of_any.iter().enumerate() {
810 self.collapsed[index] = collapsed && *parent;
811 }
812 self.update();
813 self
814 }
815
816 select_builders!();
817
818 pub fn is_collapsed(&self, index: usize) -> bool {
820 self.collapsed.get(index).copied().unwrap_or(false)
821 }
822
823 pub fn set_collapsed(&mut self, index: usize, collapsed: bool) {
825 if self.parent_of_any.get(index).copied().unwrap_or(false) {
826 self.collapsed[index] = collapsed;
827 self.update();
828 }
829 }
830
831 fn update(&mut self) {
833 let mut hidden = vec![false; self.parents.len()];
836 for index in 0..self.parents.len() {
837 hidden[index] =
838 self.parents[index].is_some_and(|parent| self.collapsed[parent] || hidden[parent]);
839 }
840 self.select.guides = Some(self.guides(None));
841 self.select.hidden = hidden;
842 self.select.refilter();
843 self.filtered = false;
844 self.refresh_guides();
845 self.crumb_line();
846 }
847
848 fn guides(&self, shown: Option<&[bool]>) -> Guides {
852 let count = self.parents.len();
853 let (last, open) = match shown {
854 None => (self.last.clone(), None),
855 Some(shown) => {
856 let mut open = vec![false; count];
857 for (index, parent) in self.parents.iter().enumerate() {
858 if let Some(parent) = parent.filter(|_| shown[index]) {
859 open[parent] = true;
860 }
861 }
862 (last_children(&self.parents, Some(shown)), Some(open))
863 }
864 };
865 let markers = (0..count)
866 .map(|index| {
867 let collapsed = match &open {
868 Some(open) => !open[index],
869 None => self.collapsed[index],
870 };
871 match (self.parent_of_any[index], collapsed) {
872 (true, true) => Marker::Folded,
873 (true, false) => Marker::Open,
874 _ => Marker::Leaf,
875 }
876 })
877 .collect();
878 Guides {
879 parents: Arc::clone(&self.parents),
880 last,
881 markers,
882 }
883 }
884
885 fn refresh_guides(&mut self) {
888 let filtering = !self.select.query_text().is_empty();
889 if !filtering && !self.filtered {
890 return;
891 }
892 let guides = if filtering {
893 let mut shown = vec![false; self.parents.len()];
894 for (index, _) in self.select.filter().matches() {
895 shown[*index] = true;
896 }
897 self.guides(Some(&shown))
898 } else {
899 self.guides(None)
900 };
901 self.select.guides = Some(guides);
902 self.filtered = filtering;
903 }
904
905 fn fold(&mut self, key: Key) -> bool {
907 if !self.select.query_text().is_empty() || key.modifiers != Default::default() {
908 return false;
909 }
910 let Some(index) = self.select.focused() else {
911 return false;
912 };
913 match key.code {
914 KeyCode::Right if self.collapsed[index] => {
915 self.collapsed[index] = false;
916 self.update();
917 }
918 KeyCode::Left if self.parent_of_any[index] && !self.collapsed[index] => {
919 self.collapsed[index] = true;
920 self.update();
921 }
922 KeyCode::Left => {
923 if let Some(parent) = self.parents[index] {
924 self.select.focus_item(parent);
925 }
926 }
927 KeyCode::Right => {}
928 _ => return false,
929 }
930 true
931 }
932}
933
934fn last_children(parents: &[Option<usize>], shown: Option<&[bool]>) -> Vec<bool> {
937 let count = parents.len();
938 let mut last = vec![true; count];
939 let mut seen = vec![false; count + 1];
941 for index in (0..count).rev() {
942 if shown.is_some_and(|shown| !shown[index]) {
943 continue;
944 }
945 let slot = parents[index].unwrap_or(count);
946 last[index] = !seen[slot];
947 seen[slot] = true;
948 }
949 last
950}
951
952#[derive(Clone, Copy, Debug, PartialEq, Eq)]
954enum Marker {
955 Leaf,
957 Folded,
958 Open,
959}
960
961pub(crate) struct Guides {
965 parents: Arc<[Option<usize>]>,
966 last: Vec<bool>,
968 markers: Vec<Marker>,
969}
970
971impl Guides {
972 pub(crate) fn prefix(&self, index: usize) -> String {
974 if index >= self.parents.len() {
975 return String::new();
976 }
977 let up: Vec<usize> = crate::kit::ancestors(&self.parents, index).collect();
978 let mut guides = String::new();
979 for &ancestor in up.iter().rev().skip(1) {
982 guides.push_str(if self.last[ancestor] {
983 " "
984 } else {
985 "│ "
986 });
987 }
988 if self.parents[index].is_some() {
989 guides.push_str(if self.last[index] {
990 "└── "
991 } else {
992 "├── "
993 });
994 }
995 guides.push_str(match self.markers[index] {
996 Marker::Folded => "▸ ",
997 Marker::Open => "▾ ",
998 Marker::Leaf => "",
999 });
1000 guides
1001 }
1002
1003 pub(crate) fn path<'a>(&self, index: usize, name: impl Fn(usize) -> &'a str) -> String {
1006 let mut path: Vec<&str> = crate::kit::ancestors(&self.parents, index)
1007 .map(&name)
1008 .collect();
1009 path.reverse();
1010 path.push(name(index));
1011 path.join("/")
1012 }
1013}
1014
1015impl<T: Clone> Component for TreeSelect<T> {
1016 type Output = T;
1017
1018 fn keymap(&self) -> Keymap {
1019 let mut keymap = if self.select.menu_open() {
1020 Keymap::default()
1021 } else {
1022 self.keymap.clone()
1023 };
1024 keymap.extend(self.select.visible_keymap());
1025 keymap
1026 }
1027
1028 fn handle(&mut self, event: &Event, context: &Context<'_>) -> Flow<T> {
1029 self.width = context.width;
1030 let flow = match event.key() {
1031 Some(key) if !self.select.menu_open() && self.keymap.is(key, "copy-path") => {
1032 self.select.set_status(None);
1034 self.copy_path();
1035 Flow::Continue
1036 }
1037 Some(key) if !self.select.menu_open() && self.fold(key) => {
1038 self.select.set_status(None);
1039 Flow::Continue
1040 }
1041 _ => self.select.handle(event, context),
1042 };
1043 self.refresh_guides();
1044 self.crumb_line();
1045 flow
1046 }
1047
1048 fn render(&self, context: &Context<'_>) -> View {
1049 self.select.render(context)
1050 }
1051
1052 fn tick(&self) -> Option<Duration> {
1053 self.select.tick()
1054 }
1055
1056 fn mouse(&self) -> bool {
1057 Component::mouse(&self.select)
1058 }
1059
1060 fn default_value(&self) -> Option<T> {
1061 self.select.default_value()
1062 }
1063
1064 fn prompt(&mut self, io: &mut dyn LineIo) -> Result<Option<T>, NotInteractive> {
1065 self.select.prompt(io)
1066 }
1067}
1068
1069fn breadcrumb_line(path: &[&str], width: usize) -> Vec<Segment> {
1076 let theme = Theme::default();
1077 let separator = " › ";
1078 let room = width.saturating_sub(2);
1079 let mut first = 0;
1080 let total = |from: usize| -> usize {
1081 let names: usize = path[from..].iter().map(|name| cell_len(name)).sum();
1082 let gaps = path.len().saturating_sub(from + 1) * cell_len(separator);
1083 names
1084 + gaps
1085 + if from > 0 {
1086 cell_len("…") + cell_len(separator)
1087 } else {
1088 0
1089 }
1090 };
1091 while first + 1 < path.len() && total(first) > room {
1092 first += 1;
1093 }
1094 let mut line = Vec::new();
1095 if first > 0 {
1096 line.push(text("…", &theme.hint));
1097 line.push(text(separator, &theme.hint));
1098 }
1099 for (offset, name) in path[first..].iter().enumerate() {
1100 if offset > 0 {
1101 line.push(text(separator, &theme.hint));
1102 }
1103 let last = first + offset + 1 == path.len();
1104 line.push(if last {
1105 text(*name, &theme.focused)
1106 } else {
1107 text(*name, &theme.hint)
1108 });
1109 }
1110 fit(line, room)
1111}