From b292a4f78a5c76019a7b1a7c2af31fef5fd0d23d Mon Sep 17 00:00:00 2001 From: linrongbin16 Date: Fri, 20 Oct 2023 22:38:17 +0800 Subject: [PATCH] fix(path): resolve symlink in Windows (#75) * fix(logger): remove 'error!' prefix * fix(git): fix command line running cwd * refactor(util): rewrite path relative, normalize util functions * fix(path): fix symlink resolve in Windows * refactor(lint): fix typecheck * refactor(lint): fix typecheck * test(spawn): fix test cases * fix(spawn): set 'cwd' options --- lua/gitlinker.lua | 4 +- lua/gitlinker/git.lua | 38 ++++++++---------- lua/gitlinker/logger.lua | 14 +++---- lua/gitlinker/spawn.lua | 13 +++--- lua/gitlinker/util.lua | 85 ++++++++++++++++++++-------------------- test/spawn_spec.lua | 50 +++++++++++++++++------ 6 files changed, 113 insertions(+), 91 deletions(-) diff --git a/lua/gitlinker.lua b/lua/gitlinker.lua index 18959b7..7ad1e5e 100644 --- a/lua/gitlinker.lua +++ b/lua/gitlinker.lua @@ -197,7 +197,7 @@ local function make_link_data(range) vim.inspect(rev) ) - local buf_path_on_root = util.relative_path(root) + local buf_path_on_root = util.path_relative(root) --[[@as string]] logger.debug( "|make_link_data| root(%s):%s, buf_path_on_root(%s):%s", vim.inspect(type(root)), @@ -215,7 +215,7 @@ local function make_link_data(range) vim.inspect(file_in_rev_result) ) - local buf_path_on_cwd = util.relative_path() + local buf_path_on_cwd = util.path_relative() --[[@as string]] logger.debug( "|make_link_data| buf_path_on_cwd:%s", vim.inspect(buf_path_on_cwd) diff --git a/lua/gitlinker/git.lua b/lua/gitlinker/git.lua index aea8645..4ed0f0d 100644 --- a/lua/gitlinker/git.lua +++ b/lua/gitlinker/git.lua @@ -41,29 +41,30 @@ end -- wrap the git command to do the right thing always --- @package --- @param args string[] ---- @param cwd string|nil +--- @param cwd string? --- @return JobResult local function cmd(args, cwd) local result = JobResult:new() - local sp = spawn.Spawn:make(args, function(line) - if type(line) == "string" then - table.insert(result.stdout, line) - end - end, function(line) - if type(line) == "string" then - table.insert(result.stderr, line) - end - end) --[[@as Spawn]] + local sp = spawn.Spawn:make(args, { + cwd = cwd or vim.fn.getcwd(), + on_stdout = function(line) + if type(line) == "string" then + table.insert(result.stdout, line) + end + end, + on_stderr = function(line) + if type(line) == "string" then + table.insert(result.stderr, line) + end + end, + }) --[[@as Spawn]] sp:run() logger.debug( - "|cmd| args(%s):%s, cwd(%s):%s, result(%s):%s", - vim.inspect(type(args)), + "|git.cmd| args:%s, cwd:%s, result:%s", vim.inspect(args), - vim.inspect(type(cwd)), vim.inspect(cwd), - vim.inspect(type(result)), vim.inspect(result) ) return result @@ -102,10 +103,8 @@ end local function _get_rev(revspec) local result = cmd({ "git", "rev-parse", revspec }) logger.debug( - "|git._get_rev| revspec(%s):%s, result(%s):%s", - vim.inspect(type(revspec)), + "|git._get_rev| revspec:%s, result:%s", vim.inspect(revspec), - vim.inspect(type(result)), vim.inspect(result) ) return result:has_out() and result.stdout[1] or nil @@ -232,12 +231,9 @@ local function get_root() local buf_dir = vim.fn.fnamemodify(buf_path, ":p:h") local result = cmd({ "git", "rev-parse", "--show-toplevel" }, buf_dir) logger.debug( - "|git.get_root| buf_path(%s):%s, buf_dir(%s):%s, result(%s):%s", - vim.inspect(type(buf_path)), + "|git.get_root| buf_path:%s, buf_dir:%s, result:%s", vim.inspect(buf_path), - vim.inspect(type(buf_dir)), vim.inspect(buf_dir), - vim.inspect(type(result)), vim.inspect(result) ) if not result:has_out() then diff --git a/lua/gitlinker/logger.lua b/lua/gitlinker/logger.lua index 41461b9..e2cac71 100644 --- a/lua/gitlinker/logger.lua +++ b/lua/gitlinker/logger.lua @@ -72,15 +72,15 @@ local function log(level, msg) local msg_lines = vim.split(msg, "\n", { plain = true }) if Configs.console_log and level >= LogLevels.INFO then local msg_chunks = {} - local prefix = "" - if level == LogLevels.ERROR then - prefix = "error! " - elseif level == LogLevels.WARN then - prefix = "warning! " - end + -- local prefix = "" + -- if level == LogLevels.ERROR then + -- prefix = "error! " + -- elseif level == LogLevels.WARN then + -- prefix = "warning! " + -- end for _, line in ipairs(msg_lines) do table.insert(msg_chunks, { - string.format("[gitlinker] %s%s", prefix, line), + string.format("[gitlinker] %s", --[[prefix,]] line), LogHighlights[level], }) end diff --git a/lua/gitlinker/spawn.lua b/lua/gitlinker/spawn.lua index 0d8a4b8..5a5283d 100644 --- a/lua/gitlinker/spawn.lua +++ b/lua/gitlinker/spawn.lua @@ -60,6 +60,7 @@ end --- @alias SpawnLineConsumer fun(line:string):any --- @class Spawn --- @field cmds string[] +--- @field cwd string? --- @field fn_out_line_consumer SpawnLineConsumer --- @field fn_err_line_consumer SpawnLineConsumer --- @field out_pipe uv_pipe_t @@ -81,10 +82,9 @@ local function dummy_stderr_line_consumer(line) end --- @param cmds string[] ---- @param fn_out_line_consumer SpawnLineConsumer ---- @param fn_err_line_consumer SpawnLineConsumer? +--- @param opts {on_stdout:SpawnLineConsumer,on_stderr:SpawnLineConsumer?,cwd:string?} --- @return Spawn? -function Spawn:make(cmds, fn_out_line_consumer, fn_err_line_consumer) +function Spawn:make(cmds, opts) local out_pipe = vim.loop.new_pipe(false) --[[@as uv_pipe_t]] local err_pipe = vim.loop.new_pipe(false) --[[@as uv_pipe_t]] if not out_pipe or not err_pipe then @@ -93,9 +93,9 @@ function Spawn:make(cmds, fn_out_line_consumer, fn_err_line_consumer) local o = { cmds = cmds, - fn_out_line_consumer = fn_out_line_consumer, - fn_err_line_consumer = fn_err_line_consumer - or dummy_stderr_line_consumer, + cwd = opts.cwd or vim.fn.getcwd(), + fn_out_line_consumer = opts.on_stdout, + fn_err_line_consumer = opts.on_stderr or dummy_stderr_line_consumer, out_pipe = out_pipe, err_pipe = err_pipe, out_buffer = nil, @@ -228,6 +228,7 @@ end function Spawn:run() self.process_handle, self.process_id = vim.loop.spawn(self.cmds[1], { args = vim.list_slice(self.cmds, 2), + cwd = self.cwd, stdio = { nil, self.out_pipe, self.err_pipe }, hide = true, -- verbatim = true, diff --git a/lua/gitlinker/util.lua b/lua/gitlinker/util.lua index 7d11898..e55d4c0 100644 --- a/lua/gitlinker/util.lua +++ b/lua/gitlinker/util.lua @@ -1,56 +1,56 @@ local logger = require("gitlinker.logger") +-- normalize path slash from '\\' to '/' +--- @param p string +--- @return string +local function path_normalize(p) + local result = vim.fn.expand(p) + if string.match(result, [[\\]]) then + result = string.gsub(result, [[\\]], [[/]]) + end + if string.match(result, [[\]]) then + result = string.gsub(result, [[\]], [[/]]) + end + return vim.trim(result) +end + --- @param cwd string? --- @return string? -local function relative_path(cwd) - logger.debug( - "|util.relative_path| cwd1(%s):%s", - vim.inspect(type(cwd)), - vim.inspect(cwd) - ) - - local buf_path = vim.api.nvim_buf_get_name(0) - if cwd == nil or string.len(cwd) <= 0 then - cwd = vim.fn.getcwd() - end - -- get real path from possibly symlink +local function path_relative(cwd) + cwd = cwd or vim.fn.getcwd() cwd = vim.fn.resolve(cwd) - -- normalize path slash from '\\' to '/' - if cwd:find("\\") then - cwd = cwd:gsub("\\\\", "/") - cwd = cwd:gsub("\\", "/") - end - if buf_path:find("\\") then - buf_path = buf_path:gsub("\\\\", "/") - buf_path = buf_path:gsub("\\", "/") - end + cwd = path_normalize(cwd) + + local bufpath = vim.api.nvim_buf_get_name(0) + bufpath = vim.fn.resolve(bufpath) + bufpath = path_normalize(bufpath) + logger.debug( - "|util.relative_path| buf_path(%s):%s, cwd(%s):%s", - vim.inspect(type(buf_path)), - vim.inspect(buf_path), - vim.inspect(type(cwd)), - vim.inspect(cwd) + "|util.path_relative| enter, cwd:%s, bufpath:%s", + vim.inspect(cwd), + vim.inspect(bufpath) ) - local relpath = nil - if buf_path:sub(1, #cwd) == cwd then - relpath = buf_path:sub(#cwd + 1, -1) - if relpath:sub(1, 1) == "/" or relpath:sub(1, 1) == "\\" then - relpath = relpath:sub(2, -1) + local result = nil + if + string.len(bufpath) >= string.len(cwd) + and bufpath:sub(1, #cwd) == cwd + then + result = bufpath:sub(#cwd + 1) + if result:sub(1, 1) == "/" or result:sub(1, 1) == "\\" then + result = result:sub(2) end end - logger.debug( - "|util.relative_path| relpath(%s):%s", - vim.inspect(type(relpath)), - vim.inspect(relpath) - ) - return relpath + logger.debug("|util.path_relative| result:%s", vim.inspect(result)) + return result end +--- @param m string +--- @return boolean local function is_visual_mode(m) - return type(m) == "string" and m:upper() == "V" - or m:upper() == "CTRL-V" - or m:upper() == "" + return type(m) == "string" and string.upper(m) == "V" + or string.upper(m) == "CTRL-V" + or string.upper(m) == "" or m == "\22" end @@ -76,9 +76,10 @@ local function line_range() return { lstart = lstart, lend = lend } end ---- @type table local M = { - relative_path = relative_path, + path_normalize = path_normalize, + path_relative = path_relative, + is_visual_mode = is_visual_mode, line_range = line_range, } diff --git a/test/spawn_spec.lua b/test/spawn_spec.lua index 3d843c1..825ec33 100644 --- a/test/spawn_spec.lua +++ b/test/spawn_spec.lua @@ -48,7 +48,10 @@ describe("spawn", function() end) describe("[Spawn]", function() it("open", function() - local sp = spawn.Spawn:make({ "cat", "README.md" }, function() end) --[[@as Spawn]] + local sp = spawn.Spawn:make( + { "cat", "README.md" }, + { on_stdout = function() end } + ) --[[@as Spawn]] assert_eq(type(sp), "table") assert_eq(type(sp.cmds), "table") assert_eq(#sp.cmds, 2) @@ -69,7 +72,10 @@ describe("spawn", function() assert_eq(line, lines[i]) i = i + 1 end - local sp = spawn.Spawn:make({ "cat", "README.md" }, process_line) --[[@as Spawn]] + local sp = spawn.Spawn:make( + { "cat", "README.md" }, + { on_stdout = process_line } + ) --[[@as Spawn]] local pos = sp:_consume_line(content, process_line) if pos <= #content then local line = content:sub(pos, #content) @@ -87,7 +93,10 @@ describe("spawn", function() assert_eq(line, lines[i]) i = i + 1 end - local sp = spawn.Spawn:make({ "cat", "README.md" }, process_line) --[[@as Spawn]] + local sp = spawn.Spawn:make( + { "cat", "README.md" }, + { on_stdout = process_line } + ) --[[@as Spawn]] local content_splits = vim.split(content, "\n", { plain = true, trimempty = false }) for j, splits in ipairs(content_splits) do @@ -110,7 +119,10 @@ describe("spawn", function() assert_eq(line, lines[i]) i = i + 1 end - local sp = spawn.Spawn:make({ "cat", "README.md" }, process_line) --[[@as Spawn]] + local sp = spawn.Spawn:make( + { "cat", "README.md" }, + { on_stdout = process_line } + ) --[[@as Spawn]] local content_splits = vim.split(content, " ", { plain = true, trimempty = false }) for j, splits in ipairs(content_splits) do @@ -136,8 +148,10 @@ describe("spawn", function() assert_eq(line, lines[i]) i = i + 1 end - local sp = - spawn.Spawn:make({ "cat", "README.md" }, process_line) --[[@as Spawn]] + local sp = spawn.Spawn:make( + { "cat", "README.md" }, + { on_stdout = process_line } + ) --[[@as Spawn]] local content_splits = vim.split( content, lower_char, @@ -165,8 +179,10 @@ describe("spawn", function() assert_eq(line, lines[i]) i = i + 1 end - local sp = - spawn.Spawn:make({ "cat", "README.md" }, process_line) --[[@as Spawn]] + local sp = spawn.Spawn:make( + { "cat", "README.md" }, + { on_stdout = process_line } + ) --[[@as Spawn]] local content_splits = vim.split( content, upper_char, @@ -183,7 +199,10 @@ describe("spawn", function() end) end it("stderr", function() - local sp = spawn.Spawn:make({ "cat", "README.md" }, function() end) --[[@as Spawn]] + local sp = spawn.Spawn:make( + { "cat", "README.md" }, + { on_stdout = function() end } + ) --[[@as Spawn]] sp:_on_stderr(nil, nil) assert_true(sp.err_pipe:is_closing()) end) @@ -198,7 +217,10 @@ describe("spawn", function() i = i + 1 end - local sp = spawn.Spawn:make({ "cat", "README.md" }, process_line) --[[@as Spawn]] + local sp = spawn.Spawn:make( + { "cat", "README.md" }, + { on_stdout = process_line } + ) --[[@as Spawn]] sp:run() end) it("iterate on lua/gitlinker.lua", function() @@ -212,14 +234,16 @@ describe("spawn", function() i = i + 1 end - local sp = - spawn.Spawn:make({ "cat", "lua/gitlinker.lua" }, process_line) --[[@as Spawn]] + local sp = spawn.Spawn:make( + { "cat", "lua/gitlinker.lua" }, + { on_stdout = process_line } + ) --[[@as Spawn]] sp:run() end) it("close handle", function() local sp = spawn.Spawn:make( { "cat", "lua/gitlinker.lua" }, - function() end + { on_stdout = function() end } ) --[[@as Spawn]] sp:run() assert_true(sp.process_handle ~= nil)