ytil_noxi/
vim_ui_select.rs

1//! Implementation of Nvim's vim.ui.select for interactive user selection.
2
3use core::fmt::Debug;
4use core::fmt::Display;
5use std::rc::Rc;
6
7use color_eyre::eyre::eyre;
8use nvim_oxi::mlua;
9use nvim_oxi::mlua::IntoLua;
10use nvim_oxi::mlua::ObjectLike;
11
12/// Configuration for quickfix list population and display.
13///
14/// Holds the trigger value that initiated the quickfix operation and the complete
15/// set of items to populate the quickfix list, where each item consists of a
16/// filename and its corresponding line number.
17pub struct QuickfixConfig {
18    /// The value that triggered the quickfix operation.
19    pub trigger_value: String,
20    /// List of all items to populate the quickfix list, each as filename and line number.
21    pub all_items: Vec<(String, i64)>,
22}
23
24/// Prompts the user to select an item from a list using Nvim's `vim.ui.select`.
25///
26/// Wraps the Lua `vim.ui.select` function to provide an interactive selection prompt.
27/// The selected index (0-based) is passed to the provided callback. If a quickfix
28/// configuration is provided, an additional synthetic choice is added to open the
29/// quickfix list instead.
30///
31/// # Errors
32/// - Fails if `vim.ui.select` cannot be fetched from Lua globals.
33/// - Fails if the options table cannot be created.
34/// - Fails if calling `vim.ui.select` encounters an error.
35pub fn open<C, K, V>(
36    choices: impl IntoIterator<Item = C> + Debug,
37    opts: &(impl IntoIterator<Item = (K, V)> + Debug + Clone),
38    callback: impl Fn(usize) + 'static,
39    maybe_quickfix: Option<QuickfixConfig>,
40) -> color_eyre::Result<()>
41where
42    C: Display,
43    K: IntoLua,
44    V: IntoLua,
45{
46    let lua = mlua::lua();
47
48    let vim_ui_select = lua
49        .globals()
50        .get_path::<mlua::Function>("vim.ui.select")
51        .map_err(|err| eyre!("cannot fetch vim.ui.select function from Lua globals | error={err:#?}"))?;
52
53    let opts_table = lua
54        .create_table_from(opts.clone())
55        .map_err(|err| eyre!("cannot create opts table | opts={opts:#?} error={err:#?}"))?;
56
57    let quickfix = maybe_quickfix.map(Rc::new);
58
59    let vim_ui_select_callback = lua
60        .create_function(
61            move |_: &mlua::Lua, (selected_value, idx): (Option<String>, Option<usize>)| {
62                if let Some(quickfix) = &quickfix
63                    && selected_value.is_some_and(|x| x == quickfix.trigger_value)
64                {
65                    let _ = crate::quickfix::open(quickfix.all_items.iter().map(|(s, i)| (s.as_str(), *i)))
66                        .inspect_err(|err| {
67                            crate::notify::error(format!("error opening quickfix | error={err:#?}"));
68                        });
69                    return Ok(());
70                }
71                if let Some(idx) = idx {
72                    callback(idx.saturating_sub(1));
73                }
74                Ok(())
75            },
76        )
77        .map_err(|err| {
78            eyre!("cannot create vim.ui.select callback | choices={choices:#?} opts={opts_table:#?} error={err:#?}")
79        })?;
80
81    let vim_ui_choices = choices.into_iter().map(|c| c.to_string()).collect::<Vec<_>>();
82
83    vim_ui_select
84        .call::<()>((vim_ui_choices.clone(), opts_table.clone(), vim_ui_select_callback))
85        .map_err(|err| {
86            eyre!("cannot call vim.ui.select | choices={vim_ui_choices:#?} opts={opts_table:#?} error={err:#?}")
87        })?;
88
89    Ok(())
90}