aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--runtime/doc/lua.txt24
-rw-r--r--runtime/doc/news.txt9
-rw-r--r--runtime/doc/various.txt9
-rw-r--r--runtime/lua/vim/lsp/handlers.lua16
-rw-r--r--runtime/lua/vim/ui.lua50
-rw-r--r--runtime/plugin/nvim.lua24
-rw-r--r--test/functional/lua/ui_spec.lua23
7 files changed, 139 insertions, 16 deletions
diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt
index fb6cbca6e3..77a89a123d 100644
--- a/runtime/doc/lua.txt
+++ b/runtime/doc/lua.txt
@@ -2343,6 +2343,30 @@ input({opts}, {on_confirm}) *vim.ui.input()*
typed (it might be an empty string if nothing was
entered), or `nil` if the user aborted the dialog.
+open({path}) *vim.ui.open()*
+ Opens `path` with the system default handler (macOS `open`, Windows
+ `explorer.exe`, Linux `xdg-open`, …), or returns (but does not show) an
+ error message on failure.
+
+ Expands "~/" and environment variables in filesystem paths.
+
+ Examples: >lua
+
+ vim.ui.open("https://neovim.io/")
+ vim.ui.open("~/path/to/file")
+ vim.ui.open("$VIMRUNTIME")
+<
+
+ Parameters: ~
+ • {path} (string) Path or URL to open
+
+ Return: ~
+ SystemCompleted|nil # Command result, or nil if not found.
+ (string|nil) # Error message on failure
+
+ See also: ~
+ • |vim.system()|
+
select({items}, {opts}, {on_choice}) *vim.ui.select()*
Prompts the user to pick from a list of items, allowing arbitrary
(potentially asynchronous) work until `on_choice`.
diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt
index 61ae92296f..24e9dc917b 100644
--- a/runtime/doc/news.txt
+++ b/runtime/doc/news.txt
@@ -105,7 +105,10 @@ The following new APIs and features were added.
https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_inlayHint
• Bundled treesitter parser and queries (highlight, folds) for Markdown,
-Python, and Bash.
+ Python, and Bash.
+
+• |vim.ui.open()| opens URIs using the system default handler (macOS `open`,
+ Windows `explorer`, Linux `xdg-open`, etc.)
==============================================================================
CHANGED FEATURES *news-changed*
@@ -143,6 +146,10 @@ The following changes to existing APIs or features add new behavior.
• |:Man| now respects 'wrapmargin'
+• |gx| now uses |vim.ui.open()| and not netrw. To customize, you can redefine
+ `vim.ui.open` or remap `gx`. To continue using netrw (deprecated): >vim
+ :call netrw#BrowseX(expand(exists("g:netrw_gx")? g:netrw_gx : '<cfile>'), netrw#CheckIfRemote())<CR>
+
==============================================================================
REMOVED FEATURES *news-removed*
diff --git a/runtime/doc/various.txt b/runtime/doc/various.txt
index 1b1dca321b..956c37fc0f 100644
--- a/runtime/doc/various.txt
+++ b/runtime/doc/various.txt
@@ -97,6 +97,15 @@ g8 Print the hex values of the bytes used in the
cursor is halfway through a multibyte character the
command won't move the cursor.
+ *gx*
+gx Opens the current filepath or URL (decided by
+ |<cfile>|, 'isfname') at cursor using the system
+ default handler, by calling |vim.ui.open()|.
+
+ *v_gx*
+{Visual}gx Opens the selected text using the system default
+ handler, by calling |vim.ui.open()|.
+
*:p* *:pr* *:print* *E749*
:[range]p[rint] [flags]
Print [range] lines (default current line).
diff --git a/runtime/lua/vim/lsp/handlers.lua b/runtime/lua/vim/lsp/handlers.lua
index 625a2ed282..70781cb7a6 100644
--- a/runtime/lua/vim/lsp/handlers.lua
+++ b/runtime/lua/vim/lsp/handlers.lua
@@ -573,22 +573,14 @@ M['window/showDocument'] = function(_, result, ctx, _)
if result.external then
-- TODO(lvimuser): ask the user for confirmation
- local cmd
- if vim.fn.has('win32') == 1 then
- cmd = { 'cmd.exe', '/c', 'start', '""', uri }
- elseif vim.fn.has('macunix') == 1 then
- cmd = { 'open', uri }
- else
- cmd = { 'xdg-open', uri }
- end
+ local ret, err = vim.ui.open(uri)
- local ret = vim.fn.system(cmd)
- if vim.v.shell_error ~= 0 then
+ if ret == nil or ret.code ~= 0 then
return {
success = false,
error = {
code = protocol.ErrorCodes.UnknownErrorCode,
- message = ret,
+ message = ret and ret.stderr or err,
},
}
end
@@ -600,7 +592,7 @@ M['window/showDocument'] = function(_, result, ctx, _)
local client = vim.lsp.get_client_by_id(client_id)
local client_name = client and client.name or string.format('id=%d', client_id)
if not client then
- err_message({ 'LSP[', client_name, '] client has shut down after sending ', ctx.method })
+ err_message('LSP[', client_name, '] client has shut down after sending ', ctx.method)
return vim.NIL
end
diff --git a/runtime/lua/vim/ui.lua b/runtime/lua/vim/ui.lua
index aaee175f3a..fd06611da2 100644
--- a/runtime/lua/vim/ui.lua
+++ b/runtime/lua/vim/ui.lua
@@ -104,4 +104,54 @@ function M.input(opts, on_confirm)
end
end
+--- Opens `path` with the system default handler (macOS `open`, Windows `explorer.exe`, Linux
+--- `xdg-open`, …), or returns (but does not show) an error message on failure.
+---
+--- Expands "~/" and environment variables in filesystem paths.
+---
+--- Examples:
+--- <pre>lua
+--- vim.ui.open("https://neovim.io/")
+--- vim.ui.open("~/path/to/file")
+--- vim.ui.open("$VIMRUNTIME")
+--- </pre>
+---
+---@param path string Path or URL to open
+---
+---@return SystemCompleted|nil # Command result, or nil if not found.
+---@return string|nil # Error message on failure
+---
+---@see |vim.system()|
+function M.open(path)
+ vim.validate({
+ path = { path, 'string' },
+ })
+ local is_uri = path:match('%w+:')
+ if not is_uri then
+ path = vim.fn.expand(path)
+ end
+
+ local cmd
+
+ if vim.fn.has('mac') == 1 then
+ cmd = { 'open', path }
+ elseif vim.fn.has('win32') == 1 then
+ cmd = { 'explorer', path }
+ elseif vim.fn.executable('wslview') == 1 then
+ cmd = { 'wslview', path }
+ elseif vim.fn.executable('xdg-open') == 1 then
+ cmd = { 'xdg-open', path }
+ else
+ return nil, 'vim.ui.open: no handler found (tried: wslview, xdg-open)'
+ end
+
+ local rv = vim.system(cmd, { text = true, detach = true }):wait()
+ if rv.code ~= 0 then
+ local msg = ('vim.ui.open: command failed (%d): %s'):format(rv.code, vim.inspect(cmd))
+ return rv, msg
+ end
+
+ return rv, nil
+end
+
return M
diff --git a/runtime/plugin/nvim.lua b/runtime/plugin/nvim.lua
index 0a33826b82..33d399e577 100644
--- a/runtime/plugin/nvim.lua
+++ b/runtime/plugin/nvim.lua
@@ -18,3 +18,27 @@ vim.api.nvim_create_user_command('InspectTree', function(cmd)
vim.treesitter.inspect_tree()
end
end, { desc = 'Inspect treesitter language tree for buffer', count = true })
+
+-- TODO: use vim.region() when it lands... #13896 #16843
+local function get_visual_selection()
+ local save_a = vim.fn.getreginfo('a')
+ vim.cmd([[norm! "ay]])
+ local selection = vim.fn.getreg('a', 1)
+ vim.fn.setreg('a', save_a)
+ return selection
+end
+
+local gx_desc =
+ 'Opens filepath or URI under cursor with the system handler (file explorer, web browser, …)'
+local function do_open(uri)
+ local _, err = vim.ui.open(uri)
+ if err then
+ vim.notify(err, vim.log.levels.ERROR)
+ end
+end
+vim.keymap.set({ 'n' }, 'gx', function()
+ do_open(vim.fn.expand('<cfile>'))
+end, { desc = gx_desc })
+vim.keymap.set({ 'x' }, 'gx', function()
+ do_open(get_visual_selection())
+end, { desc = gx_desc })
diff --git a/test/functional/lua/ui_spec.lua b/test/functional/lua/ui_spec.lua
index 9ee99b4905..d1b64419af 100644
--- a/test/functional/lua/ui_spec.lua
+++ b/test/functional/lua/ui_spec.lua
@@ -1,9 +1,11 @@
local helpers = require('test.functional.helpers')(after_each)
local eq = helpers.eq
+local matches = helpers.matches
local exec_lua = helpers.exec_lua
local clear = helpers.clear
local feed = helpers.feed
local eval = helpers.eval
+local is_os = helpers.is_os
local poke_eventloop = helpers.poke_eventloop
describe('vim.ui', function()
@@ -11,8 +13,7 @@ describe('vim.ui', function()
clear()
end)
-
- describe('select', function()
+ describe('select()', function()
it('can select an item', function()
local result = exec_lua[[
local items = {
@@ -47,7 +48,7 @@ describe('vim.ui', function()
end)
end)
- describe('input', function()
+ describe('input()', function()
it('can input text', function()
local result = exec_lua[[
local opts = {
@@ -130,4 +131,20 @@ describe('vim.ui', function()
end)
end)
+
+ describe('open()', function()
+ it('validation', function()
+ if not is_os('bsd') then
+ matches('vim.ui.open: command failed %(%d%): { "[^"]+", "non%-existent%-file" }',
+ exec_lua[[local _, err = vim.ui.open('non-existent-file') ; return err]])
+ end
+
+ exec_lua[[
+ vim.fn.has = function() return 0 end
+ vim.fn.executable = function() return 0 end
+ ]]
+ eq('vim.ui.open: no handler found (tried: wslview, xdg-open)',
+ exec_lua[[local _, err = vim.ui.open('foo') ; return err]])
+ end)
+ end)
end)