dprint_cli_core/logging/
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 SelectData<'a> {
12  prompt: &'a str,
13  item_hanging_indent: u16,
14  items: &'a [String],
15  active_index: usize,
16}
17
18pub fn show_select(logger: &Logger, context_name: &str, prompt: &str, item_hanging_indent: u16, items: &[String]) -> Result<usize> {
19  let mut data = SelectData {
20    prompt,
21    item_hanging_indent,
22    items,
23    active_index: 0,
24  };
25
26  loop {
27    let text_items = render_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::Enter => {
43          break;
44        }
45        KeyCode::Esc => {
46          logger.remove_refresh_item(LoggerRefreshItemKind::Selection);
47          bail!("Selection cancelled.");
48        }
49        _ => {}
50      }
51    } else {
52      // cause a refresh anyway
53    }
54  }
55  logger.remove_refresh_item(LoggerRefreshItemKind::Selection);
56
57  logger.log_text_items(
58    &[
59      LoggerTextItem::Text(data.prompt.to_string()),
60      LoggerTextItem::HangingText {
61        text: data.items[data.active_index].to_string(),
62        indent: item_hanging_indent,
63      },
64    ],
65    context_name,
66    crate::terminal::get_terminal_width(),
67  );
68
69  Ok(data.active_index)
70}
71
72fn render_select(data: &SelectData) -> Vec<LoggerTextItem> {
73  let mut result = vec![LoggerTextItem::Text(data.prompt.to_string())];
74
75  for (i, item_text) in data.items.iter().enumerate() {
76    let mut text = String::new();
77    text.push_str(if i == data.active_index { ">" } else { " " });
78    text.push(' ');
79    text.push_str(item_text);
80    result.push(LoggerTextItem::HangingText {
81      text,
82      indent: 2 + data.item_hanging_indent,
83    });
84  }
85
86  result
87}