aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSteven Arcangeli <stevearc@stevearc.com>2024-05-07 16:25:03 -0700
committerSteven Arcangeli <stevearc@stevearc.com>2024-05-07 16:25:15 -0700
commit6dc1603ea408f476a57937bbeaf7f86520a21a98 (patch)
tree095014f597d256cd3c3a4bfc40df8552a7c65470
parent393210360b9e807862e5329763526509f52b5218 (diff)
feat: formatters can use $RELATIVE_FILEPATH in args (#349)
-rw-r--r--README.md4
-rw-r--r--doc/conform.txt4
-rw-r--r--lua/conform/fs.lua68
-rw-r--r--lua/conform/runner.lua13
-rw-r--r--scripts/options_doc.lua4
-rw-r--r--tests/fs_spec.lua22
6 files changed, 108 insertions, 7 deletions
diff --git a/README.md b/README.md
index 027a7ce..2ff572b 100644
--- a/README.md
+++ b/README.md
@@ -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)
@@ -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 })
diff --git a/doc/conform.txt b/doc/conform.txt
index a7aed2a..977a0f0 100644
--- a/doc/conform.txt
+++ b/doc/conform.txt
@@ -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)
@@ -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 })
diff --git a/lua/conform/fs.lua b/lua/conform/fs.lua
index d303dbd..6f92e18 100644
--- a/lua/conform/fs.lua
+++ b/lua/conform/fs.lua
@@ -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
diff --git a/lua/conform/runner.lua b/lua/conform/runner.lua
index 0f31e64..b3a1e61 100644
--- a/lua/conform/runner.lua
+++ b/lua/conform/runner.lua
@@ -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 }
@@ -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
diff --git a/scripts/options_doc.lua b/scripts/options_doc.lua
index 96c0f2b..3e22802 100644
--- a/scripts/options_doc.lua
+++ b/scripts/options_doc.lua
@@ -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)
@@ -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 })
diff --git a/tests/fs_spec.lua b/tests/fs_spec.lua
new file mode 100644
index 0000000..2d788c9
--- /dev/null
+++ b/tests/fs_spec.lua
@@ -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)