mirror of
				https://github.com/neovim/neovim.git
				synced 2025-11-04 01:34:25 +00:00 
			
		
		
		
	Merge #23401 vim.ui.open: "gx" without netrw
This commit is contained in:
		@@ -2343,6 +2343,30 @@ input({opts}, {on_confirm})                                   *vim.ui.input()*
 | 
				
			|||||||
                      typed (it might be an empty string if nothing was
 | 
					                      typed (it might be an empty string if nothing was
 | 
				
			||||||
                      entered), or `nil` if the user aborted the dialog.
 | 
					                      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()*
 | 
					select({items}, {opts}, {on_choice})                         *vim.ui.select()*
 | 
				
			||||||
    Prompts the user to pick from a list of items, allowing arbitrary
 | 
					    Prompts the user to pick from a list of items, allowing arbitrary
 | 
				
			||||||
    (potentially asynchronous) work until `on_choice`.
 | 
					    (potentially asynchronous) work until `on_choice`.
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -107,6 +107,9 @@ The following new APIs and features were added.
 | 
				
			|||||||
• Bundled treesitter parser and queries (highlight, folds) for Markdown,
 | 
					• 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*
 | 
					CHANGED FEATURES                                                 *news-changed*
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -143,6 +146,10 @@ The following changes to existing APIs or features add new behavior.
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
• |:Man| now respects 'wrapmargin'
 | 
					• |: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*
 | 
					REMOVED FEATURES                                                 *news-removed*
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -97,6 +97,15 @@ g8			Print the hex values of the bytes used in the
 | 
				
			|||||||
			cursor is halfway through a multibyte character the
 | 
								cursor is halfway through a multibyte character the
 | 
				
			||||||
			command won't move the cursor.
 | 
								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*
 | 
											*:p* *:pr* *:print* *E749*
 | 
				
			||||||
:[range]p[rint] [flags]
 | 
					:[range]p[rint] [flags]
 | 
				
			||||||
			Print [range] lines (default current line).
 | 
								Print [range] lines (default current line).
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -573,22 +573,14 @@ M['window/showDocument'] = function(_, result, ctx, _)
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  if result.external then
 | 
					  if result.external then
 | 
				
			||||||
    -- TODO(lvimuser): ask the user for confirmation
 | 
					    -- TODO(lvimuser): ask the user for confirmation
 | 
				
			||||||
    local cmd
 | 
					    local ret, err = vim.ui.open(uri)
 | 
				
			||||||
    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 = vim.fn.system(cmd)
 | 
					    if ret == nil or ret.code ~= 0 then
 | 
				
			||||||
    if vim.v.shell_error ~= 0 then
 | 
					 | 
				
			||||||
      return {
 | 
					      return {
 | 
				
			||||||
        success = false,
 | 
					        success = false,
 | 
				
			||||||
        error = {
 | 
					        error = {
 | 
				
			||||||
          code = protocol.ErrorCodes.UnknownErrorCode,
 | 
					          code = protocol.ErrorCodes.UnknownErrorCode,
 | 
				
			||||||
          message = ret,
 | 
					          message = ret and ret.stderr or err,
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
@@ -600,7 +592,7 @@ M['window/showDocument'] = function(_, result, ctx, _)
 | 
				
			|||||||
  local client = vim.lsp.get_client_by_id(client_id)
 | 
					  local client = vim.lsp.get_client_by_id(client_id)
 | 
				
			||||||
  local client_name = client and client.name or string.format('id=%d', client_id)
 | 
					  local client_name = client and client.name or string.format('id=%d', client_id)
 | 
				
			||||||
  if not client then
 | 
					  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
 | 
					    return vim.NIL
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -104,4 +104,54 @@ function M.input(opts, on_confirm)
 | 
				
			|||||||
  end
 | 
					  end
 | 
				
			||||||
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
 | 
					return M
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -18,3 +18,27 @@ vim.api.nvim_create_user_command('InspectTree', function(cmd)
 | 
				
			|||||||
    vim.treesitter.inspect_tree()
 | 
					    vim.treesitter.inspect_tree()
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
end, { desc = 'Inspect treesitter language tree for buffer', count = true })
 | 
					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 })
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,9 +1,11 @@
 | 
				
			|||||||
local helpers = require('test.functional.helpers')(after_each)
 | 
					local helpers = require('test.functional.helpers')(after_each)
 | 
				
			||||||
local eq = helpers.eq
 | 
					local eq = helpers.eq
 | 
				
			||||||
 | 
					local matches = helpers.matches
 | 
				
			||||||
local exec_lua = helpers.exec_lua
 | 
					local exec_lua = helpers.exec_lua
 | 
				
			||||||
local clear = helpers.clear
 | 
					local clear = helpers.clear
 | 
				
			||||||
local feed = helpers.feed
 | 
					local feed = helpers.feed
 | 
				
			||||||
local eval = helpers.eval
 | 
					local eval = helpers.eval
 | 
				
			||||||
 | 
					local is_os = helpers.is_os
 | 
				
			||||||
local poke_eventloop = helpers.poke_eventloop
 | 
					local poke_eventloop = helpers.poke_eventloop
 | 
				
			||||||
 | 
					
 | 
				
			||||||
describe('vim.ui', function()
 | 
					describe('vim.ui', function()
 | 
				
			||||||
@@ -11,8 +13,7 @@ describe('vim.ui', function()
 | 
				
			|||||||
    clear()
 | 
					    clear()
 | 
				
			||||||
  end)
 | 
					  end)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  describe('select()', function()
 | 
				
			||||||
  describe('select', function()
 | 
					 | 
				
			||||||
    it('can select an item', function()
 | 
					    it('can select an item', function()
 | 
				
			||||||
      local result = exec_lua[[
 | 
					      local result = exec_lua[[
 | 
				
			||||||
        local items = {
 | 
					        local items = {
 | 
				
			||||||
@@ -47,7 +48,7 @@ describe('vim.ui', function()
 | 
				
			|||||||
    end)
 | 
					    end)
 | 
				
			||||||
  end)
 | 
					  end)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  describe('input', function()
 | 
					  describe('input()', function()
 | 
				
			||||||
    it('can input text', function()
 | 
					    it('can input text', function()
 | 
				
			||||||
      local result = exec_lua[[
 | 
					      local result = exec_lua[[
 | 
				
			||||||
        local opts = {
 | 
					        local opts = {
 | 
				
			||||||
@@ -130,4 +131,20 @@ describe('vim.ui', function()
 | 
				
			|||||||
    end)
 | 
					    end)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  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)
 | 
					end)
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user