Skip to content
This repository was archived by the owner on Dec 11, 2023. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 32 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,18 @@ This plugin provides a handy pop-up menu for code actions. Its key competence is
to provide the user with more detailed insights for each available code action.
Such include meta data as well as a preview diff for certain types of actions.
These includes:
- descriptive title (usually shown by all code action "menu" solutions)
- kind of the action (e.g. refactor, command, quickfix, organize imports, ...)
- name of the action
- if the action is the preferred one according to the server (preferred
actions get automatically sorted to the top of the menu)
- if the action is disabled (disabled actions can be used and get
automatically sorted to the bottom of the menu with a special hightlight)
- a preview of the changes this action will do in a diff view for all affected
files (includes a diff count box visualization as GitHub pul requests do)
- ...more will come, especially when servers start to use the `dataSupport`
capability for the code action provider

- descriptive title (usually shown by all code action "menu" solutions)
- kind of the action (e.g. refactor, command, quickfix, organize imports, ...)
- name of the action
- if the action is the preferred one according to the server (preferred
actions get automatically sorted to the top of the menu)
- if the action is disabled (disabled actions can be used and get
automatically sorted to the bottom of the menu with a special hightlight)
- a preview of the changes this action will do in a diff view for all affected
files (includes a diff count box visualization as GitHub pul requests do)
- ...more will come, especially when servers start to use the `dataSupport`
capability for the code action provider

The experience for all these features might vary according to the implementation
of the used language server. Especially for the diff view, do some servers still
Expand Down Expand Up @@ -59,6 +60,17 @@ current cursor location.
Please note that LSP and other features for this plugin require you to have at
least `v0.5.0` of NeoVim.

## Configuration

by default

```lua
require('code_action_menu.config').setup {
source = 'nvim_lsp', -- available values: nvim_lsp, coc
window_options = {}, -- additional options for code-aciton-menu windows
}
```

## Usage

The plugin has a single command only: `CodeActionMenu` This command works in
Expand All @@ -72,6 +84,15 @@ apply it right away. In any case the menu window will close itself. If you want
to manually close the window without selecting an action, just hit `<Esc>` or
`q`.

If you are using coc.nvim, just change the mappings

| Original | Now |
| --------------------------------- | ---------------------------------- |
| `<Plug>(coc-codeaction-selected)` | `<Plug>(codeaction-selected-menu)` |
| `<Plug>(coc-codeaction)` | `<Plug>(codeaction-menu)` |
| `<Plug>(coc-codeaction-line)` | `<Plug>(codeaction-line-menu)` |
| `<Plug>(coc-codeaction-cursor)` | `<Plug>(codeaction-cursor-menu)` |

## Customization

The plugin allows for a bunch of customization. While there is no classic
Expand Down
25 changes: 10 additions & 15 deletions lua/code_action_menu.lua
Original file line number Diff line number Diff line change
Expand Up @@ -39,29 +39,28 @@ local function close_warning_message_window()
end
end

local function open_code_action_menu()
-- Might still be open.
close_code_action_menu()
close_warning_message_window()

local use_range = vim.api.nvim_get_mode().mode ~= 'n'
local all_actions = action_utils.request_actions_from_all_servers(use_range)

if #all_actions == 0 then
local function actions_handler(actions)
if actions and #actions == 0 then
warning_message_window_instace = WarningMessageWindow:new()
warning_message_window_instace:open()
vim.api.nvim_command(
'autocmd! CursorMoved <buffer> ++once lua require("code_action_menu").close_warning_message_window()'
)
else
anchor_window_instance = AnchorWindow:new()
menu_window_instance = MenuWindow:new(all_actions)
menu_window_instance = MenuWindow:new(actions)
menu_window_instance:open({
window_stack = { anchor_window_instance },
})
end
end

local function open_code_action_menu(...)
close_code_action_menu()
close_warning_message_window()
action_utils.request_actions_from_all_servers(actions_handler, ...)
end

local function update_selected_action()
local selected_action = menu_window_instance:get_selected_action()

Expand Down Expand Up @@ -97,11 +96,7 @@ local function execute_selected_action()
local selected_action = menu_window_instance:get_selected_action()

if selected_action:is_disabled() then
vim.api.nvim_notify(
'Can not execute disabled action!',
vim.log.levels.ERROR,
{}
)
vim.notify('Can not execute disabled action!', vim.log.levels.ERROR)
else
close_code_action_menu() -- Close first to execute the action in the correct buffer.
selected_action:execute()
Expand Down
23 changes: 23 additions & 0 deletions lua/code_action_menu/config.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
local settings = {
source = 'nvim_lsp', -- nvim_lsp | coc
window_options = {}, -- additional options for code-aciton-menu windows
}

local function setup(opts)
settings = vim.tbl_extend('force', settings, opts or {})

if vim.g.coc_service_initialized == 1 and settings.source ~= 'coc' then
vim.notify(
('Detected that you are using COC, but the source is %s'):format(
settings.source
),
vim.log.levels.WARN
)
end
end

return setmetatable({ setup = setup }, {
__index = function(_, key)
return settings[key]
end,
})
27 changes: 17 additions & 10 deletions lua/code_action_menu/lsp_objects/actions/code_action.lua
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ local TextDocumentEdit = require(
local WorkspaceEdit = require(
'code_action_menu.lsp_objects.edits.workspace_edit'
)
local config = require('code_action_menu.config')

local CodeAction = BaseAction:new({})

Expand Down Expand Up @@ -77,16 +78,22 @@ function CodeAction:get_workspace_edit()
end

function CodeAction:execute()
if self:is_workspace_edit() then
vim.lsp.util.apply_workspace_edit(self.server_data.edit)
elseif self:is_command() then
vim.lsp.buf.execute_command(self.server_data.command)
else
vim.api.nvim_notify(
'Failed to execute code action of unknown kind!',
vim.log.levels.ERROR,
{}
)
local source = config.source
if source == 'nvim_lsp' then
do
if self:is_workspace_edit() then
vim.lsp.util.apply_workspace_edit(self.server_data.edit)
elseif self:is_command() then
vim.lsp.buf.execute_command(self.server_data.command)
else
vim.notify(
'Failed to execute code action of unknown kind!',
vim.log.levels.ERROR
)
end
end
elseif source == 'coc' then
vim.fn.CocActionAsync('doCodeAction', self.server_data)
end
end

Expand Down
8 changes: 7 additions & 1 deletion lua/code_action_menu/lsp_objects/actions/command.lua
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ local TextDocumentEdit = require(
local WorkspaceEdit = require(
'code_action_menu.lsp_objects.edits.workspace_edit'
)
local config = require('code_action_menu.config')

local Command = BaseAction:new({})

Expand Down Expand Up @@ -40,7 +41,12 @@ function Command:get_workspace_edit()
end

function Command:execute()
vim.lsp.buf.execute_command(self.server_data)
local source = config.source
if source == 'nvim_lsp' then
vim.lsp.buf.execute_command(self.server_data)
elseif source == 'coc' then
vim.fn.CocActionAsync('doCodeAction', self.server_data)
end
end

return Command
97 changes: 74 additions & 23 deletions lua/code_action_menu/utility_functions/actions.lua
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
local Command = require('code_action_menu.lsp_objects.actions.command')
local CodeAction = require('code_action_menu.lsp_objects.actions.code_action')
local config = require('code_action_menu.config')

local function unpack_result_and_error(server_data, client_id)
local client = vim.lsp.get_client_by_id(client_id)
local result = nil
local error = nil

if server_data == nil then
error = "Server for client '" .. client.name .. "' not yet ready!"
error = 'Server for client \'' .. client.name .. '\' not yet ready!'
elseif type(server_data) == 'table' and server_data.err ~= nil then
error = "Server of client '" .. client.name .. "' returned error: "
error = 'Server of client \'' .. client.name .. '\' returned error: '

if type(server_data.err) == 'string' then
error = error .. server_data.err
elseif type(server_data.err) == 'table' and type(server_data.err.message) == 'string' then
elseif
type(server_data.err) == 'table'
and type(server_data.err.message) == 'string'
then
error = error .. server_data.err.message
else
error = error .. 'unknown error - failed to parse response'
Expand All @@ -33,14 +37,17 @@ local function resolve_code_action(client_id, code_action_object)
local action_object, error = unpack_result_and_error(response, client_id)

if error then
vim.api.nvim_notify(error, vim.log.levels.WARN, {})
vim.notify(error, vim.log.levels.WARN)
end

return action_object
end

local function parse_object_as_action(code_action_object)
if type(code_action_object) == 'table' and type(code_action_object.command) == 'string' then
if
type(code_action_object) == 'table'
and type(code_action_object.command) == 'string'
then
return Command:new(code_action_object)
elseif
type(code_action_object) == 'table'
Expand All @@ -51,8 +58,9 @@ local function parse_object_as_action(code_action_object)
then
return CodeAction:new(code_action_object)
else
local error = 'Failed to parse unknown code action or command data structure! Skipped.'
vim.api.nvim_notify(error, vim.log.levels.WARN, {})
local error =
'Failed to parse unknown code action or command data structure! Skipped.'
vim.notify(error, vim.log.levels.WARN)
return nil
end
end
Expand All @@ -61,7 +69,9 @@ local function parse_action_data_objects(client_id, all_code_action_objects)
local all_actions = {}

for _, code_action_object in ipairs(all_code_action_objects) do
if type(code_action_object) == 'table' and code_action_object.data ~= nil then
if
type(code_action_object) == 'table' and code_action_object.data ~= nil
then
code_action_object = resolve_code_action(client_id, code_action_object)
end

Expand All @@ -86,31 +96,72 @@ local function request_actions_from_server(client_id, parameters)
local action_objects, error = unpack_result_and_error(response, client_id)

if error then
vim.api.nvim_notify(error, vim.log.levels.WARN, {})
vim.notify(error, vim.log.levels.WARN)
end

return action_objects or {}
end

local function request_actions_from_all_servers(use_range)
vim.validate({ ['request action for range'] = { use_range, 'boolean', true } })
local source_map = {
['nvim_lsp'] = function(handler)
local use_range = vim.api.nvim_get_mode().mode ~= 'n'
local line_diagnostics = vim.lsp.diagnostic.get_line_diagnostics()
local request_parameters = use_range
and vim.lsp.util.make_given_range_params()
or vim.lsp.util.make_range_params()

request_parameters.context = { diagnostics = line_diagnostics }

local all_clients = vim.lsp.buf_get_clients()
local all_actions = {}

for _, client in ipairs(all_clients) do
local action_data_objects = request_actions_from_server(
client.id,
request_parameters
)
local actions = parse_action_data_objects(client.id, action_data_objects)
vim.list_extend(all_actions, actions)
end

local line_diagnostics = vim.lsp.diagnostic.get_line_diagnostics()
local request_parameters = use_range and vim.lsp.util.make_given_range_params()
or vim.lsp.util.make_range_params()
handler(all_actions)
end,
['coc'] = function(handler, mode)
if vim.g.coc_service_initialized ~= 1 then
return vim.notify('Coc is not ready!', vim.log.levels.ERROR)
end

request_parameters.context = { diagnostics = line_diagnostics }
vim.fn.CocActionAsync(
'codeActions',
mode or vim.api.nvim_get_mode().mode,
function(err, res)
if err ~= vim.NIL then
return
end

local all_clients = vim.lsp.buf_get_clients()
local all_actions = {}
local all_actions = {}

for _, client in ipairs(all_clients) do
local action_data_objects = request_actions_from_server(client.id, request_parameters)
local actions = parse_action_data_objects(client.id, action_data_objects)
vim.list_extend(all_actions, actions)
end
for _, data in ipairs(res) do
local action

return all_actions
if type(data.edit) == 'table' or type(data.command) == 'table' then
action = CodeAction:new(data)
else
action = Command:new(data)
end

table.insert(all_actions, action)
end

handler(all_actions)
end
)
end,
}

---The first argument should be a function that handler all actions
local function request_actions_from_all_servers(...)
source_map[config.source](...)
end

local function order_actions(action_table, key_a, key_b)
Expand Down
5 changes: 5 additions & 0 deletions lua/code_action_menu/windows/base_window.lua
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
local buffer_utils = require('code_action_menu.utility_functions.buffers')
local config = require('code_action_menu.config')

local BaseWindow = {
window_number = -1,
Expand Down Expand Up @@ -89,6 +90,10 @@ function BaseWindow:open(window_configuration_options)
vim.api.nvim_win_set_config(self.window_number, window_configuration)
end

for name, value in pairs(config.window_options) do
vim.api.nvim_win_set_option(self.window_number, name, value)
end

self:after_opened()
end

Expand Down
11 changes: 11 additions & 0 deletions plugin/code_action_menu.vim
Original file line number Diff line number Diff line change
@@ -1 +1,12 @@
command! CodeActionMenu lua require('code_action_menu').open_code_action_menu()

" These behave differently only for COC users
function! s:CodeActionFromSelected(type)
call luaeval("require('code_action_menu').open_code_action_menu(_A)", a:type)
endfunction

vnoremap <Plug>(codeaction-selected-menu) <CMD>call luaeval("require('code_action_menu').open_code_action_menu(_A)", visualmode())<CR>
nnoremap <Plug>(codeaction-selected-menu) <CMD>set operatorfunc=<SID>CodeActionFromSelected<CR>g@
nnoremap <Plug>(codeaction-menu) <CMD>require('code_action_menu').open_code_action_menu('')<CR>
nnoremap <Plug>(codeaction-line-menu) <CMD>require('code_action_menu').open_code_action_menu('line')<CR>
nnoremap <Plug>(codeaction-cursor-menu) <CMD>require('code_action_menu').open_code_action_menu('cursor')CR>