uri.lua 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  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)
  35. return string.format('%02x', b)
  36. end
  37. end
  38. ---@private
  39. local function percent_encode_char(char)
  40. return '%' .. tohex(sbyte(char), 2)
  41. end
  42. uri_encode = function(text, rfc)
  43. if not text then
  44. return
  45. end
  46. local pattern = PATTERNS[rfc] or PATTERNS.rfc3986
  47. return text:gsub('([' .. pattern .. '])', percent_encode_char)
  48. end
  49. end
  50. ---@private
  51. local function is_windows_file_uri(uri)
  52. return uri:match('^file:/+[a-zA-Z]:') ~= nil
  53. end
  54. --- Get a URI from a file path.
  55. ---@param path string Path to file
  56. ---@return string URI
  57. local function uri_from_fname(path)
  58. local volume_path, fname = path:match('^([a-zA-Z]:)(.*)')
  59. local is_windows = volume_path ~= nil
  60. if is_windows then
  61. path = volume_path .. uri_encode(fname:gsub('\\', '/'))
  62. else
  63. path = uri_encode(path)
  64. end
  65. local uri_parts = { 'file://' }
  66. if is_windows then
  67. table.insert(uri_parts, '/')
  68. end
  69. table.insert(uri_parts, path)
  70. return table.concat(uri_parts)
  71. end
  72. local URI_SCHEME_PATTERN = '^([a-zA-Z]+[a-zA-Z0-9.+-]*):.*'
  73. local WINDOWS_URI_SCHEME_PATTERN = '^([a-zA-Z]+[a-zA-Z0-9.+-]*):[a-zA-Z]:.*'
  74. --- Get a URI from a bufnr
  75. ---@param bufnr number
  76. ---@return string URI
  77. local function uri_from_bufnr(bufnr)
  78. local fname = vim.api.nvim_buf_get_name(bufnr)
  79. local volume_path = fname:match('^([a-zA-Z]:).*')
  80. local is_windows = volume_path ~= nil
  81. local scheme
  82. if is_windows then
  83. fname = fname:gsub('\\', '/')
  84. scheme = fname:match(WINDOWS_URI_SCHEME_PATTERN)
  85. else
  86. scheme = fname:match(URI_SCHEME_PATTERN)
  87. end
  88. if scheme then
  89. return fname
  90. else
  91. return uri_from_fname(fname)
  92. end
  93. end
  94. --- Get a filename from a URI
  95. ---@param uri string
  96. ---@return string filename or unchanged URI for non-file URIs
  97. local function uri_to_fname(uri)
  98. local scheme = assert(uri:match(URI_SCHEME_PATTERN), 'URI must contain a scheme: ' .. uri)
  99. if scheme ~= 'file' then
  100. return uri
  101. end
  102. uri = uri_decode(uri)
  103. -- TODO improve this.
  104. if is_windows_file_uri(uri) then
  105. uri = uri:gsub('^file:/+', '')
  106. uri = uri:gsub('/', '\\')
  107. else
  108. uri = uri:gsub('^file:/+', '/')
  109. end
  110. return uri
  111. end
  112. --- Get the buffer for a uri.
  113. --- Creates a new unloaded buffer if no buffer for the uri already exists.
  114. --
  115. ---@param uri string
  116. ---@return number bufnr
  117. local function uri_to_bufnr(uri)
  118. return vim.fn.bufadd(uri_to_fname(uri))
  119. end
  120. return {
  121. uri_from_fname = uri_from_fname,
  122. uri_from_bufnr = uri_from_bufnr,
  123. uri_to_fname = uri_to_fname,
  124. uri_to_bufnr = uri_to_bufnr,
  125. }
  126. -- vim:sw=2 ts=2 et