diff --git a/lua/plenary/job.lua b/lua/plenary/job.lua index 7f549719..3dd1a2cd 100644 --- a/lua/plenary/job.lua +++ b/lua/plenary/job.lua @@ -4,6 +4,10 @@ local compat = require "plenary.compat" local F = require "plenary.functional" +--qqq +local U = require "plenary.utils" +--!qqq + ---@class Job ---@field command string Command to run ---@field args? string[] List of arguments to pass @@ -64,8 +68,24 @@ end local function expand(path) if vim.in_fast_event() then + --qqq + if U.is_msys2 then + path = U.posix_to_windows(path) + -- Experiments with "{}" escaping. + --path = U.posix_to_windows(vim.fn.expand(vim.fn.escape(path, "{}[]$"), true)) + end return assert(uv.fs_realpath(path), string.format("Path must be valid: %s", path)) + --!qqq + --return assert(uv.fs_realpath(path), string.format("Path must be valid: %s", path)) else + --qqq + if U.is_msys2 then + path = U.posix_to_windows(vim.fn.expand(vim.fn.escape(path, "[]$"), true)) + --path = U.posix_to_windows(vim.fn.expand(vim.fn.escape(path, "{}[]$"), true)) + --path = U.posix_to_windows(vim.fn.expand(path), true) + return path + end + --!qqq -- TODO: Probably want to check that this is valid here... otherwise that's weird. return vim.fn.expand(vim.fn.escape(path, "[]$"), true) end @@ -112,6 +132,18 @@ function Job:new(o) obj.command = command obj.args = args + --qqq + -- This is so annoying that path can be in posix style even here. + --vim.notify("Job:new(): " .. vim.inspect(obj), vim.log.levels.ERROR) + if U.is_msys2 and o.cwd then + -- "git_apply_stash" from Telescope does not work here possibly due to nil "o.cwd". + -- NVM. This was caused by curly braces expansion in stash@{0}. + --if not o.cwd then + -- o.cwd = vim.loop.cwd() + --end + o.cwd = U.posix_to_windows(o.cwd) + end + --!qqq obj._raw_cwd = o.cwd if o.env then if type(o.env) ~= "table" then @@ -269,6 +301,12 @@ function Job:_create_uv_options() options.stdio = { self.stdin, self.stdout, self.stderr } if self._raw_cwd then + --qqq + if U.is_msys2 then + --vim.notify("Job:_create_uv_options(): " .. vim.inspect(self), vim.log.levels.ERROR) + self._raw_cwd = U.posix_to_windows(self._raw_cwd) + end + --!qqq options.cwd = expand(self._raw_cwd) end if self.env then @@ -400,6 +438,15 @@ function Job:_execute() self:_user_on_start() end + --qqq + -- Path can be in posix style even here + -- UPD. Making the path transformation in Job:new() solves "nvim ."->":Telescope git_commits" + --if U.is_msys2 then + -- vim.notify("Job:_execute(): " .. vim.inspect(self) .. ", " .. vim.inspect(options), vim.log.levels.ERROR) + -- self._raw_cwd = U.posix_to_windows(self._raw_cwd) + --end + --!qqq + self.handle, self.pid = uv.spawn(options.command, options, shutdown_factory(self, options)) if not self.handle then diff --git a/lua/plenary/path.lua b/lua/plenary/path.lua index 0865f2e3..e9f05c21 100644 --- a/lua/plenary/path.lua +++ b/lua/plenary/path.lua @@ -8,6 +8,10 @@ local uv = vim.loop local F = require "plenary.functional" +--qqq +local U = require "plenary.utils" +--!qqq + local S_IF = { -- S_IFDIR = 0o040000 # directory DIR = 0x4000, @@ -16,14 +20,29 @@ local S_IF = { } local path = {} -path.home = vim.loop.os_homedir() +--qqq +-- vim.loop.os_homedir() ignores HOME env var in msys2 +if U.is_msys2 then + path.home = U.posix_to_windows(vim.fn.expand("~")) +else + path.home = vim.loop.os_homedir() +end +--!qqq +--path.home = vim.loop.os_homedir() path.sep = (function() if jit then local os = string.lower(jit.os) - if os ~= "windows" then + if os ~= "windows" and not U.is_msys2 then return "/" else + --qqq + --vim.notify("os: " .. vim.inspect(os) .. ", U.is_msys: " .. vim.inspect(U.is_msys2), vim.log.levels.ERROR) + -- msys2 works fine with forward slashes as well as cmd.exe/powershell.exe + -- but not UNC paths if the paths will ever be supported. + -- Maybe returning "/" as path separator can break something in existing logic. + --return "/" + --!qqq return "\\" end else @@ -39,7 +58,17 @@ path.root = (function() else return function(base) base = base or vim.loop.cwd() - return base:sub(1, 1) .. ":\\" + --qqq + if U.is_msys2 then + base = U.posix_to_windows(base) + --if not base:find("^[A-Za-z]:" .. path.sep) then + -- vim.notify("path.root IIFE: base has incorrect path style for msys2!", vim.log.levels.ERROR) + --end + end + return base:sub(1, 1) .. ":" .. path.sep + --return base:sub(1, 3) + --!qqq + --return base:sub(1, 1) .. ":\\" end end end)() @@ -55,8 +84,14 @@ local concat_paths = function(...) end local function is_root(pathname) - if path.sep == "\\" then - return string.match(pathname, "^[A-Z]:\\?$") + if path.sep == "\\" or U.is_msys2 then + --qqq + if U.is_msys2 then + pathname = U.posix_to_windows(pathname) + end + return string.match(pathname, "^[A-Za-z]:[\\/]?$") + --!qqq + --return string.match(pathname, "^[A-Z]:\\?$") end return pathname == "/" end @@ -77,7 +112,12 @@ local is_uri = function(filename) end local is_absolute = function(filename, sep) - if sep == "\\" then + if sep == "\\" or U.is_msys2 then + --qqq + if U.is_msys2 then + filename = U.posix_to_windows(filename) + end + --!qqq return string.match(filename, "^[%a]:[\\/].*$") ~= nil end return string.sub(filename, 1, 1) == sep @@ -98,12 +138,22 @@ local function _normalize_path(filename, cwd) local has = string.find(filename, path.sep .. "..", 1, true) or string.find(filename, ".." .. path.sep, 1, true) + --qqq + --vim.notify("_normalize_path: has = " .. vim.inspect(has), vim.log.levels.WARN) + --!qqq + if has then local is_abs = is_absolute(filename, path.sep) + --qqq + --vim.notify("_normalize_path: is_abs = " .. vim.inspect(is_abs), vim.log.levels.WARN) + --!qqq local split_without_disk_name = function(filename_local) local parts = _split_by_separator(filename_local) -- Remove disk name part on Windows - if path.sep == "\\" and is_abs then + if is_abs and (path.sep == "\\" or U.is_msys2) then + --qqq + --vim.notify("_normalize_path: parts = " .. vim.inspect(parts), vim.log.levels.WARN) + --!qqq table.remove(parts, 1) end return parts @@ -137,9 +187,19 @@ local function _normalize_path(filename, cwd) out_file = prefix .. table.concat(parts, path.sep) end + --qqq + if U.is_msys2 then + out_file = U.posix_to_windows(out_file) + end + --!qqq + return out_file end +--qqq +--vim.notify("_normalize_path: call returned " .. vim.inspect(_normalize_path("C:\\msys64\\home\\..\\123", "C:\\msys64\\123")), vim.log.levels.WARN) +--!qqq + local clean = function(pathname) if is_uri(pathname) then return pathname @@ -182,12 +242,22 @@ Path.__index = function(t, k) if k == "_cwd" then local cwd = uv.fs_realpath "." + --qqq + if U.is_msys2 then + cwd = U.posix_to_windows(cwd) + end + --!qqq t._cwd = cwd return cwd end if k == "_absolute" then local absolute = uv.fs_realpath(t.filename) + --qqq + if U.is_msys2 then + absolute = U.posix_to_windows(absolute) + end + --!qqq t._absolute = absolute return absolute end @@ -234,6 +304,13 @@ function Path:new(...) -- If we already have a Path, it's fine. -- Just return it if Path.is_path(path_input) then + --qqq + -- Maybe we should fix fields with paths here as well + --if U.is_msys2 then + -- vim.notify("Path:new(...): returning already defined Path object " .. vim.inspect(path_input), vim.log.levels.ERROR) + -- path_input.filename = U.posix_to_windows(path_input.filename) + --end + --!qqq return path_input end @@ -259,8 +336,18 @@ function Path:new(...) end path_string = table.concat(path_objs, sep) + --qqq + if U.is_msys2 then + path_string = U.posix_to_windows(path_string) + end + --!qqq else assert(type(path_input) == "string", vim.inspect(path_input)) + --qqq + if U.is_msys2 then + path_input = U.posix_to_windows(path_input) + end + --!qqq path_string = path_input end @@ -319,7 +406,15 @@ function Path:expand() -- TODO support windows local expanded if string.find(self.filename, "~") then - expanded = string.gsub(self.filename, "^~", vim.loop.os_homedir()) + --qqq + -- vim.loop.os_homedir() ignores HOME env variable in msys2 + if U.is_msys2 then + expanded = string.gsub(self.filename, "^~", vim.fn.expand("~")) + else + expanded = string.gsub(self.filename, "^~", vim.loop.os_homedir()) + end + --!qqq + --expanded = string.gsub(self.filename, "^~", vim.loop.os_homedir()) elseif string.find(self.filename, "^%.") then expanded = vim.loop.fs_realpath(self.filename) if expanded == nil then @@ -336,9 +431,22 @@ function Path:expand() else expanded = self.filename end + --qqq + if expanded and U.is_msys2 then + --vim.notify("Path:expand(): " .. "vim.inspect(expanded), vim.log.levels.ERROR") + expanded = U.posix_to_windows(expanded) + end + --!qqq return expanded and expanded or error "Path not valid" end +--qqq +-- Can be tough to achieve in msys2 for posix-style paths. +-- Like from C:\\msys64\\home\\User\\personal\\project\\src\\file.lua (rel. src\\file.lua) +-- to /home/User/personal/project/src/file.lua (rel. src/file.lua). +-- This will probably require back-and-forth path conversions. +-- But do we actually need to bother about that instead of leaving Windows-style rel. path? +--!qqq function Path:make_relative(cwd) if is_uri(self.filename) then return self.filename @@ -358,6 +466,12 @@ function Path:make_relative(cwd) end end + --qqq + if U.is_msys2 then + self.filename = U.posix_to_windows(self.filename) + end + --!qqq + return self.filename end @@ -435,7 +549,7 @@ local shorten = (function() return shorten_len(filename, 1) end - if jit and path.sep ~= "\\" then + if jit and path.sep ~= "\\" then --and not U.is_msys2 then local ffi = require "ffi" ffi.cdef [[ typedef unsigned char char_u; @@ -489,7 +603,7 @@ function Path:mkdir(opts) for _, dir in ipairs(dirs) do if dir ~= "" then local joined = concat_paths(processed, dir) - if processed == "" and self._sep == "\\" then + if processed == "" and (self._sep == "\\") then -- or U.is_msys2) then joined = dir end local stat = uv.fs_stat(joined) or {} diff --git a/lua/plenary/utils.lua b/lua/plenary/utils.lua new file mode 100644 index 00000000..c7ddfa6f --- /dev/null +++ b/lua/plenary/utils.lua @@ -0,0 +1,138 @@ +--qqq + +-- utils.lua +local M = {} + +M.is_msys2 = (function() + -- Run this once + local ok, result = pcall(vim.fn.system, "uname") + if ok and result then + if result:match("MINGW64_NT") or + result:match("MINGW32_NT") or + result:match("MSYS_NT") then + return true + end + end + return false +end)() + +-- How can we find msys2 installation +-- if it cannot be located from ENV vars? +-- Registry? +-- Or using vim.fn.expand("~") which expands to Windows style path? +M.msys2_root = M.is_msys2 and (function() + -- msys2 path in windows style + -- C:\\msys64 is a common location + return vim.fn.expand("~"):match("^.*msys64") +end)() or nil + +M.msys2_root_map = M.msys2_root and (function() + -- Direct root mapping + return { + ["/bin"] = M.msys2_root .. "\\bin", + ["/clang64"] = M.msys2_root .. "\\clang64", + ["/clangarm64"] = M.msys2_root .. "\\clangarm64", + ["/dev"] = M.msys2_root .. "\\dev", + ["/etc"] = M.msys2_root .. "\\etc", + ["/home"] = M.msys2_root .. "\\home", + ["/installerResources"] = M.msys2_root .. "\\installerResources", + ["/mingw32"] = M.msys2_root .. "\\mingw32", + ["/mingw64"] = M.msys2_root .. "\\mingw64", + ["/opt"] = M.msys2_root .. "\\opt", + ["/proc"] = M.msys2_root .. "\\proc", + ["/tmp"] = M.msys2_root .. "\\tmp", + ["/ucrt64"] = M.msys2_root .. "\\ucrt64", + ["/usr"] = M.msys2_root .. "\\usr", + ["/var"] = M.msys2_root .. "\\var" + } +end)() or nil + +-- msys2 to windows actually. +function M.posix_to_windows(posix_path) + -- Sanity checks + if not posix_path or not M.msys2_root or not #M.msys2_root then + return posix_path + end + + + local prefix_changed = false + + + -- For edgy-ephemeral cases when vim.fn.expand() eats posix-style path. + -- In that case we get backslashes everywhere which we don't need + -- when working with libuv which uses WinAPI under the hood. + -- E.g. vim.fn.expand("/home/User") gives "\home\User". + posix_path = posix_path:gsub("\\", "/") + + + -- Another one edgy-ephemeral case when we have only "/". + if not prefix_changed and #posix_path == 1 and posix_path:find("/") then + --vim.notify("Only '/' case: " .. posix_path, vim.log.levels.WARN) + ---@type string + posix_path = M.msys2_root + prefix_changed = true + end + + + -- Apply root folder mappings only if path starts with "/" and has at least 3 chars after + if not prefix_changed and posix_path:find("^/[A-Za-z][A-Za-z][A-Za-z]") then + for prefix, replacement in pairs(M.msys2_root_map) do + if posix_path:find("^" .. prefix) then + --vim.notify("msys2 root mapping case: " .. posix_path, vim.log.levels.WARN) + posix_path = posix_path:gsub("^" .. prefix, replacement) + prefix_changed = true + break + end + end + end + + + -- Drive letter paths /c/Users -> C:\\Users (+edge case on fast pane split in WezTerm /C:/Users -> C:\\Users). + -- It is possible to have only "/c" (w/o trailing "/") but not "/C:" (WezTerm internally trails it with "/"). + -- Idk if WezTerm paths behaviour leaks to msys2 actually cause even in regular cmd.exe/powershell.exe it uses + -- "/:/" notation internally for panes. + if not prefix_changed then + if #posix_path == 2 and posix_path:find("/[A-Za-z]") then + --vim.notify("Only '/[A-Za-z]' case: " .. posix_path, vim.log.levels.WARN) + posix_path = posix_path:gsub("^/([A-Za-z])", "%1:\\") + prefix_changed = true + elseif posix_path:find("/[A-Za-z]:?/") then + --vim.notify("'/[A-Za-z]:?/' case: " .. posix_path, vim.log.levels.WARN) + posix_path = posix_path:gsub("^/([A-Za-z]):?/", "%1:\\") + prefix_changed = true + else + -- The code path can be taken only in specific cases + -- like a custom folder under msys2 root (/abc). + --vim.notify("General mapping case for given path: " .. posix_path, vim.log.levels.WARN) + posix_path = posix_path:gsub("^/", M.msys2_root .. "\\") + prefix_changed = true + end + end + + -- Lets try to use posix-style paths for testing. + --posix_path = posix_path:gsub("^/([A-Za-z]):?([^0-9A-Za-z_-]?)", "/%1%2") + + + -- Replace remaining forward slashes with backslashes. + posix_path = posix_path:gsub("/", "\\") + + -- For bash.exe (nvim shell) it is better to use forward slashes + -- cause backslashes must to be escaped. Need to come up with something + -- cause nvim for windows (even clang64 binary) prefers windows-style paths (but works with forward slashes as well?). + -- UPD. "set shellslash" makes the trick by converting backslashes to forward slashes in shell invocations. + --posix_path = posix_path:gsub("\\", "/") + + + -- Drive letter to upper case + if posix_path:find("^[a-z]:") then + posix_path = posix_path:sub(1, 1):upper() .. posix_path:sub(2) + end + + + --vim.notify("posix_to_windows will return " .. posix_path, vim.log.levels.WARN) + + return posix_path +end + +return M +--!qqq