mirror of
https://github.com/neovim/neovim
synced 2025-07-16 01:01:49 +00:00
feat(net): vim.net.request(), :edit [url] #34140
Problem: Nvim depends on netrw to download/request URL contents. Solution: - Add `vim.net.request()` as a thin curl wrapper: - Basic GET with --silent, --show-error, --fail, --location, --retry - Optional `opts.outpath` to save to a file - Operates asynchronously. Pass an `on_response` handler to get the result. - Add integ tests (requires NVIM_TEST_INTEG to be set) to test success and 404 failure. - Health check for missing `curl`. - Handle `:edit https://…` using `vim.net.request()`. API Usage: 1. Asynchronous request: vim.net.request('https://httpbingo.org/get', { retry = 2 }, function(err, response) if err then print('Fetch failed:', err) else print('Got body of length:', #response.body) end end) 2. Download to file: vim.net.request('https://httpbingo.org/get', { outpath = 'out_async.txt' }, function(err) if err then print('Error:', err) end end) 3. Remote :edit integration (in runtime/plugin/net.lua) fetches into buffer: :edit https://httpbingo.org/get
This commit is contained in:
@ -41,6 +41,7 @@ for k, v in pairs({
|
||||
snippet = true,
|
||||
pack = true,
|
||||
_watch = true,
|
||||
net = true,
|
||||
}) do
|
||||
vim._submodules[k] = v
|
||||
end
|
||||
|
@ -428,6 +428,72 @@ local function check_external_tools()
|
||||
else
|
||||
health.warn('git not available (required by `vim.pack`)')
|
||||
end
|
||||
|
||||
if vim.fn.executable('curl') == 1 then
|
||||
local curl_path = vim.fn.exepath('curl')
|
||||
local curl_job = vim.system({ curl_path, '--version' }):wait()
|
||||
|
||||
if curl_job.code == 0 then
|
||||
local curl_out = curl_job.stdout
|
||||
if not curl_out or curl_out == '' then
|
||||
health.warn(
|
||||
string.format('`%s --version` produced no output', curl_path),
|
||||
{ curl_job.stderr }
|
||||
)
|
||||
return
|
||||
end
|
||||
local curl_version = vim.version.parse(curl_out)
|
||||
if not curl_version then
|
||||
health.warn('Unable to parse curl version from `curl --version`')
|
||||
return
|
||||
end
|
||||
if vim.version.le(curl_version, { 7, 12, 3 }) then
|
||||
health.warn('curl version %s not compatible', curl_version)
|
||||
return
|
||||
end
|
||||
local lines = { string.format('curl %s (%s)', curl_version, curl_path) }
|
||||
|
||||
for line in vim.gsplit(curl_out, '\n', { plain = true }) do
|
||||
if line ~= '' and not line:match('^curl') then
|
||||
table.insert(lines, line)
|
||||
end
|
||||
end
|
||||
|
||||
-- Add subtitle only if any env var is present
|
||||
local added_env_header = false
|
||||
for _, var in ipairs({
|
||||
'curl_ca_bundle',
|
||||
'curl_home',
|
||||
'curl_ssl_backend',
|
||||
'ssl_cert_dir',
|
||||
'ssl_cert_file',
|
||||
'https_proxy',
|
||||
'http_proxy',
|
||||
'all_proxy',
|
||||
'no_proxy',
|
||||
}) do
|
||||
---@type string?
|
||||
local val = vim.env[var] or vim.env[var:upper()]
|
||||
if val then
|
||||
if not added_env_header then
|
||||
table.insert(lines, 'curl-related environment variables:')
|
||||
added_env_header = true
|
||||
end
|
||||
local shown_var = vim.env[var] and var or var:upper()
|
||||
table.insert(lines, string.format(' %s=%s', shown_var, val))
|
||||
end
|
||||
end
|
||||
|
||||
health.ok(table.concat(lines, '\n'))
|
||||
else
|
||||
health.warn('curl is installed but failed to run `curl --version`', { curl_job.stderr })
|
||||
end
|
||||
else
|
||||
health.error('curl not found', {
|
||||
'Required for vim.net.request() to function.',
|
||||
'Install curl using your package manager.',
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
function M.check()
|
||||
|
63
runtime/lua/vim/net.lua
Normal file
63
runtime/lua/vim/net.lua
Normal file
@ -0,0 +1,63 @@
|
||||
local M = {}
|
||||
|
||||
--- Makes an HTTP GET request to the given URL (asynchronous).
|
||||
---
|
||||
--- This function operates in one mode:
|
||||
--- - Asynchronous (non-blocking): Returns immediately and passes the response object to the
|
||||
--- provided `on_response` handler on completetion.
|
||||
---
|
||||
--- @param url string The URL for the request.
|
||||
--- @param opts? table Optional parameters:
|
||||
--- - `verbose` (boolean|nil): Enables verbose output.
|
||||
--- - `retry` (integer|nil): Number of retries on transient failures (default: 3).
|
||||
--- - `outpath` (string|nil): File path to save the response body to. If set, the `body` value in the Response Object will be `true` instead of the response body.
|
||||
--- @param on_response fun(err?: string, response?: { body: string|boolean }) Callback invoked on request
|
||||
--- completetion. The `body` field in the response object contains the raw response data (text or binary).
|
||||
--- Called with (err, nil) on failure, or (nil, { body = string|boolean }) on success.
|
||||
function M.request(url, opts, on_response)
|
||||
vim.validate({
|
||||
url = { url, 'string' },
|
||||
opts = { opts, 'table', true },
|
||||
on_response = { on_response, 'function' },
|
||||
})
|
||||
|
||||
opts = opts or {}
|
||||
local retry = opts.retry or 3
|
||||
|
||||
-- Build curl command
|
||||
local args = { 'curl' }
|
||||
if opts.verbose then
|
||||
table.insert(args, '--verbose')
|
||||
else
|
||||
vim.list_extend(args, { '--silent', '--show-error', '--fail' })
|
||||
end
|
||||
vim.list_extend(args, { '--location', '--retry', tostring(retry) })
|
||||
|
||||
if opts.outpath then
|
||||
vim.list_extend(args, { '--output', opts.outpath })
|
||||
end
|
||||
|
||||
table.insert(args, url)
|
||||
|
||||
local function on_exit(res)
|
||||
local err_msg = nil
|
||||
local response = nil
|
||||
|
||||
if res.code ~= 0 then
|
||||
err_msg = (res.stderr ~= '' and res.stderr)
|
||||
or string.format('Request failed with exit code %d', res.code)
|
||||
else
|
||||
response = {
|
||||
body = opts.outpath and true or res.stdout,
|
||||
}
|
||||
end
|
||||
|
||||
if on_response then
|
||||
on_response(err_msg, response)
|
||||
end
|
||||
end
|
||||
|
||||
vim.system(args, {}, on_exit)
|
||||
end
|
||||
|
||||
return M
|
Reference in New Issue
Block a user