From 52cca9d7c44a3628e7e663a5da6484f8fca8089c Mon Sep 17 00:00:00 2001 From: Anshuman Date: Sat, 4 Mar 2023 19:19:43 +0800 Subject: [PATCH 1/5] vim.ui.select for code_action_group --- lua/rust-tools/code_action_group.lua | 330 ++++----------------------- 1 file changed, 50 insertions(+), 280 deletions(-) diff --git a/lua/rust-tools/code_action_group.lua b/lua/rust-tools/code_action_group.lua index 66900bf..6b91e48 100644 --- a/lua/rust-tools/code_action_group.lua +++ b/lua/rust-tools/code_action_group.lua @@ -1,8 +1,7 @@ -local utils = require("rust-tools.utils.utils") local M = {} ---@private -function M.apply_action(action, client, ctx) +local function apply_action(action, client, ctx) if action.edit then vim.lsp.util.apply_workspace_edit(action.edit, client.offset_encoding) end @@ -20,8 +19,8 @@ function M.apply_action(action, client, ctx) end ---@private -function M.on_user_choice(action_tuple, ctx) - if not action_tuple then +local function on_user_choice(action, ctx) + if not action then return end -- textDocument/codeAction can return either Command[] or CodeAction[] @@ -36,8 +35,7 @@ function M.on_user_choice(action_tuple, ctx) -- command: string -- arguments?: any[] -- - local client = vim.lsp.get_client_by_id(action_tuple[1]) - local action = action_tuple[2] + local client = vim.lsp.get_client_by_id(action.client_id) local code_action_provider = nil if vim.fn.has("nvim-0.8.0") then code_action_provider = client.server_capabilities.codeActionProvider @@ -55,60 +53,23 @@ function M.on_user_choice(action_tuple, ctx) vim.notify(err.code .. ": " .. err.message, vim.log.levels.ERROR) return end - M.apply_action(resolved_action, client, ctx) + apply_action(resolved_action, client, ctx) end) else - M.apply_action(action, client, ctx) + apply_action(action, client, ctx) end end -local function compute_width(action_tuples, is_group) - local width = 0 - - for _, value in pairs(action_tuples) do - local action = value[2] - local text = action.title - - if is_group and action.group then - text = action.group .. " ▶" - end - local len = string.len(text) - if len > width then - width = len - end - end - - return { width = width + 5 } -end - -local function on_primary_enter_press() - if M.state.secondary.winnr then - vim.api.nvim_set_current_win(M.state.secondary.winnr) - return - end - - local line = vim.api.nvim_win_get_cursor(M.state.secondary.winnr or 0)[1] - - for _, value in ipairs(M.state.actions.ungrouped) do - if value[2].idx == line then - M.on_user_choice(value, M.state.ctx) - end +local select_code_action_results = function(results, ctx) + if results.error then + vim.notify(results.error, vim.log.levels.ERROR) end - M.cleanup() -end - -local function on_primary_quit() - M.cleanup() -end - -local function on_code_action_results(results, ctx) - M.state.ctx = ctx - local action_tuples = {} for client_id, result in pairs(results) do for _, action in pairs(result.result or {}) do - table.insert(action_tuples, { client_id, action }) + action.client_id = client_id + table.insert(action_tuples, action) end end if #action_tuples == 0 then @@ -116,252 +77,61 @@ local function on_code_action_results(results, ctx) return end - M.state.primary.geometry = compute_width(action_tuples, true) - - M.state.actions.grouped = {} - - M.state.actions.ungrouped = {} - - for _, value in ipairs(action_tuples) do - local action = value[2] - + local entries, groups = {}, {} + for _, action in ipairs(action_tuples) do -- Some clippy lints may have newlines in them action.title = string.gsub(action.title, "[\n\r]+", " ") if action.group then - if not M.state.actions.grouped[action.group] then - M.state.actions.grouped[action.group] = { actions = {}, idx = nil } + if not groups[action.group] then + local group = + { title = action.group .. " ▶", actions = {}, idx = nil } + groups[action.group] = group + table.insert(entries, group) end - table.insert(M.state.actions.grouped[action.group].actions, value) + table.insert(groups[action.group].actions, action) else - table.insert(M.state.actions.ungrouped, value) + table.insert(entries, action) end end - M.state.primary.bufnr = vim.api.nvim_create_buf(false, true) - M.state.primary.winnr = vim.api.nvim_open_win(M.state.primary.bufnr, true, { - relative = "cursor", - width = M.state.primary.geometry.width, - height = vim.tbl_count(M.state.actions.grouped) - + vim.tbl_count(M.state.actions.ungrouped), - focusable = true, - border = "rounded", - row = 1, - col = 0, - }) - - local idx = 1 - for key, value in pairs(M.state.actions.grouped) do - value.idx = idx - vim.api.nvim_buf_set_lines( - M.state.primary.bufnr, - -1, - -1, - false, - { key .. " ▶" } - ) - idx = idx + 1 - end - - for _, value in pairs(M.state.actions.ungrouped) do - local action = value[2] - value[2].idx = idx - vim.api.nvim_buf_set_lines( - M.state.primary.bufnr, - -1, - -1, - false, - { action.title } - ) - idx = idx + 1 - end - - vim.api.nvim_buf_set_lines(M.state.primary.bufnr, 0, 1, false, {}) - - vim.keymap.set( - "n", - "", - on_primary_enter_press, - { buffer = M.state.primary.bufnr } - ) - - vim.keymap.set("n", "q", on_primary_quit, { buffer = M.state.primary.bufnr }) - - M.codeactionify_window_buffer(M.state.primary.winnr, M.state.primary.bufnr) - - vim.api.nvim_buf_attach(M.state.primary.bufnr, false, { - on_detach = function(_, _) - M.state.primary.clear() - vim.schedule(M.cleanup) + vim.ui.select(entries, { + prompt = "Code Actions:", + format_item = function(item) + return item.title end, - }) - - vim.api.nvim_create_autocmd("CursorMoved", { - buffer = M.state.primary.bufnr, - callback = M.on_cursor_move, - }) - - vim.cmd("redraw") -end - -function M.codeactionify_window_buffer(winnr, bufnr) - vim.api.nvim_buf_set_option(bufnr, "modifiable", false) - vim.api.nvim_buf_set_option(bufnr, "bufhidden", "delete") - vim.api.nvim_buf_set_option(bufnr, "buftype", "nofile") - - vim.api.nvim_win_set_option(winnr, "nu", true) - vim.api.nvim_win_set_option(winnr, "rnu", false) - vim.api.nvim_win_set_option(winnr, "cul", true) -end - -local function on_secondary_enter_press() - local line = vim.api.nvim_win_get_cursor(M.state.secondary.winnr)[1] - local active_group = nil - - for _, value in pairs(M.state.actions.grouped) do - if value.idx == M.state.active_group_index then - active_group = value - break - end - end - - if active_group then - for _, value in pairs(active_group.actions) do - if value[2].idx == line then - M.on_user_choice(value, M.state.ctx) - end - end - end - - M.cleanup() -end - -local function on_secondary_quit() - local winnr = M.state.secondary.winnr - -- we clear first because if we close the window first, the cursor moved - -- autocmd of the first buffer gets called which then sees that - -- M.state.secondary.winnr exists (when it shouldnt because it is closed) - -- and errors out - M.state.secondary.clear() - - utils.close_win(winnr) -end - -function M.cleanup() - if M.state.primary.winnr then - utils.close_win(M.state.primary.winnr) - M.state.primary.clear() - end - - if M.state.secondary.winnr then - utils.close_win(M.state.secondary.winnr) - M.state.secondary.clear() - end - - M.state.actions = {} - M.state.active_group_index = nil - M.state.ctx = {} -end - -function M.on_cursor_move() - local line = vim.api.nvim_win_get_cursor(M.state.primary.winnr)[1] - - for _, value in pairs(M.state.actions.grouped) do - if value.idx == line then - M.state.active_group_index = line - - if M.state.secondary.winnr then - utils.close_win(M.state.secondary.winnr) - M.state.secondary.clear() - end - - M.state.secondary.geometry = compute_width(value.actions, false) - - M.state.secondary.bufnr = vim.api.nvim_create_buf(false, true) - M.state.secondary.winnr = - vim.api.nvim_open_win(M.state.secondary.bufnr, false, { - relative = "win", - win = M.state.primary.winnr, - width = M.state.secondary.geometry.width, - height = #value.actions, - focusable = true, - border = "rounded", - row = line - 2, - col = M.state.primary.geometry.width + 1, - }) - - local idx = 1 - for _, inner_value in pairs(value.actions) do - local action = inner_value[2] - action.idx = idx - vim.api.nvim_buf_set_lines( - M.state.secondary.bufnr, - -1, - -1, - false, - { action.title } - ) - idx = idx + 1 - end - - vim.api.nvim_buf_set_lines(M.state.secondary.bufnr, 0, 1, false, {}) - - M.codeactionify_window_buffer( - M.state.secondary.winnr, - M.state.secondary.bufnr - ) - - vim.keymap.set( - "n", - "", - on_secondary_enter_press, - { buffer = M.state.secondary.bufnr } - ) - - vim.keymap.set( - "n", - "q", - on_secondary_quit, - { buffer = M.state.secondary.bufnr } - ) - + }, function(selected) + if not selected then return end - if M.state.secondary.winnr then - utils.close_win(M.state.secondary.winnr) - M.state.secondary.clear() + if selected.actions then + vim.schedule(function() + vim.ui.select(selected.actions, { + prompt = selected.title .. ": ", + format_item = function(item) + return item.title + end, + }, function(selected_group) + if not selected_group then + return + end + print(vim.inspect(selected_group)) + + vim.schedule(function() + on_user_choice(selected_group, ctx) + end) + end) + end) + else + vim.schedule(function() + on_user_choice(selected, ctx) + end) end - end + end) end -M.state = { - ctx = {}, - actions = {}, - active_group_index = nil, - primary = { - bufnr = nil, - winnr = nil, - geometry = nil, - clear = function() - M.state.primary.geometry = nil - M.state.primary.bufnr = nil - M.state.primary.winnr = nil - end, - }, - secondary = { - bufnr = nil, - winnr = nil, - geometry = nil, - clear = function() - M.state.secondary.geometry = nil - M.state.secondary.bufnr = nil - M.state.secondary.winnr = nil - end, - }, -} - function M.code_action_group() local context = {} context.diagnostics = vim.lsp.diagnostic.get_line_diagnostics() @@ -373,7 +143,7 @@ function M.code_action_group() "textDocument/codeAction", params, function(results) - on_code_action_results( + select_code_action_results( results, { bufnr = 0, method = "textDocument/codeAction", params = params } ) From 2b3344aeb892e49c60339439ece97ab0c365e974 Mon Sep 17 00:00:00 2001 From: Anshuman Date: Sun, 5 Mar 2023 14:00:54 +0800 Subject: [PATCH 2/5] Telescope based selector for previews I wish vim.ui.select had a way to supply the secondary window information --- README.md | 7 +++ doc/rust-tools.txt | 7 +++ lua/rust-tools/code_action_group.lua | 88 ++++++++++++++++++++++++++-- lua/rust-tools/config.lua | 7 +++ 4 files changed, 103 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 72f67a8..7e514be 100644 --- a/README.md +++ b/README.md @@ -237,6 +237,13 @@ local opts = { -- automatically call RustReloadWorkspace when writing to a Cargo.toml file. reload_workspace_from_cargo_toml = true, + code_action_group = { + -- Function with signature of `vim.ui.select` used to display the code_actions + -- default: require("rust-tools.code_action_group").telescope_select(), + selector = require("rust-tools.code_action_group").telescope_select(), + -- selector = vim.ui.select, + }, + -- These apply to the default RustSetInlayHints command inlay_hints = { -- automatically set inlay hints (type hints) diff --git a/doc/rust-tools.txt b/doc/rust-tools.txt index 6242870..da881fa 100644 --- a/doc/rust-tools.txt +++ b/doc/rust-tools.txt @@ -162,6 +162,13 @@ for keys that are not provided. -- automatically call RustReloadWorkspace when writing to a Cargo.toml file. reload_workspace_from_cargo_toml = true, + + code_action_group = { + -- Function with signature of `vim.ui.select` used to display the code_actions + -- default: require("rust-tools.code_action_group").telescope_select(), + selector = require("rust-tools.code_action_group").telescope_select(), + -- selector = vim.ui.select, + }, -- These apply to the default RustSetInlayHints command inlay_hints = { diff --git a/lua/rust-tools/code_action_group.lua b/lua/rust-tools/code_action_group.lua index 6b91e48..7ff38d0 100644 --- a/lua/rust-tools/code_action_group.lua +++ b/lua/rust-tools/code_action_group.lua @@ -13,7 +13,8 @@ local function apply_action(action, client, ctx) enriched_ctx.client_id = client.id fn(command, ctx) else - M.execute_command(command) + local opts = require("rust-tools").config.options.tools + opts.executor.execute_command(command) end end end @@ -60,9 +61,71 @@ local function on_user_choice(action, ctx) end end -local select_code_action_results = function(results, ctx) +function M.telescope_select(picker_opts, sorter_opts) + picker_opts = picker_opts or require("telescope.themes").get_cursor() -- get_dropdown + local conf = require("telescope.config").values + + return function(items, opts, on_choice) + opts = opts or {} + local prompt_title = opts.prompt or "Rust Code Actions" + + require("telescope.pickers") + .new(picker_opts, { + prompt_title = prompt_title, + finder = require("telescope.finders").new_table({ + results = items, -- TODO: + entry_maker = function(entry) + local str = entry.title + + return { + value = entry, + display = str, + ordinal = str, + } + end, + }), + sorter = conf.generic_sorter(sorter_opts), + previewer = require("telescope.previewers").new_buffer_previewer({ + define_preview = function(self, entry, status) + local bufnr = self.state.bufnr + local item = entry.value + vim.api.nvim_buf_set_lines( + bufnr, + 0, + -1, + false, + M.code_action_submenu(item) + ) + end, + }), + attach_mappings = function(prompt_bufnr, map) + require("telescope.actions").select_default:replace(function() + require("telescope.actions").close(prompt_bufnr) + local selection = + require("telescope.actions.state").get_selected_entry() + on_choice(selection.value, selection.index) + end) + return true + end, + }) + :find() + end +end + +M.code_action_submenu = function(item) + if item.actions then + vim.tbl_map(function(action) + return " ▶" .. action.title + end, item.actions) + else + return {} + end +end + +M.code_actions_results_to_nested_table = function(results) if results.error then vim.notify(results.error, vim.log.levels.ERROR) + return end local action_tuples = {} @@ -96,11 +159,25 @@ local select_code_action_results = function(results, ctx) end end - vim.ui.select(entries, { + return entries +end + +local select_code_action_results = function(results, ctx) + local entries = M.code_actions_results_to_nested_table(results) + if entries == nil then + return + end + local opts = require("rust-tools").config.options.tools + local select = opts.code_action_group.selector + + select(entries, { prompt = "Code Actions:", format_item = function(item) return item.title end, + preview_item = function(item) + return M.code_action_submenu(item) + end, }, function(selected) if not selected then return @@ -108,8 +185,8 @@ local select_code_action_results = function(results, ctx) if selected.actions then vim.schedule(function() - vim.ui.select(selected.actions, { - prompt = selected.title .. ": ", + select(selected.actions, { + prompt = selected.title, format_item = function(item) return item.title end, @@ -117,7 +194,6 @@ local select_code_action_results = function(results, ctx) if not selected_group then return end - print(vim.inspect(selected_group)) vim.schedule(function() on_user_choice(selected_group, ctx) diff --git a/lua/rust-tools/config.lua b/lua/rust-tools/config.lua index d4ce0ac..b2e4cfd 100644 --- a/lua/rust-tools/config.lua +++ b/lua/rust-tools/config.lua @@ -21,6 +21,13 @@ local defaults = { -- automatically call RustReloadWorkspace when writing to a Cargo.toml file. reload_workspace_from_cargo_toml = true, + code_action_group = { + -- Function with signature of `vim.ui.select` used to display the code_actions + -- default: require("rust-tools.code_action_group").telescope_select(), + selector = require("rust-tools.code_action_group").telescope_select(), + -- selector = vim.ui.select, + }, + -- These apply to the default RustSetInlayHints command inlay_hints = { -- automatically set inlay hints (type hints) From 7f0422921261693168bb2db51252ca3cfd8a5dbb Mon Sep 17 00:00:00 2001 From: IndianBoy42 Date: Sun, 5 Mar 2023 06:06:58 +0000 Subject: [PATCH 3/5] Auto generate docs --- doc/rust-tools.txt | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/doc/rust-tools.txt b/doc/rust-tools.txt index da881fa..bf89813 100644 --- a/doc/rust-tools.txt +++ b/doc/rust-tools.txt @@ -1,4 +1,4 @@ -*rust-tools.txt* For NVIM v0.8.0 Last change: 2023 February 20 +*rust-tools.txt* For NVIM v0.8.0 Last change: 2023 March 05 ============================================================================== Table of Contents *rust-tools-table-of-contents* @@ -162,13 +162,13 @@ for keys that are not provided. -- automatically call RustReloadWorkspace when writing to a Cargo.toml file. reload_workspace_from_cargo_toml = true, - - code_action_group = { - -- Function with signature of `vim.ui.select` used to display the code_actions - -- default: require("rust-tools.code_action_group").telescope_select(), - selector = require("rust-tools.code_action_group").telescope_select(), - -- selector = vim.ui.select, - }, + + code_action_group = { + -- Function with signature of `vim.ui.select` used to display the code_actions + -- default: require("rust-tools.code_action_group").telescope_select(), + selector = require("rust-tools.code_action_group").telescope_select(), + -- selector = vim.ui.select, + }, -- These apply to the default RustSetInlayHints command inlay_hints = { From c758e1db841232c09ed42df845e2ece1f4d6ee59 Mon Sep 17 00:00:00 2001 From: Anshuman Date: Mon, 6 Mar 2023 21:50:10 +0800 Subject: [PATCH 4/5] fix typo --- lua/rust-tools/code_action_group.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/rust-tools/code_action_group.lua b/lua/rust-tools/code_action_group.lua index 7ff38d0..dedc484 100644 --- a/lua/rust-tools/code_action_group.lua +++ b/lua/rust-tools/code_action_group.lua @@ -114,7 +114,7 @@ end M.code_action_submenu = function(item) if item.actions then - vim.tbl_map(function(action) + return vim.tbl_map(function(action) return " ▶" .. action.title end, item.actions) else From ec0a013d014edf7807fa4e4be86333f344f7eb9d Mon Sep 17 00:00:00 2001 From: IndianBoy42 Date: Mon, 6 Mar 2023 13:50:42 +0000 Subject: [PATCH 5/5] Auto generate docs --- doc/rust-tools.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/rust-tools.txt b/doc/rust-tools.txt index bf89813..59ae1b8 100644 --- a/doc/rust-tools.txt +++ b/doc/rust-tools.txt @@ -1,4 +1,4 @@ -*rust-tools.txt* For NVIM v0.8.0 Last change: 2023 March 05 +*rust-tools.txt* For NVIM v0.8.0 Last change: 2023 March 06 ============================================================================== Table of Contents *rust-tools-table-of-contents*