mirror of
https://github.com/neovim/neovim.git
synced 2025-09-06 11:28:22 +00:00

RFC 8089, which defines the file URI scheme, also allows URIs without a hostname, i.e. of the form file:/path/to/file. These are returned by some language servers and accepted by other LSP implementations, such as VSCode's, so it is reasonable for us to accept them as well.
131 lines
3.2 KiB
Lua
131 lines
3.2 KiB
Lua
--- TODO: This is implemented only for files now.
|
|
-- https://tools.ietf.org/html/rfc3986
|
|
-- https://tools.ietf.org/html/rfc2732
|
|
-- https://tools.ietf.org/html/rfc2396
|
|
|
|
|
|
local uri_decode
|
|
do
|
|
local schar = string.char
|
|
|
|
--- Convert hex to char
|
|
--@private
|
|
local function hex_to_char(hex)
|
|
return schar(tonumber(hex, 16))
|
|
end
|
|
uri_decode = function(str)
|
|
return str:gsub("%%([a-fA-F0-9][a-fA-F0-9])", hex_to_char)
|
|
end
|
|
end
|
|
|
|
local uri_encode
|
|
do
|
|
local PATTERNS = {
|
|
--- RFC 2396
|
|
-- https://tools.ietf.org/html/rfc2396#section-2.2
|
|
rfc2396 = "^A-Za-z0-9%-_.!~*'()";
|
|
--- RFC 2732
|
|
-- https://tools.ietf.org/html/rfc2732
|
|
rfc2732 = "^A-Za-z0-9%-_.!~*'()[]";
|
|
--- RFC 3986
|
|
-- https://tools.ietf.org/html/rfc3986#section-2.2
|
|
rfc3986 = "^A-Za-z0-9%-._~!$&'()*+,;=:@/";
|
|
}
|
|
local sbyte, tohex = string.byte
|
|
if jit then
|
|
tohex = require'bit'.tohex
|
|
else
|
|
tohex = function(b) return string.format("%02x", b) end
|
|
end
|
|
|
|
--@private
|
|
local function percent_encode_char(char)
|
|
return "%"..tohex(sbyte(char), 2)
|
|
end
|
|
uri_encode = function(text, rfc)
|
|
if not text then return end
|
|
local pattern = PATTERNS[rfc] or PATTERNS.rfc3986
|
|
return text:gsub("(["..pattern.."])", percent_encode_char)
|
|
end
|
|
end
|
|
|
|
|
|
--@private
|
|
local function is_windows_file_uri(uri)
|
|
return uri:match('^file:/+[a-zA-Z]:') ~= nil
|
|
end
|
|
|
|
--- Get a URI from a file path.
|
|
--@param path (string): Path to file
|
|
--@return URI
|
|
local function uri_from_fname(path)
|
|
local volume_path, fname = path:match("^([a-zA-Z]:)(.*)")
|
|
local is_windows = volume_path ~= nil
|
|
if is_windows then
|
|
path = volume_path..uri_encode(fname:gsub("\\", "/"))
|
|
else
|
|
path = uri_encode(path)
|
|
end
|
|
local uri_parts = {"file://"}
|
|
if is_windows then
|
|
table.insert(uri_parts, "/")
|
|
end
|
|
table.insert(uri_parts, path)
|
|
return table.concat(uri_parts)
|
|
end
|
|
|
|
local URI_SCHEME_PATTERN = '^([a-zA-Z]+[a-zA-Z0-9+-.]*):/+.*'
|
|
|
|
--- Get a URI from a bufnr
|
|
--@param bufnr (number): Buffer number
|
|
--@return URI
|
|
local function uri_from_bufnr(bufnr)
|
|
local fname = vim.api.nvim_buf_get_name(bufnr)
|
|
local scheme = fname:match(URI_SCHEME_PATTERN)
|
|
if scheme then
|
|
return fname
|
|
else
|
|
return uri_from_fname(fname)
|
|
end
|
|
end
|
|
|
|
--- Get a filename from a URI
|
|
--@param uri (string): The URI
|
|
--@return Filename
|
|
local function uri_to_fname(uri)
|
|
local scheme = assert(uri:match(URI_SCHEME_PATTERN), 'URI must contain a scheme: ' .. uri)
|
|
if scheme ~= 'file' then
|
|
return uri
|
|
end
|
|
uri = uri_decode(uri)
|
|
-- TODO improve this.
|
|
if is_windows_file_uri(uri) then
|
|
uri = uri:gsub('^file:/+', '')
|
|
uri = uri:gsub('/', '\\')
|
|
else
|
|
uri = uri:gsub('^file:/+', '/')
|
|
end
|
|
return uri
|
|
end
|
|
|
|
--- Return or create a buffer for a uri.
|
|
--@param uri (string): The URI
|
|
--@return bufnr.
|
|
--@note Creates buffer but does not load it
|
|
local function uri_to_bufnr(uri)
|
|
local scheme = assert(uri:match(URI_SCHEME_PATTERN), 'URI must contain a scheme: ' .. uri)
|
|
if scheme == 'file' then
|
|
return vim.fn.bufadd(uri_to_fname(uri))
|
|
else
|
|
return vim.fn.bufadd(uri)
|
|
end
|
|
end
|
|
|
|
return {
|
|
uri_from_fname = uri_from_fname,
|
|
uri_from_bufnr = uri_from_bufnr,
|
|
uri_to_fname = uri_to_fname,
|
|
uri_to_bufnr = uri_to_bufnr,
|
|
}
|
|
-- vim:sw=2 ts=2 et
|