Skip to main content

inquire/prompts/sort/
mod.rs

1//! Sort prompt to reorder a list of options.
2
3mod action;
4mod config;
5mod prompt;
6#[cfg(test)]
7#[cfg(feature = "crossterm")]
8mod test;
9
10use std::fmt::Display;
11
12use crate::{
13    config::get_configuration,
14    error::{InquireError, InquireResult},
15    formatter::MultiOptionFormatter,
16    list_option::ListOption,
17    prompts::prompt::Prompt,
18    terminal::get_default_terminal,
19    ui::{Backend, RenderConfig, SortBackend},
20};
21
22use self::prompt::SortPrompt;
23
24/// Prompt designed for sorting/re-ordering a list of items.
25///
26/// The user moves the cursor via ↑/↓, presses Space to select/highlight an item,
27/// and then moves that item up/down using ↑/↓ or j/k (Vim style) to change its order.
28/// Pressing Enter submits the final sorted list.
29///
30/// # Example
31///
32/// ```no_run
33/// use inquire::Sort;
34///
35/// let options = vec!["Apple", "Banana", "Cherry"];
36/// let sorted = Sort::new("Order your favorite fruits:", options)
37///     .prompt()
38///     .unwrap();
39/// ```
40#[derive(Clone)]
41pub struct Sort<'a, T> {
42    /// Message to be presented to the user.
43    pub message: &'a str,
44
45    /// Options/Items to be sorted.
46    pub options: Vec<T>,
47
48    /// Help message to be presented to the user.
49    pub help_message: Option<&'a str>,
50
51    /// Page size of the options displayed to the user.
52    pub page_size: usize,
53
54    /// Whether vim mode is enabled.
55    pub vim_mode: bool,
56
57    /// Starting cursor index.
58    pub starting_cursor: usize,
59
60    /// Function that formats the sorted list for final display.
61    pub formatter: MultiOptionFormatter<'a, T>,
62
63    /// RenderConfig to apply to the rendered interface.
64    pub render_config: RenderConfig<'a>,
65}
66
67impl<'a, T> Sort<'a, T>
68where
69    T: Display,
70{
71    /// Default formatter: Prints the sorted list joined by commas.
72    pub const DEFAULT_FORMATTER: MultiOptionFormatter<'a, T> = &|ans| {
73        ans.iter()
74            .map(|opt| opt.to_string())
75            .collect::<Vec<String>>()
76            .join(", ")
77    };
78
79    /// Default page size of the list of options.
80    pub const DEFAULT_PAGE_SIZE: usize = crate::config::DEFAULT_PAGE_SIZE;
81
82    /// Default setting for Vim mode (disabled by default).
83    pub const DEFAULT_VIM_MODE: bool = crate::config::DEFAULT_VIM_MODE;
84
85    /// Default index where the cursor starts.
86    pub const DEFAULT_STARTING_CURSOR: usize = 0;
87
88    /// Help message instructing how to reorder the items.
89    pub const DEFAULT_HELP_MESSAGE: Option<&'a str> =
90        Some("[Space] to select, [↑/↓] (or j/k) to reorder, [Enter] to apply");
91
92    /// Creates a [Sort] prompt with the provided message and options.
93    pub fn new(message: &'a str, options: Vec<T>) -> Self {
94        Self {
95            message,
96            options,
97            help_message: Self::DEFAULT_HELP_MESSAGE,
98            page_size: Self::DEFAULT_PAGE_SIZE,
99            vim_mode: Self::DEFAULT_VIM_MODE,
100            starting_cursor: Self::DEFAULT_STARTING_CURSOR,
101            formatter: Self::DEFAULT_FORMATTER,
102            render_config: get_configuration(),
103        }
104    }
105
106    /// Sets the help message to be presented to the user.
107    pub fn with_help_message(mut self, message: &'a str) -> Self {
108        self.help_message = Some(message);
109        self
110    }
111
112    /// Removes the help message.
113    pub fn without_help_message(mut self) -> Self {
114        self.help_message = None;
115        self
116    }
117
118    /// Sets the page size of the options displayed to the user.
119    pub fn with_page_size(mut self, page_size: usize) -> Self {
120        self.page_size = page_size;
121        self
122    }
123
124    /// Enables or disables Vim-style keybindings.
125    pub fn with_vim_mode(mut self, vim_mode: bool) -> Self {
126        self.vim_mode = vim_mode;
127        self
128    }
129
130    /// Sets the starting cursor index.
131    pub fn with_starting_cursor(mut self, starting_cursor: usize) -> Self {
132        self.starting_cursor = starting_cursor;
133        self
134    }
135
136    /// Sets the formatter to customize how the final selected answer is printed.
137    pub fn with_formatter(mut self, formatter: MultiOptionFormatter<'a, T>) -> Self {
138        self.formatter = formatter;
139        self
140    }
141
142    /// Sets the custom render configuration.
143    pub fn with_render_config(mut self, render_config: RenderConfig<'a>) -> Self {
144        self.render_config = render_config;
145        self
146    }
147
148    /// Prompts the user and returns the final sorted list of elements.
149    pub fn prompt(self) -> InquireResult<Vec<T>> {
150        self.raw_prompt()
151            .map(|op| op.into_iter().map(|o| o.value).collect())
152    }
153
154    /// Prompts the user and returns the final sorted list of elements,
155    /// returning `None` if the operation was canceled.
156    pub fn prompt_skippable(self) -> InquireResult<Option<Vec<T>>> {
157        match self.prompt() {
158            Ok(answer) => Ok(Some(answer)),
159            Err(InquireError::OperationCanceled) => Ok(None),
160            Err(err) => Err(err),
161        }
162    }
163
164    /// Prompts the user and returns the final sorted list of options with original indices,
165    /// returning `None` if the operation was canceled.
166    pub fn raw_prompt_skippable(self) -> InquireResult<Option<Vec<ListOption<T>>>> {
167        match self.raw_prompt() {
168            Ok(answer) => Ok(Some(answer)),
169            Err(InquireError::OperationCanceled) => Ok(None),
170            Err(err) => Err(err),
171        }
172    }
173
174    /// Prompts the user and returns the final sorted list of options, preserving original index details.
175    pub fn raw_prompt(self) -> InquireResult<Vec<ListOption<T>>> {
176        let (input_reader, terminal) = get_default_terminal()?;
177        let mut backend = Backend::new(input_reader, terminal, self.render_config)?;
178        self.prompt_with_backend(&mut backend)
179    }
180
181    /// Prompts the user using the provided rendering backend.
182    pub(crate) fn prompt_with_backend<B: SortBackend>(
183        self,
184        backend: &mut B,
185    ) -> InquireResult<Vec<ListOption<T>>> {
186        SortPrompt::new(self)?.prompt(backend)
187    }
188}