uri.lua 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  1. --- TODO: This is implemented only for files now.
  2. -- https://tools.ietf.org/html/rfc3986
  3. -- https://tools.ietf.org/html/rfc2732
  4. -- https://tools.ietf.org/html/rfc2396
  5. local uri_decode
  6. do
  7. local schar = string.char
  8. --- Convert hex to char
  9. ---@private
  10. local function hex_to_char(hex)
  11. return schar(tonumber(hex, 16))
  12. end
  13. uri_decode = function(str)
  14. return str:gsub("%%([a-fA-F0-9][a-fA-F0-9])", hex_to_char)
  15. end
  16. end
  17. local uri_encode
  18. do
  19. local PATTERNS = {
  20. --- RFC 2396
  21. -- https://tools.ietf.org/html/rfc2396#section-2.2
  22. rfc2396 = "^A-Za-z0-9%-_.!~*'()";
  23. --- RFC 2732
  24. -- https://tools.ietf.org/html/rfc2732
  25. rfc2732 = "^A-Za-z0-9%-_.!~*'()[]";
  26. --- RFC 3986
  27. -- https://tools.ietf.org/html/rfc3986#section-2.2
  28. rfc3986 = "^A-Za-z0-9%-._~!$&'()*+,;=:@/";
  29. }
  30. local sbyte, tohex = string.byte
  31. if jit then
  32. tohex = require'bit'.tohex
  33. else
  34. tohex = function(b) return string.format("%02x", b) end
  35. end
  36. ---@private
  37. local function percent_encode_char(char)
  38. return "%"..tohex(sbyte(char), 2)
  39. end
  40. uri_encode = function(text, rfc)
  41. if not text then return end
  42. local pattern = PATTERNS[rfc] or PATTERNS.rfc3986
  43. return text:gsub("(["..pattern.."])", percent_encode_char)
  44. end
  45. end
  46. ---@private
  47. local function is_windows_file_uri(uri)
  48. return uri:match('^file:/+[a-zA-Z]:') ~= nil
  49. end
  50. --- Get a URI from a file path.
  51. ---@param path string Path to file
  52. ---@return string URI
  53. local function uri_from_fname(path)
  54. local volume_path, fname = path:match("^([a-zA-Z]:)(.*)")
  55. local is_windows = volume_path ~= nil
  56. if is_windows then
  57. path = volume_path..uri_encode(fname:gsub("\\", "/"))
  58. else
  59. path = uri_encode(path)
  60. end
  61. local uri_parts = {"file://"}
  62. if is_windows then
  63. table.insert(uri_parts, "/")
  64. end
  65. table.insert(uri_parts, path)
  66. return table.concat(uri_parts)
  67. end
  68. local URI_SCHEME_PATTERN = '^([a-zA-Z]+[a-zA-Z0-9.+-]*):.*'
  69. local WINDOWS_URI_SCHEME_PATTERN = '^([a-zA-Z]+[a-zA-Z0-9.+-]*):[a-zA-Z]:.*'
  70. --- Get a URI from a bufnr
  71. ---@param bufnr number
  72. ---@return string URI
  73. local function uri_from_bufnr(bufnr)
  74. local fname = vim.api.nvim_buf_get_name(bufnr)
  75. local volume_path = fname:match("^([a-zA-Z]:).*")
  76. local is_windows = volume_path ~= nil
  77. local scheme
  78. if is_windows then
  79. fname = fname:gsub("\\", "/")
  80. scheme = fname:match(WINDOWS_URI_SCHEME_PATTERN)
  81. else
  82. scheme = fname:match(URI_SCHEME_PATTERN)
  83. end
  84. if scheme then
  85. return fname
  86. else
  87. return uri_from_fname(fname)
  88. end
  89. end
  90. --- Get a filename from a URI
  91. ---@param uri string
  92. ---@return string filename or unchanged URI for non-file URIs
  93. local function uri_to_fname(uri)
  94. local scheme = assert(uri:match(URI_SCHEME_PATTERN), 'URI must contain a scheme: ' .. uri)
  95. if scheme ~= 'file' then
  96. return uri
  97. end
  98. uri = uri_decode(uri)
  99. -- TODO improve this.
  100. if is_windows_file_uri(uri) then
  101. uri = uri:gsub('^file:/+', '')
  102. uri = uri:gsub('/', '\\')
  103. else
  104. uri = uri:gsub('^file:/+', '/')
  105. end
  106. return uri
  107. end
  108. --- Get the buffer for a uri.
  109. --- Creates a new unloaded buffer if no buffer for the uri already exists.
  110. --
  111. ---@param uri string
  112. ---@return number bufnr
  113. local function uri_to_bufnr(uri)
  114. return vim.fn.bufadd(uri_to_fname(uri))
  115. end
  116. return {
  117. uri_from_fname = uri_from_fname,
  118. uri_from_bufnr = uri_from_bufnr,
  119. uri_to_fname = uri_to_fname,
  120. uri_to_bufnr = uri_to_bufnr,
  121. }
  122. -- vim:sw=2 ts=2 et