1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
|
--- 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
|