Skip to content

Commit

Permalink
feat: formatters can use $RELATIVE_FILEPATH in args (#349)
Browse files Browse the repository at this point in the history
  • Loading branch information
stevearc committed May 7, 2024
1 parent 3932103 commit 6dc1603
Show file tree
Hide file tree
Showing 6 changed files with 108 additions and 7 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -464,7 +464,7 @@ require("conform").setup({
-- Return a single string instead of a list to run the command in a shell
args = { "--stdin-from-filename", "$FILENAME" },
-- If the formatter supports range formatting, create the range arguments here
range_args = function(ctx)
range_args = function(self, ctx)
return { "--line-start", ctx.range.start[1], "--line-end", ctx.range["end"][1] }
end,
-- Send file contents to stdin, read new contents from stdout (default true)
Expand All @@ -478,7 +478,7 @@ require("conform").setup({
-- When stdin=false, use this template to generate the temporary file that gets formatted
tmpfile_format = ".conform.$RANDOM.$FILENAME",
-- When returns false, the formatter will not be used
condition = function(ctx)
condition = function(self, ctx)
return vim.fs.basename(ctx.filename) ~= "README.md"
end,
-- Exit codes that indicate success (default { 0 })
Expand Down
4 changes: 2 additions & 2 deletions doc/conform.txt
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ OPTIONS *conform-option
-- Return a single string instead of a list to run the command in a shell
args = { "--stdin-from-filename", "$FILENAME" },
-- If the formatter supports range formatting, create the range arguments here
range_args = function(ctx)
range_args = function(self, ctx)
return { "--line-start", ctx.range.start[1], "--line-end", ctx.range["end"][1] }
end,
-- Send file contents to stdin, read new contents from stdout (default true)
Expand All @@ -75,7 +75,7 @@ OPTIONS *conform-option
-- When stdin=false, use this template to generate the temporary file that gets formatted
tmpfile_format = ".conform.$RANDOM.$FILENAME",
-- When returns false, the formatter will not be used
condition = function(ctx)
condition = function(self, ctx)
return vim.fs.basename(ctx.filename) ~= "README.md"
end,
-- Exit codes that indicate success (default { 0 })
Expand Down
68 changes: 68 additions & 0 deletions lua/conform/fs.lua
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,72 @@ 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)
assert(source ~= new_source)
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
13 changes: 12 additions & 1 deletion lua/conform/runner.lua
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,17 @@ M.build_cmd = function(formatter_name, ctx, config)
end
end

local cwd
if config.cwd then
cwd = config.cwd(config, ctx)
end
local relative_filename = fs.relative_path(cwd or vim.fn.getcwd(), ctx.filename)

if type(args) == "string" then
local interpolated = args:gsub("$FILENAME", ctx.filename):gsub("$DIRNAME", ctx.dirname)
local interpolated = args
:gsub("$FILENAME", ctx.filename)
:gsub("$DIRNAME", ctx.dirname)
:gsub("$RELATIVE_FILEPATH", relative_filename)
return command .. " " .. interpolated
else
local cmd = { command }
Expand All @@ -44,6 +53,8 @@ M.build_cmd = function(formatter_name, ctx, config)
v = ctx.filename
elseif v == "$DIRNAME" then
v = ctx.dirname
elseif v == "$RELATIVE_FILEPATH" then
v = relative_filename
end
table.insert(cmd, v)
end
Expand Down
4 changes: 2 additions & 2 deletions scripts/options_doc.lua
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ require("conform").setup({
-- Return a single string instead of a list to run the command in a shell
args = { "--stdin-from-filename", "$FILENAME" },
-- If the formatter supports range formatting, create the range arguments here
range_args = function(ctx)
range_args = function(self, ctx)
return { "--line-start", ctx.range.start[1], "--line-end", ctx.range["end"][1] }
end,
-- Send file contents to stdin, read new contents from stdout (default true)
Expand All @@ -62,7 +62,7 @@ require("conform").setup({
-- When stdin=false, use this template to generate the temporary file that gets formatted
tmpfile_format = ".conform.$RANDOM.$FILENAME",
-- When returns false, the formatter will not be used
condition = function(ctx)
condition = function(self, ctx)
return vim.fs.basename(ctx.filename) ~= "README.md"
end,
-- Exit codes that indicate success (default { 0 })
Expand Down
22 changes: 22 additions & 0 deletions tests/fs_spec.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
local fs = require("conform.fs")

describe("fs", function()
local relative_paths = {
{ "/home", "/home/file.txt", "file.txt" },
{ "/home/", "/home/file.txt", "file.txt" },
{ "/home", "/foo/file.txt", "../foo/file.txt" },
{ "/home/foo", "/home/bar/file.txt", "../bar/file.txt" },
{ "/home", "/file.txt", "../file.txt" },
{ "/home", "/home/foo/file.txt", "foo/file.txt" },
{ ".", "foo/file.txt", "foo/file.txt" },
{ "home", "home/file.txt", "file.txt" },
{ "home", "file.txt", "../file.txt" },
}

it("relative_path", function()
for _, paths in ipairs(relative_paths) do
local source, target, expected = unpack(paths)
assert.are.same(fs.relative_path(source, target), expected)
end
end)
end)

0 comments on commit 6dc1603

Please sign in to comment.