2023-08-01 08:28:28 -07:00
|
|
|
---TODO: This is implemented only for files currently.
|
2019-11-13 13:55:26 -07:00
|
|
|
-- https://tools.ietf.org/html/rfc3986
|
|
|
|
-- https://tools.ietf.org/html/rfc2732
|
|
|
|
-- https://tools.ietf.org/html/rfc2396
|
|
|
|
|
2023-08-01 08:28:28 -07:00
|
|
|
local M = {}
|
|
|
|
local sbyte = string.byte
|
|
|
|
local schar = string.char
|
|
|
|
local tohex = require('bit').tohex
|
|
|
|
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]:.*'
|
|
|
|
local PATTERNS = {
|
2024-02-06 04:30:02 -07:00
|
|
|
-- RFC 2396
|
|
|
|
-- https://tools.ietf.org/html/rfc2396#section-2.2
|
2023-08-01 08:28:28 -07:00
|
|
|
rfc2396 = "^A-Za-z0-9%-_.!~*'()",
|
2024-02-06 04:30:02 -07:00
|
|
|
-- RFC 2732
|
|
|
|
-- https://tools.ietf.org/html/rfc2732
|
2023-08-01 08:28:28 -07:00
|
|
|
rfc2732 = "^A-Za-z0-9%-_.!~*'()[]",
|
2024-02-06 04:30:02 -07:00
|
|
|
-- RFC 3986
|
|
|
|
-- https://tools.ietf.org/html/rfc3986#section-2.2
|
2023-08-01 08:28:28 -07:00
|
|
|
rfc3986 = "^A-Za-z0-9%-._~!$&'()*+,;=:@/",
|
|
|
|
}
|
2020-09-14 06:12:17 -07:00
|
|
|
|
2023-08-01 08:28:28 -07:00
|
|
|
---Converts hex to char
|
|
|
|
---@param hex string
|
|
|
|
---@return string
|
|
|
|
local function hex_to_char(hex)
|
|
|
|
return schar(tonumber(hex, 16))
|
2019-11-13 13:55:26 -07:00
|
|
|
end
|
|
|
|
|
2023-08-01 08:28:28 -07:00
|
|
|
---@param char string
|
|
|
|
---@return string
|
|
|
|
local function percent_encode_char(char)
|
|
|
|
return '%' .. tohex(sbyte(char), 2)
|
2019-11-13 13:55:26 -07:00
|
|
|
end
|
|
|
|
|
2023-08-01 08:28:28 -07:00
|
|
|
---@param uri string
|
|
|
|
---@return boolean
|
2019-11-13 13:55:26 -07:00
|
|
|
local function is_windows_file_uri(uri)
|
2021-07-07 15:34:22 -07:00
|
|
|
return uri:match('^file:/+[a-zA-Z]:') ~= nil
|
2019-11-13 13:55:26 -07:00
|
|
|
end
|
|
|
|
|
2023-08-01 08:28:28 -07:00
|
|
|
---URI-encodes a string using percent escapes.
|
|
|
|
---@param str string string to encode
|
|
|
|
---@param rfc "rfc2396" | "rfc2732" | "rfc3986" | nil
|
|
|
|
---@return string encoded string
|
|
|
|
function M.uri_encode(str, rfc)
|
|
|
|
local pattern = PATTERNS[rfc] or PATTERNS.rfc3986
|
|
|
|
return (str:gsub('([' .. pattern .. '])', percent_encode_char)) -- clamped to 1 retval with ()
|
|
|
|
end
|
|
|
|
|
|
|
|
---URI-decodes a string containing percent escapes.
|
|
|
|
---@param str string string to decode
|
|
|
|
---@return string decoded string
|
|
|
|
function M.uri_decode(str)
|
|
|
|
return (str:gsub('%%([a-fA-F0-9][a-fA-F0-9])', hex_to_char)) -- clamped to 1 retval with ()
|
|
|
|
end
|
2023-07-18 07:42:30 -07:00
|
|
|
|
2023-08-01 08:28:28 -07:00
|
|
|
---Gets a URI from a file path.
|
2021-11-18 13:12:21 -07:00
|
|
|
---@param path string Path to file
|
|
|
|
---@return string URI
|
2023-07-18 07:42:30 -07:00
|
|
|
function M.uri_from_fname(path)
|
2023-09-20 19:03:40 -07:00
|
|
|
local volume_path, fname = path:match('^([a-zA-Z]:)(.*)') ---@type string?
|
2019-11-13 13:55:26 -07:00
|
|
|
local is_windows = volume_path ~= nil
|
|
|
|
if is_windows then
|
2023-08-01 08:28:28 -07:00
|
|
|
path = volume_path .. M.uri_encode(fname:gsub('\\', '/'))
|
2019-11-13 13:55:26 -07:00
|
|
|
else
|
2023-08-01 08:28:28 -07:00
|
|
|
path = M.uri_encode(path)
|
2019-11-13 13:55:26 -07:00
|
|
|
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
|
|
|
|
|
2023-08-01 08:28:28 -07:00
|
|
|
---Gets a URI from a bufnr.
|
2023-03-04 06:05:16 -07:00
|
|
|
---@param bufnr integer
|
2021-11-18 13:12:21 -07:00
|
|
|
---@return string URI
|
2023-07-18 07:42:30 -07:00
|
|
|
function M.uri_from_bufnr(bufnr)
|
2020-05-04 20:06:40 -07:00
|
|
|
local fname = vim.api.nvim_buf_get_name(bufnr)
|
2021-10-15 12:03:41 -07:00
|
|
|
local volume_path = fname:match('^([a-zA-Z]:).*')
|
|
|
|
local is_windows = volume_path ~= nil
|
2023-09-20 19:03:40 -07:00
|
|
|
local scheme ---@type string?
|
2021-10-15 12:03:41 -07:00
|
|
|
if is_windows then
|
|
|
|
fname = fname:gsub('\\', '/')
|
|
|
|
scheme = fname:match(WINDOWS_URI_SCHEME_PATTERN)
|
|
|
|
else
|
|
|
|
scheme = fname:match(URI_SCHEME_PATTERN)
|
|
|
|
end
|
2020-05-04 20:06:40 -07:00
|
|
|
if scheme then
|
|
|
|
return fname
|
|
|
|
else
|
2023-07-18 07:42:30 -07:00
|
|
|
return M.uri_from_fname(fname)
|
2020-05-04 20:06:40 -07:00
|
|
|
end
|
2019-11-13 13:55:26 -07:00
|
|
|
end
|
|
|
|
|
2023-08-01 08:28:28 -07:00
|
|
|
---Gets a filename from a URI.
|
2021-11-18 13:12:21 -07:00
|
|
|
---@param uri string
|
|
|
|
---@return string filename or unchanged URI for non-file URIs
|
2023-07-18 07:42:30 -07:00
|
|
|
function M.uri_to_fname(uri)
|
2020-05-21 09:17:21 -07:00
|
|
|
local scheme = assert(uri:match(URI_SCHEME_PATTERN), 'URI must contain a scheme: ' .. uri)
|
|
|
|
if scheme ~= 'file' then
|
|
|
|
return uri
|
|
|
|
end
|
2023-08-01 08:28:28 -07:00
|
|
|
uri = M.uri_decode(uri)
|
|
|
|
--TODO improve this.
|
2019-11-13 13:55:26 -07:00
|
|
|
if is_windows_file_uri(uri) then
|
2023-09-20 19:03:40 -07:00
|
|
|
uri = uri:gsub('^file:/+', ''):gsub('/', '\\')
|
2019-11-13 13:55:26 -07:00
|
|
|
else
|
2023-09-20 19:03:40 -07:00
|
|
|
uri = uri:gsub('^file:/+', '/') ---@type string
|
2019-11-13 13:55:26 -07:00
|
|
|
end
|
2020-03-30 05:30:24 -07:00
|
|
|
return uri
|
2019-11-13 13:55:26 -07:00
|
|
|
end
|
|
|
|
|
2023-08-01 08:28:28 -07:00
|
|
|
---Gets the buffer for a uri.
|
|
|
|
---Creates a new unloaded buffer if no buffer for the uri already exists.
|
2021-11-18 13:12:21 -07:00
|
|
|
--
|
|
|
|
---@param uri string
|
2023-03-04 06:05:16 -07:00
|
|
|
---@return integer bufnr
|
2023-07-18 07:42:30 -07:00
|
|
|
function M.uri_to_bufnr(uri)
|
|
|
|
return vim.fn.bufadd(M.uri_to_fname(uri))
|
2019-11-20 12:39:54 -07:00
|
|
|
end
|
|
|
|
|
2023-07-18 07:42:30 -07:00
|
|
|
return M
|