aboutsummaryrefslogtreecommitdiffstats
path: root/lua/conform/fs.lua
blob: 187bd7cfd534cd6670e1f8453836ac22cc6e5344 (plain)
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
local M = {}

local uv = vim.uv or vim.loop

---@type boolean
M.is_windows = uv.os_uname().version:match("Windows")

M.is_mac = uv.os_uname().sysname == "Darwin"

---@type string
M.sep = M.is_windows and "\\" or "/"

---@param ... string
M.join = function(...)
  return table.concat({ ... }, M.sep)
end

M.is_absolute = function(path)
  if M.is_windows then
    return path:lower():match("^%a:")
  else
    return vim.startswith(path, "/")
  end
end

M.abspath = function(path)
  if not M.is_absolute(path) then
    path = vim.fn.fnamemodify(path, ":p")
  end
  return path
end

--- Returns true if candidate is a subpath of root, or if they are the same path.
---@param root string
---@param candidate string
---@return boolean
M.is_subpath = function(root, candidate)
  if candidate == "" then
    return false
  end
  root = vim.fs.normalize(M.abspath(root))
  -- Trim trailing "/" from the root
  if root:find("/", -1) then
    root = root:sub(1, -2)
  end
  candidate = vim.fs.normalize(M.abspath(candidate))
  if M.is_windows then
    root = root:lower()
    candidate = candidate:lower()
  end
  if root == candidate then
    return true
  end
  local prefix = candidate:sub(1, root:len())
  if prefix ~= root then
    return false
  end

  local candidate_starts_with_sep = candidate:find("/", root:len() + 1, true) == root:len() + 1
  local root_ends_with_sep = root:find("/", root:len(), true) == root:len()

  return candidate_starts_with_sep or root_ends_with_sep
end

---Create a relative path from the source to the target
---@param source string
---@param target string
---@return string
M.relative_path = function(source, target)
  source = M.abspath(source)
  target = M.abspath(target)
  local path = {}
  while not M.is_subpath(source, target) do
    table.insert(path, "..")
    local new_source = vim.fs.dirname(source)

    -- If source is a root directory, we can't go up further so there is no relative path to the
    -- target. This should only happen on Windows, which prohibits relative paths between drives.
    if source == new_source then
      local log = require("conform.log")
      log.warn("Could not find relative path from %s to %s", source, target)
      return target
    end

    source = new_source
  end

  local offset = vim.endswith(source, M.sep) and 1 or 2
  local rel_target = target:sub(source:len() + offset)
  table.insert(path, rel_target)
  return M.join(unpack(path))
end

return M