Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BUG: Can not open file insde App router with parentheses in its name. (Windows only) #1448

Open
3 tasks done
Ngdaz opened this issue Apr 25, 2024 · 15 comments · May be fixed by #1458
Open
3 tasks done

BUG: Can not open file insde App router with parentheses in its name. (Windows only) #1448

Ngdaz opened this issue Apr 25, 2024 · 15 comments · May be fixed by #1458
Assignees
Labels
bug Something isn't working working-on-it

Comments

@Ngdaz
Copy link

Ngdaz commented Apr 25, 2024

Did you check docs and existing issues?

  • I have read all the docs.
  • I have searched the existing issues.
  • I have searched the existing discussions.

Neovim Version (nvim -v)

0.9.5

Operating System / Version

Win 10

Describe the Bug

Can not open file insde App router with parentheses in its name. (Windows only)

Screenshots, Traceback

image

Steps to Reproduce

  1. Open the app router project
  2. Open page.tsx in side folder has () wrap

Expected Behavior

Can open the file

Your Configuration

-- DO NOT change the paths and don't remove the colorscheme
local root = vim.fn.fnamemodify("./.repro", ":p")

-- set stdpaths to use .repro
for _, name in ipairs({ "config", "data", "state", "cache" }) do
  vim.env[("XDG_%s_HOME"):format(name:upper())] = root .. "/" .. name
end

-- bootstrap lazy
local lazypath = root .. "/plugins/lazy.nvim"
if not vim.loop.fs_stat(lazypath) then
  vim.fn.system({ "git", "clone", "--filter=blob:none", "https://github.com/folke/lazy.nvim.git", lazypath, })
end
vim.opt.runtimepath:prepend(lazypath)

-- install plugins
local plugins = {
  "folke/tokyonight.nvim",
  -- add any other plugins here
}

local neotree_config = {
  "nvim-neo-tree/neo-tree.nvim",
  dependencies = { "MunifTanjim/nui.nvim", "nvim-tree/nvim-web-devicons", "nvim-lua/plenary.nvim" },
  cmd = { "Neotree" },
  keys = {
    { "<Leader>e", "<Cmd>Neotree<CR>" }, -- change or remove this line if relevant.
  },
  opts = {
    -- Your config here
    -- ...
  },
}

table.insert(plugins, neotree_config)
require("lazy").setup(plugins, {
  root = root .. "/plugins",
})

vim.cmd.colorscheme("tokyonight")
-- add anything else here
@Ngdaz Ngdaz added the bug Something isn't working label Apr 25, 2024
@pysan3
Copy link
Collaborator

pysan3 commented Apr 25, 2024

What is your neo-tree version?

@miversen33
Copy link
Collaborator

I don't think this is just parenthisis that is hurting you, notice that the entire path just falls over when it hits ] in your path.

I haven't looked at the path escaping code in quite a while, I know it has morphed since I last touched it. It likely needs a bit more robustness to handle some of that weirdness

@pysan3 pysan3 added the question Further information is requested label Apr 26, 2024
@Ngdaz
Copy link
Author

Ngdaz commented Apr 26, 2024

What is your neo-tree version?

I'm using the latest one 3.25

@Ngdaz
Copy link
Author

Ngdaz commented Apr 26, 2024

I don't think this is just parenthisis that is hurting you, notice that the entire path just falls over when it hits ] in your path.

I haven't looked at the path escaping code in quite a while, I know it has morphed since I last touched it. It likely needs a bit more robustness to handle some of that weirdness

I found that I have same issue with this #889

@pysan3 pysan3 removed the question Further information is requested label Apr 29, 2024
@pysan3
Copy link
Collaborator

pysan3 commented May 1, 2024

Pinging @bwpge

@pysan3 pysan3 added the help wanted Extra attention is needed label May 1, 2024
@bwpge
Copy link
Contributor

bwpge commented May 1, 2024

Ah Windows paths, the gift that keeps on giving 😂

@pysan3 I will take a look in the morning

@bwpge
Copy link
Contributor

bwpge commented May 1, 2024

For anyone who wants to take a look in the meantime, my best initial guess is we'll probably just have to add braces to this escape logic (totally guessing though):

for _, c in ipairs({ "&", "(", ")", ";", "^", "`" }) do

@pysan3
Copy link
Collaborator

pysan3 commented May 1, 2024

According to my comment #1353 (comment) [ should work with the builtin vim commands (eg :edit). It may be that the same rule does not apply to a lua function we call somewhere?

I haven't been able to test so I might be completely wrong and forgive me I can't do anything until this weekends at the earliest :'(

@bwpge
Copy link
Contributor

bwpge commented May 1, 2024

Reproduced the issue on my machine, looks like somewhere the parenthesis is not getting escaped (trying to open baz.txt or other.txt):

