aboutsummaryrefslogtreecommitdiff
path: root/runtime/lua/vim/uri.lua
blob: f1a12c72ecc9ec64c05ff9344fc477950854524b (plain) (blame)
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