mirror of
https://github.com/neovim/neovim.git
synced 2024-12-19 18:55:14 -07:00
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.
This commit is contained in:
parent
6736ee8be5
commit
63fde086d9
@ -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:
|
||||
|
@ -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
|
||||
|
36
runtime/lua/vim/ui.lua
Normal file
36
runtime/lua/vim/ui.lua
Normal file
@ -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
|
@ -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',
|
||||
|
@ -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
|
||||
})
|
||||
|
46
test/functional/lua/ui_spec.lua
Normal file
46
test/functional/lua/ui_spec.lua
Normal file
@ -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)
|
Loading…
Reference in New Issue
Block a user