neovim/runtime/lua/vim/uri.lua
Michael Lingelbach 68b2a9e569
fix: correctly capture uri scheme on windows (#16027)
closes https://github.com/neovim/neovim/issues/15261

* normalize uri path to forward slashes on windows
* use a capture group on windows that avoids mistaking drive letters as uri scheme
2021-10-15 12:03:41 -07:00

140 lines
3.5 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+-.]*):.*'
local WINDOWS_URI_SCHEME_PATTERN = '^([a-zA-Z]+[a-zA-Z0-9+-.]*):[a-zA-Z]:.*'
--- 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 volume_path = fname:match("^([a-zA-Z]:).*")
local is_windows = volume_path ~= nil
local scheme
if is_windows then
fname = fname:gsub("\\", "/")
scheme = fname:match(WINDOWS_URI_SCHEME_PATTERN)
else
scheme = fname:match(URI_SCHEME_PATTERN)
end
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