[Neo-tree ERROR] X:\test\[foo](bar) :  ENOENT: no such file or directory: X:\test\[foo](bar)
[Neo-tree ERROR] X:\test\[foo](bar)\qux :  ENOENT: no such file or directory: X:\test\[foo](bar)\qux

Using the following test directory:

test
├── [foo]
│   └── (bar)
│       ├── baz.txt
│       └── qux
│           └── other.txt
├── stylua.toml
└── test.lua

Edit: What I find odd though is the escape_path_for_cmd function is correctly escaping the paren (threw in a debug message before returning):

DEBUG: using escaped path `X:\test\[foo]\\(bar)\qux\other.txt`

As a funny side note, nice to know that even established tools like zoxide have the same struggles with Windows paths. Wasn't able to change directory to make the test files...

@bwpge
Copy link
Contributor

bwpge commented May 1, 2024

After doing some testing, it seems specifically the [foo] (square bracket) segment followed by (bar) (parenthesized) segment causes this super odd problem. I can't even directly open the file by typing :e X:\test\[foo]\\(bar)\baz.txt, it's almost like Neovim loses the path separator somewhere else in the pipeline. But this doesn't happen with something like X:\test\\(foo)\\(bar)\baz.txt.

Test files:

test
├── (foo)
│   ├── (bar)
│   │   └── baz.txt       <- ok
│   └── foo.txt           <- ok
├── [foo]
│   ├── (bar)
│   │   ├── baz.txt       <- ERROR
│   │   └── qux
│   │       └── other.txt <- ERROR
│   ├── [bar]
│   │   └── baz.txt       <- ok
│   └── some.txt          <- ok

I tried using 3 backslashes to open those files that errored and that actually worked, e.g., :e X:\test\[foo]\\\(bar)\qux\other.txt. I'll try some more testing this afternoon to figure out exactly when this problem happens (is it just [...]\(...), or do other weird combinations cause the same effect?).

Just to restate, this seems to be a really specific Windows path problem with Neovim (not Neo-tree) that doesn't follow any of the other established rules about escaping, since I can't even manually open those types of paths without using a triple backslash (or using a / separator, which unfortunately breaks other plugins when we do that).

@pysan3
Copy link
Collaborator

pysan3 commented May 1, 2024

Thank you so much for your deep dive @bwpge !

With my pathlib.nvim I force all path separators to be forward regardless of the OS when passing to a vim command (eg :edit).

https://github.com/pysan3/pathlib.nvim/blob/main/lua/pathlib/base.lua#L1209-L1220
(self._raw_paths contains the elements of the path: { "folder", "foo.txt" } -> "folder/foo.txt").

Do you think we should rather take this route instead? This won't raise any errors with escaping.

@bwpge
Copy link
Contributor

bwpge commented May 1, 2024

