From e3788d290a4623831d4e75327c107085558df9ee Mon Sep 17 00:00:00 2001 From: Nicholas Ciechanowski Date: Mon, 6 Oct 2025 23:46:46 +1100 Subject: [PATCH 1/2] feat: native nvim `vim.ui.select` support --- lua/compiler/init.lua | 2 +- lua/compiler/picker.lua | 110 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 111 insertions(+), 1 deletion(-) create mode 100644 lua/compiler/picker.lua diff --git a/lua/compiler/init.lua b/lua/compiler/init.lua index 03926632..5d2e3e01 100644 --- a/lua/compiler/init.lua +++ b/lua/compiler/init.lua @@ -8,7 +8,7 @@ local M = {} M.setup = function(opts) cmd("CompilerOpen", function() - require("compiler.telescope").show() + require("compiler.picker").show() end, { desc = "Open the compiler" }) cmd("CompilerToggleResults", function() diff --git a/lua/compiler/picker.lua b/lua/compiler/picker.lua new file mode 100644 index 00000000..dd471b53 --- /dev/null +++ b/lua/compiler/picker.lua @@ -0,0 +1,110 @@ +--- ### Picker frontend for compiler.nvim +-- Automatically detects and uses telescope.nvim or falls back to vim.ui.select + +local M = {} + +function M.show() + local telescope_ok = pcall(require, "telescope") + + if telescope_ok then + require("compiler.telescope").show() + else + -- Fallback to vim.ui.select implementation + -- If working directory is home, don't open picker. + if vim.loop.os_homedir() == vim.loop.cwd() then + vim.notify( + "You must :cd your project dir first.\nHome is not allowed as working dir.", + vim.log.levels.WARN, + { + title = "Compiler.nvim", + } + ) + return + end + + local utils = require("compiler.utils") + local utils_bau = require("compiler.utils-bau") + + local buffer = vim.api.nvim_get_current_buf() + local filetype = + vim.api.nvim_get_option_value("filetype", { buf = buffer }) + + -- POPULATE + -- ======================================================================== + + -- Programatically require the backend for the current language. + local language = utils.require_language(filetype) + + -- On unsupported languages, default to make. + if not language then language = utils.require_language("make") or {} end + + -- Also show options discovered on Makefile, Cmake... and other bau. + if not language.bau_added then + language.bau_added = true + local bau_opts = utils_bau.get_bau_opts() + + -- Insert a separator for every bau. + local last_bau_value = nil + for _, item in ipairs(bau_opts) do + if last_bau_value ~= item.bau then + table.insert(language.options, { text = "", value = "separator" }) + last_bau_value = item.bau + end + table.insert(language.options, item) + end + end + + -- Add numbers in front of the options to display. + local index_counter = 0 + for _, option in ipairs(language.options) do + if option.value ~= "separator" then + index_counter = index_counter + 1 + option.text = index_counter .. " - " .. option.text + end + end + + -- Create items for vim.ui.select (filter out separators) + local items = {} + local item_map = {} + for _, option in ipairs(language.options) do + if option.value ~= "separator" then + table.insert(items, option.text) + item_map[option.text] = { value = option.value, bau = option.bau } + end + end + + -- SHOW VIM.UI.SELECT + -- ======================================================================== + vim.ui.select(items, { + prompt = "Compiler: ", + }, function(choice) + if not choice then return end + local selected = item_map[choice] + if not selected or selected.value == "" then return end + + -- Do the selected option belong to a build automation utility? + local bau = selected.bau + if bau then -- call the bau backend. + bau = utils_bau.require_bau(bau) + if bau then bau.action(selected.value) end + -- then + -- clean redo (language) + _G.compiler_redo_selection = nil + -- save redo (bau) + _G.compiler_redo_bau_selection = selected.value + _G.compiler_redo_bau = bau + else -- call the language backend. + language.action(selected.value) + -- then + -- save redo (language) + _G.compiler_redo_selection = selected.value + _G.compiler_redo_filetype = filetype + -- clean redo (bau) + _G.compiler_redo_bau_selection = nil + _G.compiler_redo_bau = nil + end + end) + end +end + +return M From dba7c3765ea784a66e945a5be16a3cab8d5f5b6d Mon Sep 17 00:00:00 2001 From: Nicholas Ciechanowski Date: Tue, 7 Oct 2025 00:05:05 +1100 Subject: [PATCH 2/2] feat: picker-util to remove duplicated code --- lua/compiler/picker-util.lua | 131 +++++++++++++++++++++++++++++++++++ lua/compiler/picker.lua | 106 ++++++---------------------- lua/compiler/telescope.lua | 107 ++++++---------------------- 3 files changed, 176 insertions(+), 168 deletions(-) create mode 100644 lua/compiler/picker-util.lua diff --git a/lua/compiler/picker-util.lua b/lua/compiler/picker-util.lua new file mode 100644 index 00000000..6b824ac8 --- /dev/null +++ b/lua/compiler/picker-util.lua @@ -0,0 +1,131 @@ +--- ### Shared utilities for compiler.nvim pickers + +local M = {} + +--- Validates that the current working directory is not the home directory +--- @return boolean true if valid, false if invalid (also shows notification) +function M.validate_working_directory() + if vim.loop.os_homedir() == vim.loop.cwd() then + vim.notify( + "You must :cd your project dir first.\nHome is not allowed as working dir.", + vim.log.levels.WARN, + { + title = "Compiler.nvim", + } + ) + return false + end + return true +end + +--- Prepares compiler options by gathering language and BAU options +--- @return table { language, options, filetype } +function M.prepare_compiler_options() + local utils = require("compiler.utils") + local utils_bau = require("compiler.utils-bau") + + local buffer = vim.api.nvim_get_current_buf() + local filetype = vim.api.nvim_get_option_value("filetype", { buf = buffer }) + + -- Programatically require the backend for the current language. + local language = utils.require_language(filetype) + + -- On unsupported languages, default to make. + if not language then language = utils.require_language("make") or {} end + + -- Also show options discovered on Makefile, Cmake... and other bau. + if not language.bau_added then + language.bau_added = true + local bau_opts = utils_bau.get_bau_opts() + + -- Insert a separator for every bau. + local last_bau_value = nil + for _, item in ipairs(bau_opts) do + if last_bau_value ~= item.bau then + table.insert(language.options, { text = "", value = "separator" }) + last_bau_value = item.bau + end + table.insert(language.options, item) + end + end + + -- Add numbers in front of the options to display. + local index_counter = 0 + for _, option in ipairs(language.options) do + if option.value ~= "separator" then + index_counter = index_counter + 1 + option.text = index_counter .. " - " .. option.text + end + end + + return { + language = language, + options = language.options, + filetype = filetype, + } +end + +--- Executes the selected compiler option +--- @param selected_value string The value of the selected option +--- @param selected_text string The display text of the selected option +--- @param language_options table The full options table +--- @param filetype string The current filetype +function M.execute_selection( + selected_value, + selected_text, + language_options, + language, + filetype +) + if selected_value == "" or selected_value == "separator" then return end + + local utils_bau = require("compiler.utils-bau") + + -- Do the selected option belong to a build automation utility? + local bau = nil + for _, value in ipairs(language_options) do + if value.text == selected_text or value.value == selected_value then + bau = value.bau + break + end + end + + if bau then -- call the bau backend. + bau = utils_bau.require_bau(bau) + if bau then bau.action(selected_value) end + -- then + -- clean redo (language) + _G.compiler_redo_selection = nil + -- save redo (bau) + _G.compiler_redo_bau_selection = selected_value + _G.compiler_redo_bau = bau + else -- call the language backend. + language.action(selected_value) + -- then + -- save redo (language) + _G.compiler_redo_selection = selected_value + _G.compiler_redo_filetype = filetype + -- clean redo (bau) + _G.compiler_redo_bau_selection = nil + _G.compiler_redo_bau = nil + end +end + +--- Creates items and mapping for vim.ui.select (filters out separators) +--- @param language_options table The full options table +--- @return table { items, item_map } +function M.create_select_items(language_options) + local items = {} + local item_map = {} + + for _, option in ipairs(language_options) do + if option.value ~= "separator" then + table.insert(items, option.text) + item_map[option.text] = { value = option.value, bau = option.bau } + end + end + + return { items = items, item_map = item_map } +end + +return M diff --git a/lua/compiler/picker.lua b/lua/compiler/picker.lua index dd471b53..91aeec21 100644 --- a/lua/compiler/picker.lua +++ b/lua/compiler/picker.lua @@ -10,99 +10,37 @@ function M.show() require("compiler.telescope").show() else -- Fallback to vim.ui.select implementation - -- If working directory is home, don't open picker. - if vim.loop.os_homedir() == vim.loop.cwd() then - vim.notify( - "You must :cd your project dir first.\nHome is not allowed as working dir.", - vim.log.levels.WARN, - { - title = "Compiler.nvim", - } - ) - return - end - - local utils = require("compiler.utils") - local utils_bau = require("compiler.utils-bau") - - local buffer = vim.api.nvim_get_current_buf() - local filetype = - vim.api.nvim_get_option_value("filetype", { buf = buffer }) - - -- POPULATE - -- ======================================================================== - - -- Programatically require the backend for the current language. - local language = utils.require_language(filetype) + local picker_util = require("compiler.picker-util") - -- On unsupported languages, default to make. - if not language then language = utils.require_language("make") or {} end + -- Validate working directory + if not picker_util.validate_working_directory() then return end - -- Also show options discovered on Makefile, Cmake... and other bau. - if not language.bau_added then - language.bau_added = true - local bau_opts = utils_bau.get_bau_opts() + -- Prepare compiler options + local compiler_data = picker_util.prepare_compiler_options() + local language = compiler_data.language + local options = compiler_data.options + local filetype = compiler_data.filetype - -- Insert a separator for every bau. - local last_bau_value = nil - for _, item in ipairs(bau_opts) do - if last_bau_value ~= item.bau then - table.insert(language.options, { text = "", value = "separator" }) - last_bau_value = item.bau - end - table.insert(language.options, item) - end - end + -- Create items for vim.ui.select + local select_data = picker_util.create_select_items(options) + local items = select_data.items + local item_map = select_data.item_map - -- Add numbers in front of the options to display. - local index_counter = 0 - for _, option in ipairs(language.options) do - if option.value ~= "separator" then - index_counter = index_counter + 1 - option.text = index_counter .. " - " .. option.text - end - end - - -- Create items for vim.ui.select (filter out separators) - local items = {} - local item_map = {} - for _, option in ipairs(language.options) do - if option.value ~= "separator" then - table.insert(items, option.text) - item_map[option.text] = { value = option.value, bau = option.bau } - end - end - - -- SHOW VIM.UI.SELECT - -- ======================================================================== + -- Show vim.ui.select vim.ui.select(items, { prompt = "Compiler: ", }, function(choice) if not choice then return end local selected = item_map[choice] - if not selected or selected.value == "" then return end - - -- Do the selected option belong to a build automation utility? - local bau = selected.bau - if bau then -- call the bau backend. - bau = utils_bau.require_bau(bau) - if bau then bau.action(selected.value) end - -- then - -- clean redo (language) - _G.compiler_redo_selection = nil - -- save redo (bau) - _G.compiler_redo_bau_selection = selected.value - _G.compiler_redo_bau = bau - else -- call the language backend. - language.action(selected.value) - -- then - -- save redo (language) - _G.compiler_redo_selection = selected.value - _G.compiler_redo_filetype = filetype - -- clean redo (bau) - _G.compiler_redo_bau_selection = nil - _G.compiler_redo_bau = nil - end + if not selected then return end + + picker_util.execute_selection( + selected.value, + choice, + options, + language, + filetype + ) end) end end diff --git a/lua/compiler/telescope.lua b/lua/compiler/telescope.lua index 408dabef..5fc0f512 100644 --- a/lua/compiler/telescope.lua +++ b/lua/compiler/telescope.lua @@ -3,61 +3,23 @@ local M = {} function M.show() - -- If working directory is home, don't open telescope. - if vim.loop.os_homedir() == vim.loop.cwd() then - vim.notify("You must :cd your project dir first.\nHome is not allowed as working dir.", vim.log.levels.WARN, { - title = "Compiler.nvim" - }) - return - end + local picker_util = require("compiler.picker-util") + + -- Validate working directory + if not picker_util.validate_working_directory() then return end -- Dependencies local conf = require("telescope.config").values - local actions = require "telescope.actions" - local state = require "telescope.actions.state" - local pickers = require "telescope.pickers" - local finders = require "telescope.finders" - local utils = require("compiler.utils") - local utils_bau = require("compiler.utils-bau") - - local buffer = vim.api.nvim_get_current_buf() - local filetype = vim.api.nvim_get_option_value("filetype", { buf = buffer }) - - - -- POPULATE - -- ======================================================================== - - -- Programatically require the backend for the current language. - local language = utils.require_language(filetype) - - -- On unsupported languages, default to make. - if not language then language = utils.require_language("make") or {} end - - -- Also show options discovered on Makefile, Cmake... and other bau. - if not language.bau_added then - language.bau_added = true - local bau_opts = utils_bau.get_bau_opts() - - -- Insert a separator on telescope for every bau. - local last_bau_value = nil - for _, item in ipairs(bau_opts) do - if last_bau_value ~= item.bau then - table.insert(language.options, { text = "", value = "separator" }) - last_bau_value = item.bau - end - table.insert(language.options, item) - end - end - - -- Add numbers in front of the options to display. - local index_counter = 0 - for _, option in ipairs(language.options) do - if option.value ~= "separator" then - index_counter = index_counter + 1 - option.text = index_counter .. " - " .. option.text - end - end + local actions = require("telescope.actions") + local state = require("telescope.actions.state") + local pickers = require("telescope.pickers") + local finders = require("telescope.finders") + -- Prepare compiler options + local compiler_data = picker_util.prepare_compiler_options() + local language = compiler_data.language + local language_options = compiler_data.options + local filetype = compiler_data.filetype -- RUN ACTION ON SELECTED -- ======================================================================== @@ -66,41 +28,18 @@ function M.show() local function on_option_selected(prompt_bufnr) actions.close(prompt_bufnr) -- Close Telescope on selection local selection = state.get_selected_entry() - if selection.value == "" then return end -- Ignore separators if selection then - -- Do the selected option belong to a build automation utility? - local bau = nil - for _, value in ipairs(language.options) do - if value.text == selection.display then - bau = value.bau - end - end - - if bau then -- call the bau backend. - bau = utils_bau.require_bau(bau) - if bau then bau.action(selection.value) end - -- then - -- clean redo (language) - _G.compiler_redo_selection = nil - -- save redo (bau) - _G.compiler_redo_bau_selection = selection.value - _G.compiler_redo_bau = bau - else -- call the language backend. - language.action(selection.value) - -- then - -- save redo (language) - _G.compiler_redo_selection = selection.value - _G.compiler_redo_filetype = filetype - -- clean redo (bau) - _G.compiler_redo_bau_selection = nil - _G.compiler_redo_bau = nil - end - + picker_util.execute_selection( + selection.value, + selection.display, + language_options, + language, + filetype + ) end end - -- SHOW TELESCOPE -- ======================================================================== local function open_telescope() @@ -108,8 +47,8 @@ function M.show() .new({}, { prompt_title = "Compiler", results_title = "Options", - finder = finders.new_table { - results = language.options, + finder = finders.new_table({ + results = language_options, entry_maker = function(entry) return { display = entry.text, @@ -117,7 +56,7 @@ function M.show() ordinal = entry.text, } end, - }, + }), sorter = conf.generic_sorter(), attach_mappings = function(_, map) map(