Skip to main content

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 nvim_oxi::mlua;
8use nvim_oxi::mlua::IntoLua;
9use nvim_oxi::mlua::ObjectLike;
10use rootcause::report;
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) -> rootcause::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| report!("cannot fetch vim.ui.select function from Lua globals").attach(err.to_string()))?;
52
53    let opts_table = lua.create_table_from(opts.clone()).map_err(|err| {
54        report!("cannot create opts table")
55            .attach(format!("opts={opts:#?}"))
56            .attach(err.to_string())
57    })?;
58
59    let quickfix = maybe_quickfix.map(Rc::new);
60
61    let vim_ui_select_callback = lua
62        .create_function(
63            move |_: &mlua::Lua, (selected_value, idx): (Option<String>, Option<usize>)| {
64                if let Some(quickfix) = &quickfix
65                    && selected_value.is_some_and(|x| x == quickfix.trigger_value)
66                {
67                    let _ = crate::quickfix::open(quickfix.all_items.iter().map(|(s, i)| (s.as_str(), *i)))
68                        .inspect_err(|err| {
69                            crate::notify::error(format!("error opening quickfix: {err:#}"));
70                        });
71                    return Ok(());
72                }
73                if let Some(idx) = idx {
74                    callback(idx.saturating_sub(1));
75                }
76                Ok(())
77            },
78        )
79        .map_err(|err| {
80            report!("cannot create vim.ui.select callback")
81                .attach(format!("choices={choices:#?} opts={opts_table:#?}"))
82                .attach(err.to_string())
83        })?;
84
85    let vim_ui_choices = choices.into_iter().map(|c| c.to_string()).collect::<Vec<_>>();
86
87    vim_ui_select
88        .call::<()>((vim_ui_choices.clone(), opts_table.clone(), vim_ui_select_callback))
89        .map_err(|err| {
90            report!("cannot call vim.ui.select")
91                .attach(format!("choices={vim_ui_choices:#?} opts={opts_table:#?}"))
92                .attach(err.to_string())
93        })?;
94
95    Ok(())
96}