The / separator actually goes back to my original contribution to Neo-tree, unfortunately this causes problems with other plugins (see: nvim-lualine/lualine.nvim#1183 for example).

I know it's not Neo-tree's responsibility to make every other plugin in Neovim happy, but it introduces a really subtle change that is really difficult to pinpoint for users. Essentially, a lot of plugins do their own path parsing and may rely on the platform separator (e.g., package.config:sub(1, 1)), so if you open a file (e.g., :e) using / AND an absolute path (such as X:/foo/bar.txt) it changes the buffer name on Windows. So if a plugin is trying to do some path stuff with the buffer name, it may trip on itself if we use a / instead of \.

Again, not really a Neo-tree problem per se, but it breaks other popular plugins. I've also run into some weird buffer behavior with LSP refactors like renaming a symbol, where it opens a buffer with the same name but different path separators.

@bwpge
Copy link
Contributor

bwpge commented May 1, 2024

On the bright side, this is what I'm testing now and seems to be working for weird paths, including something like X:\[foo]\(bar)\&baz.txt (I found (bar)\\&baz.txt has the same problem with dropping the path separator):

M.escape_path_for_cmd = function(path)
  local escaped_path = vim.fn.fnameescape(path)
  if M.is_windows then
    local evil = "[%(%)%^&;`]"
    escaped_path = escaped_path:gsub("\\" .. evil, "\\%1")
    escaped_path = escaped_path:gsub("(" .. evil .. ")\\\\(" .. evil .. ")", "%1\\\\\\%2")
    -- special case for #1448
    escaped_path = escaped_path:gsub("%]\\\\%(", "]\\\\\\(")
    vim.notify("DEBUG: using escaped path `" .. escaped_path .. "`")
  end
  return escaped_path
end

@bwpge
Copy link
Contributor

bwpge commented May 2, 2024

Just wanted to check in, haven't forgotten about this issue just had a crazy schedule this week. Hope to get a PR sent in tonight or tomorrow night.

@bwpge
Copy link
Contributor

bwpge commented May 4, 2024

From some more testing, I've come to the conclusion that a square bracket or backtick in the path makes other problematic punctuation (the ones we discovered like &, ^, etc.) require an extra escape character.

I'm using the following script to verify logic:

local punc = { "()", "[]", "^", "&", ";", "`" }

-- testing implementation for this function
local function escape_path_for_cmd(path)
  local escaped_path = vim.fn.fnameescape(path)
  if true then
    local need_extra_esc = path:find("[%[%]`]")
    local esc = need_extra_esc and "\\\\" or "\\"
    escaped_path = escaped_path:gsub("\\[%(%)%^&;]", esc .. "%1")
    -- backticks get escaped by fnameescape and always need an extra
    -- separator if they are the first character in a path segment
    escaped_path = escaped_path:gsub("\\\\`", "\\%1")
  end
  return escaped_path
end

-- makes a value like `[foo]` from part='foo' and p='[]'
local function make_part(part, p)
  local open = p:sub(1, 1)
  local close = p:sub(2, 2)
  if close == "" then
    close = open
  end
  return (open .. part .. close)
end

-- create nested directories with leading/trailing punctuation
local dirs = {}
for _, p1 in ipairs(punc) do
  for _, p2 in ipairs(punc) do
    local part1 = make_part("foo", p1)
    local part2 = make_part("bar", p2)
    local path = string.format(".\\tests\\%s\\%s", part1, part2)
    vim.fn.mkdir(path, "p")

    table.insert(dirs, path)
  end
end

-- try to write files with weird path
for _, d in ipairs(dirs) do
  local p = escape_path_for_cmd(d .. "\\bar.txt")
  local ok, err = pcall(vim.cmd, "silent w " .. p)
  if not ok then
    print("fail:", p, "->", err)
  else
    print("wrote:", p)
  end
end

This solution above solves the problem for all paths tested:

wrote: .\tests\\(foo)\\(bar)\bar.txt
wrote: .\tests\\\(foo)\[bar]\bar.txt
wrote: .\tests\\(foo)\\^bar^\bar.txt
wrote: .\tests\\(foo)\\&bar&\bar.txt
wrote: .\tests\\(foo)\\;bar;\bar.txt
wrote: .\tests\\\(foo)\\\`bar\`\bar.txt
wrote: .\tests\[foo]\\\(bar)\bar.txt
wrote: .\tests\[foo]\[bar]\bar.txt
wrote: .\tests\[foo]\\\^bar^\bar.txt
wrote: .\tests\[foo]\\\&bar&\bar.txt
wrote: .\tests\[foo]\\\;bar;\bar.txt
wrote: .\tests\[foo]\\\`bar\`\bar.txt
wrote: .\tests\\^foo^\\(bar)\bar.txt
wrote: .\tests\\\^foo^\[bar]\bar.txt
wrote: .\tests\\^foo^\\^bar^\bar.txt
wrote: .\tests\\^foo^\\&bar&\bar.txt
wrote: .\tests\\^foo^\\;bar;\bar.txt
wrote: .\tests\\\^foo^\\\`bar\`\bar.txt
wrote: .\tests\\&foo&\\(bar)\bar.txt
wrote: .\tests\\\&foo&\[bar]\bar.txt
wrote: .\tests\\&foo&\\^bar^\bar.txt
wrote: .\tests\\&foo&\\&bar&\bar.txt
wrote: .\tests\\&foo&\\;bar;\bar.txt
wrote: .\tests\\\&foo&\\\`bar\`\bar.txt
wrote: .\tests\\;foo;\\(bar)\bar.txt
wrote: .\tests\\\;foo;\[bar]\bar.txt
wrote: .\tests\\;foo;\\^bar^\bar.txt
wrote: .\tests\\;foo;\\&bar&\bar.txt
wrote: .\tests\\;foo;\\;bar;\bar.txt
wrote: .\tests\\\;foo;\\\`bar\`\bar.txt
wrote: .\tests\\\`foo\`\\\(bar)\bar.txt
wrote: .\tests\\\`foo\`\[bar]\bar.txt
wrote: .\tests\\\`foo\`\\\^bar^\bar.txt
wrote: .\tests\\\`foo\`\\\&bar&\bar.txt
wrote: .\tests\\\`foo\`\\\;bar;\bar.txt
wrote: .\tests\\\`foo\`\\\`bar\`\bar.txt

I have some things to take care of today but later I'm going to test all the same punctuation above on the beginning filename (I found that this creates some additional problems, so I want to test that separately).

@bwpge bwpge linked a pull request May 4, 2024 that will close this issue
@pysan3 pysan3 added working-on-it and removed help wanted Extra attention is needed labels May 6, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working working-on-it
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants