aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMathias Fußenegger <mfussenegger@users.noreply.github.com>2021-09-27 21:57:28 +0200
committerGitHub <noreply@github.com>2021-09-27 21:57:28 +0200
commit63fde086d9e7936b21c51e145253ec69820b2112 (patch)
treefe42e09315181b66e32b5787508f6bd81e719c67
parent6736ee8be5ae9a3b004bd9033d7efffad74a03be (diff)
downloadrneovim-63fde086d9e7936b21c51e145253ec69820b2112.tar.gz
rneovim-63fde086d9e7936b21c51e145253ec69820b2112.tar.bz2
rneovim-63fde086d9e7936b21c51e145253ec69820b2112.zip
feat(ui): add vim.ui.select and use in code actions (#15771)
Continuation of https://github.com/neovim/neovim/pull/15202 A plugin like telescope could override it with a fancy implementation and then users would get the telescope-ui within each plugin that utilizes the vim.ui.select function. There are some plugins which override the `textDocument/codeAction` handler solely to provide a different UI. With custom client commands and soon codeAction resolve support, it becomes more difficult to implement the handler right - so having a dedicated way to override the picking function will be useful.
-rw-r--r--runtime/doc/lua.txt21
-rw-r--r--runtime/lua/vim/lsp/handlers.lua70
-rw-r--r--runtime/lua/vim/ui.lua36
-rwxr-xr-xscripts/gen_vimdoc.py3
-rw-r--r--src/nvim/lua/vim.lua3
-rw-r--r--test/functional/lua/ui_spec.lua46
6 files changed, 145 insertions, 34 deletions
diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt
index 53d68fa5e6..22e323baa7 100644
--- a/runtime/doc/lua.txt
+++ b/runtime/doc/lua.txt
@@ -1645,4 +1645,25 @@ uri_to_fname({uri}) *vim.uri_to_fname()*
Return: ~
Filename
+
+==============================================================================
+Lua module: ui *lua-ui*
+
+select({items}, {opts}, {on_choice}) *vim.ui.select()*
+ Prompts the user to pick a single item from a collection of
+ entries
+
+ Parameters: ~
+ {items} table Arbitrary items
+ {opts} table Additional options
+ • prompt (string|nil) Text of the prompt.
+ Defaults to `Select one of:`
+ • format_item (function item -> text)
+ Function to format an individual item from
+ `items` . Defaults to `tostring` .
+ {on_choice} function ((item|nil, idx|nil) -> ()) Called
+ once the user made a choice. `idx` is the
+ 1-based index of `item` within `item` . `nil`
+ if the user aborted the dialog.
+
vim:tw=78:ts=8:ft=help:norl:
diff --git a/runtime/lua/vim/lsp/handlers.lua b/runtime/lua/vim/lsp/handlers.lua
index 624f8b5462..538bd2605b 100644
--- a/runtime/lua/vim/lsp/handlers.lua
+++ b/runtime/lua/vim/lsp/handlers.lua
@@ -116,42 +116,44 @@ M['textDocument/codeAction'] = function(_, result, ctx)
return
end
- local option_strings = {"Code actions:"}
- for i, action in ipairs(result) do
- local title = action.title:gsub('\r\n', '\\r\\n')
- title = title:gsub('\n', '\\n')
- table.insert(option_strings, string.format("%d. %s", i, title))
- end
-
- local choice = vim.fn.inputlist(option_strings)
- if choice < 1 or choice > #result then
- return
- end
- local action = result[choice]
- -- textDocument/codeAction can return either Command[] or CodeAction[]
- --
- -- CodeAction
- -- ...
- -- edit?: WorkspaceEdit -- <- must be applied before command
- -- command?: Command
- --
- -- Command:
- -- title: string
- -- command: string
- -- arguments?: any[]
- --
- if action.edit then
- util.apply_workspace_edit(action.edit)
- end
- if action.command then
- local command = type(action.command) == 'table' and action.command or action
- local fn = vim.lsp.commands[command.command]
- if fn then
- fn(command, ctx)
- else
- buf.execute_command(command)
+ ---@private
+ local function on_user_choice(action)
+ if not action then
+ return
+ end
+ -- textDocument/codeAction can return either Command[] or CodeAction[]
+ --
+ -- CodeAction
+ -- ...
+ -- edit?: WorkspaceEdit -- <- must be applied before command
+ -- command?: Command
+ --
+ -- Command:
+ -- title: string
+ -- command: string
+ -- arguments?: any[]
+ --
+ if action.edit then
+ util.apply_workspace_edit(action.edit)
+ end
+ if action.command then
+ local command = type(action.command) == 'table' and action.command or action
+ local fn = vim.lsp.commands[command.command]
+ if fn then
+ fn(command, ctx)
+ else
+ buf.execute_command(command)
+ end
end
end
+
+ vim.ui.select(result, {
+ prompt = 'Code actions:',
+ format_entry = function(action)
+ local title = action.title:gsub('\r\n', '\\r\\n')
+ return title:gsub('\n', '\\n')
+ end,
+ }, on_user_choice)
end
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_applyEdit
diff --git a/runtime/lua/vim/ui.lua b/runtime/lua/vim/ui.lua
new file mode 100644
index 0000000000..aebb63d56f
--- /dev/null
+++ b/runtime/lua/vim/ui.lua
@@ -0,0 +1,36 @@
+local M = {}
+
+--- Prompts the user to pick a single item from a collection of entries
+---
+---@param items table Arbitrary items
+---@param opts table Additional options
+--- - prompt (string|nil)
+--- Text of the prompt. Defaults to `Select one of:`
+--- - format_item (function item -> text)
+--- Function to format an
+--- individual item from `items`. Defaults to `tostring`.
+---@param on_choice function ((item|nil, idx|nil) -> ())
+--- Called once the user made a choice.
+--- `idx` is the 1-based index of `item` within `item`.
+--- `nil` if the user aborted the dialog.
+function M.select(items, opts, on_choice)
+ vim.validate {
+ items = { items, 'table', false },
+ on_choice = { on_choice, 'function', false },
+ }
+ opts = opts or {}
+ local choices = {opts.prompt or 'Select one of:'}
+ local format_entry = opts.format_entry or tostring
+ for i, item in pairs(items) do
+ table.insert(choices, string.format('%d: %s', i, format_entry(item)))
+ end
+ local choice = vim.fn.inputlist(choices)
+ if choice < 1 or choice > #items then
+ on_choice(nil, nil)
+ else
+ on_choice(items[choice], choice)
+ end
+end
+
+
+return M
diff --git a/scripts/gen_vimdoc.py b/scripts/gen_vimdoc.py
index 64ed8d61f6..36e01153f1 100755
--- a/scripts/gen_vimdoc.py
+++ b/scripts/gen_vimdoc.py
@@ -123,11 +123,13 @@ CONFIG = {
'vim.lua',
'shared.lua',
'uri.lua',
+ 'ui.lua',
],
'files': ' '.join([
os.path.join(base_dir, 'src/nvim/lua/vim.lua'),
os.path.join(base_dir, 'runtime/lua/vim/shared.lua'),
os.path.join(base_dir, 'runtime/lua/vim/uri.lua'),
+ os.path.join(base_dir, 'runtime/lua/vim/ui.lua'),
]),
'file_patterns': '*.lua',
'fn_name_prefix': '',
@@ -141,6 +143,7 @@ CONFIG = {
# `shared` functions are exposed on the `vim` module.
'shared': 'vim',
'uri': 'vim',
+ 'ui': 'vim.ui',
},
'append_only': [
'shared.lua',
diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua
index ba124c41ad..7a209f2d79 100644
--- a/src/nvim/lua/vim.lua
+++ b/src/nvim/lua/vim.lua
@@ -108,6 +108,9 @@ setmetatable(vim, {
elseif key == 'diagnostic' then
t.diagnostic = require('vim.diagnostic')
return t.diagnostic
+ elseif key == 'ui' then
+ t.ui = require('vim.ui')
+ return t.ui
end
end
})
diff --git a/test/functional/lua/ui_spec.lua b/test/functional/lua/ui_spec.lua
new file mode 100644
index 0000000000..25a208b70b
--- /dev/null
+++ b/test/functional/lua/ui_spec.lua
@@ -0,0 +1,46 @@
+local helpers = require('test.functional.helpers')(after_each)
+local eq = helpers.eq
+local exec_lua = helpers.exec_lua
+local clear = helpers.clear
+
+describe('vim.ui', function()
+ before_each(function()
+ clear()
+ end)
+
+
+ describe('select', function()
+ it('can select an item', function()
+ local result = exec_lua[[
+ local items = {
+ { name = 'Item 1' },
+ { name = 'Item 2' },
+ }
+ local opts = {
+ format_entry = function(entry)
+ return entry.name
+ end
+ }
+ local selected
+ local cb = function(item)
+ selected = item
+ end
+ -- inputlist would require input and block the test;
+ local choices
+ vim.fn.inputlist = function(x)
+ choices = x
+ return 1
+ end
+ vim.ui.select(items, opts, cb)
+ vim.wait(100, function() return selected ~= nil end)
+ return {selected, choices}
+ ]]
+ eq({ name = 'Item 1' }, result[1])
+ eq({
+ 'Select one of:',
+ '1: Item 1',
+ '2: Item 2',
+ }, result[2])
+ end)
+ end)
+end)