dprint_cli_core/logging/
multi_select.rs

1use anyhow::bail;
2use anyhow::Result;
3use crossterm::event::Event;
4use crossterm::event::KeyCode;
5
6use crate::logging::Logger;
7use crate::logging::LoggerRefreshItemKind;
8use crate::logging::LoggerTextItem;
9use crate::terminal::read_terminal_event;
10
11struct MultiSelectData<'a> {
12  prompt: &'a str,
13  item_hanging_indent: u16,
14  items: Vec<(bool, &'a String)>,
15  active_index: usize,
16}
17
18pub fn show_multi_select(logger: &Logger, context_name: &str, prompt: &str, item_hanging_indent: u16, items: Vec<(bool, &String)>) -> Result<Vec<usize>> {
19  let mut data = MultiSelectData {
20    prompt,
21    items,
22    item_hanging_indent,
23    active_index: 0,
24  };
25
26  loop {
27    let text_items = render_multi_select(&data);
28    logger.set_refresh_item(LoggerRefreshItemKind::Selection, text_items);
29
30    if let Event::Key(key_event) = read_terminal_event()? {
31      match &key_event.code {
32        KeyCode::Up => {
33          if data.active_index == 0 {
34            data.active_index = data.items.len() - 1;
35          } else {
36            data.active_index -= 1;
37          }
38        }
39        KeyCode::Down => {
40          data.active_index = (data.active_index + 1) % data.items.len();
41        }
42        KeyCode::Char(' ') => {
43          // select an item
44          let mut current_item = data.items.get_mut(data.active_index).unwrap();
45          current_item.0 = !current_item.0;
46        }
47        KeyCode::Enter => {
48          break;
49        }
50        KeyCode::Esc => {
51          logger.remove_refresh_item(LoggerRefreshItemKind::Selection);
52          bail!("Selection cancelled.");
53        }
54        _ => {}
55      }
56    } else {
57      // cause a refresh anyway
58    }
59  }
60  logger.remove_refresh_item(LoggerRefreshItemKind::Selection);
61
62  logger.log_text_items(&render_complete(&data), context_name, crate::terminal::get_terminal_width());
63
64  // return the selected indexes
65  let mut result = Vec::new();
66  for (i, (is_selected, _)) in data.items.iter().enumerate() {
67    if *is_selected {
68      result.push(i);
69    }
70  }
71  Ok(result)
72}
73
74fn render_multi_select(data: &MultiSelectData) -> Vec<LoggerTextItem> {
75  let mut result = vec![LoggerTextItem::Text(data.prompt.to_string())];
76
77  for (i, (is_selected, item_text)) in data.items.iter().enumerate() {
78    let mut text = String::new();
79    text.push_str(if i == data.active_index { ">" } else { " " });
80    text.push_str(" [");
81    text.push_str(if *is_selected { "x" } else { " " });
82    text.push_str("] ");
83    text.push_str(item_text);
84
85    result.push(LoggerTextItem::HangingText {
86      text,
87      indent: 7 + data.item_hanging_indent,
88    });
89  }
90
91  result
92}
93
94fn render_complete(data: &MultiSelectData) -> Vec<LoggerTextItem> {
95  let mut result = Vec::new();
96  if data.items.iter().any(|(is_selected, _)| *is_selected) {
97    result.push(LoggerTextItem::Text(data.prompt.to_string()));
98    for (is_selected, item_text) in data.items.iter() {
99      if *is_selected {
100        result.push(LoggerTextItem::HangingText {
101          text: format!(" * {}", item_text),
102          indent: 3 + data.item_hanging_indent,
103        });
104      }
105    }
106  }
107  result
108}