aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--runtime/doc/lua.txt21
-rw-r--r--runtime/lua/vim/lsp/codelens.lua16
-rw-r--r--runtime/lua/vim/lsp/handlers.lua70
-rw-r--r--runtime/lua/vim/lsp/rpc.lua39
-rw-r--r--runtime/lua/vim/ui.lua36
-rwxr-xr-xscripts/finddeclarations.pl50
-rwxr-xr-xscripts/gen_vimdoc.py3
-rw-r--r--src/nvim/lua/vim.lua3
-rw-r--r--test/functional/lua/ui_spec.lua46
-rw-r--r--test/functional/plugin/lsp/codelens_spec.lua28
10 files changed, 194 insertions, 118 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/codelens.lua b/runtime/lua/vim/lsp/codelens.lua
index 9cedb2f1db..20b203fe99 100644
--- a/runtime/lua/vim/lsp/codelens.lua
+++ b/runtime/lua/vim/lsp/codelens.lua
@@ -31,10 +31,24 @@ local function execute_lens(lens, bufnr, client_id)
local line = lens.range.start.line
api.nvim_buf_clear_namespace(bufnr, namespaces[client_id], line, line + 1)
+ local command = lens.command
+ local fn = vim.lsp.commands[command.command]
+ if fn then
+ fn(command, { bufnr = bufnr, client_id = client_id })
+ return
+ end
-- Need to use the client that returned the lens → must not use buf_request
local client = vim.lsp.get_client_by_id(client_id)
assert(client, 'Client is required to execute lens, client_id=' .. client_id)
- client.request('workspace/executeCommand', lens.command, function(...)
+ local command_provider = client.server_capabilities.executeCommandProvider
+ local commands = type(command_provider) == 'table' and command_provider.commands or {}
+ if not vim.tbl_contains(commands, command.command) then
+ vim.notify(string.format(
+ "Language server does not support command `%s`. This command may require a client extension.", command.command),
+ vim.log.levels.WARN)
+ return
+ end
+ client.request('workspace/executeCommand', command, function(...)
local result = vim.lsp.handlers['workspace/executeCommand'](...)
M.refresh()
return result
diff --git a/runtime/lua/vim/lsp/handlers.lua b/runtime/lua/vim/lsp/handlers.lua
index 624f8b5462..def83a7320 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_item = 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/lsp/rpc.lua b/runtime/lua/vim/lsp/rpc.lua
index 716f42faf9..255eb65dfe 100644
--- a/runtime/lua/vim/lsp/rpc.lua
+++ b/runtime/lua/vim/lsp/rpc.lua
@@ -4,34 +4,6 @@ local log = require('vim.lsp.log')
local protocol = require('vim.lsp.protocol')
local validate, schedule, schedule_wrap = vim.validate, vim.schedule, vim.schedule_wrap
--- TODO replace with a better implementation.
----@private
---- Encodes to JSON.
----
----@param data (table) Data to encode
----@returns (string) Encoded object
-local function json_encode(data)
- local status, result = pcall(vim.json.encode, data)
- if status then
- return result
- else
- return nil, result
- end
-end
----@private
---- Decodes from JSON.
----
----@param data (string) Data to decode
----@returns (table) Decoded JSON object
-local function json_decode(data)
- local status, result = pcall(vim.json.decode, data)
- if status then
- return result
- else
- return nil, result
- end
-end
-
---@private
--- Checks whether a given path exists and is a directory.
---@param filename (string) path to check
@@ -389,12 +361,12 @@ local function start(cmd, cmd_args, dispatchers, extra_spawn_params)
--- Encodes {payload} into a JSON-RPC message and sends it to the remote
--- process.
---
- ---@param payload (table) Converted into a JSON string, see |json_encode()|
+ ---@param payload table
---@returns true if the payload could be scheduled, false if the main event-loop is in the process of closing.
local function encode_and_send(payload)
local _ = log.debug() and log.debug("rpc.send", payload)
if handle == nil or handle:is_closing() then return false end
- local encoded = assert(json_encode(payload))
+ local encoded = vim.json.encode(payload)
stdin:write(format_message_with_content_length(encoded))
return true
end
@@ -485,14 +457,15 @@ local function start(cmd, cmd_args, dispatchers, extra_spawn_params)
---@private
local function handle_body(body)
- local decoded, err = json_decode(body)
- if not decoded then
- -- on_error(client_errors.INVALID_SERVER_JSON, err)
+ local ok, decoded = pcall(vim.json.decode, body)
+ if not ok then
+ on_error(client_errors.INVALID_SERVER_JSON, decoded)
return
end
local _ = log.debug() and log.debug("rpc.receive", decoded)
if type(decoded.method) == 'string' and decoded.id then
+ local err
-- Server Request
decoded.params = convert_NIL(decoded.params)
-- Schedule here so that the users functions don't trigger an error and
diff --git a/runtime/lua/vim/ui.lua b/runtime/lua/vim/ui.lua
new file mode 100644
index 0000000000..5eab20fc54
--- /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_item = opts.format_item or tostring
+ for i, item in pairs(items) do
+ table.insert(choices, string.format('%d: %s', i, format_item(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/finddeclarations.pl b/scripts/finddeclarations.pl
deleted file mode 100755
index 1b1a57b9b7..0000000000
--- a/scripts/finddeclarations.pl
+++ /dev/null
@@ -1,50 +0,0 @@
-#!/usr/bin/perl
-
-use strict;
-use warnings;
-
-if ($ARGV[0] eq '--help') {
- print << "EOF";
-Usage:
-
- $0 definitions.c
-EOF
- exit;
-}
-
-my ($cfname, $sfname, $gfname, $cpp) = @ARGV;
-
-my $F;
-
-open $F, "<", $cfname;
-
-my $text = join "", <$F>;
-
-close $F;
-
-my $s = qr/(?>\s*)/aso;
-my $w = qr/(?>\w+)/aso;
-my $argname = qr/$w(?:\[(?>\w+)\])?/aso;
-my $type_regex = qr/(?:$w$s\**$s)+/aso;
-my $arg_regex = qr/(?:$type_regex$s$argname)/aso;
-
-while ($text =~ /
- (?<=\n) # Definition starts at the start of line
- $type_regex # Return type
- $s$w # Function name
- $s\($s
- (?:
- $arg_regex(?:$s,$s$arg_regex)*+
- ($s,$s\.\.\.)? # varargs function
- |void
- )?
- $s\)
- (?:$s FUNC_ATTR_$w(?:\((?>[^)]*)\))?)*+ # Optional attributes
- (?=$s;) # Ending semicolon
- /axsogp) {
- my $match = "${^MATCH}";
- my $s = "${^PREMATCH}";
- $s =~ s/[^\n]++//g;
- my $line = 1 + length $s;
- print "${cfname}:${line}: $match\n";
-}
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..94f1b5840b
--- /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_item = 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)
diff --git a/test/functional/plugin/lsp/codelens_spec.lua b/test/functional/plugin/lsp/codelens_spec.lua
index e48a0ad260..c8b75e65fc 100644
--- a/test/functional/plugin/lsp/codelens_spec.lua
+++ b/test/functional/plugin/lsp/codelens_spec.lua
@@ -58,5 +58,33 @@ describe('vim.lsp.codelens', function()
]], bufnr)
eq({[1] = {'Lens1', 'LspCodeLens'}}, virtual_text_chunks)
+
+ end)
+ it('codelens uses client commands', function()
+ local fake_uri = "file:///fake/uri"
+ local cmd = exec_lua([[
+ fake_uri = ...
+ local bufnr = vim.uri_to_bufnr(fake_uri)
+ vim.fn.bufload(bufnr)
+ vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, {'One line'})
+ local lenses = {
+ {
+ range = {
+ start = { line = 0, character = 0, },
+ ['end'] = { line = 0, character = 8 }
+ },
+ command = { title = 'Lens1', command = 'Dummy' }
+ },
+ }
+ vim.lsp.codelens.on_codelens(nil, lenses, {method='textDocument/codeLens', client_id=1, bufnr=bufnr})
+ local cmd_called = nil
+ vim.lsp.commands['Dummy'] = function(command)
+ cmd_called = command
+ end
+ vim.api.nvim_set_current_buf(bufnr)
+ vim.lsp.codelens.run()
+ return cmd_called
+ ]], fake_uri)
+ eq({ command = 'Dummy', title = 'Lens1' }, cmd)
end)
end)