nvim-tree / nvim-tree.lua

A file explorer tree for neovim written in lua
Other
7.12k stars 609 forks source link

Problems with Braces in Pathnames under Windows #1848

Closed ghost closed 1 year ago

ghost commented 1 year ago

Description

I have a SvelteKit project that I'm developing on a Windows machine.

In SvelteKit, you can define routing groups (invisible parent folders, for lag of a better term) by putting the folder name into braces like so (publiconly)

Screenshot 2022-12-22 070222

But there seems to be some sort of escaping issue with the pathnames, since Windows uses \ instead of / as directory separator. So the \ before the ( is removed. I guess to escape the (.

Screenshot 2022-12-22 070356

Neovim version

NVIM v0.9.0-dev-530+gde90a8bfe
Build type: RelWithDebInfo
LuaJIT 2.1.0-beta3
Übersetzt von runneradmin@fv-az368-246

Features: -acl +iconv +tui
See ":help feature-compile"

          System-vimrc-Datei: "$VIM\sysinit.vim"
     Voreinstellung für $VIM: "C:/Program Files (x86)/nvim/share/nvim"

Run :checkhealth for more info

Operating system and version

Windows 10 Update I should clearify, that I'm NOT running WSL.

nvim-tree version

'e14c289'

Minimal config

vim.cmd [[set runtimepath=$VIMRUNTIME]]
vim.cmd [[set packpath=/tmp/nvt-min/site]]
local package_root = "/tmp/nvt-min/site/pack"
local install_path = package_root .. "/packer/start/packer.nvim"
local function load_plugins()
  require("packer").startup {
    {
      "wbthomason/packer.nvim",
      "nvim-tree/nvim-tree.lua",
      "nvim-tree/nvim-web-devicons",
      -- ADD PLUGINS THAT ARE _NECESSARY_ FOR REPRODUCING THE ISSUE
    },
    config = {
      package_root = package_root,
      compile_path = install_path .. "/plugin/packer_compiled.lua",
      display = { non_interactive = true },
    },
  }
end
if vim.fn.isdirectory(install_path) == 0 then
  print "Installing nvim-tree and dependencies."
  vim.fn.system { "git", "clone", "--depth=1", "https://github.com/wbthomason/packer.nvim", install_path }
end
load_plugins()
require("packer").sync()
vim.cmd [[autocmd User PackerComplete ++once echo "Ready!" | lua setup()]]
vim.opt.termguicolors = true
vim.opt.cursorline = true

-- MODIFY NVIM-TREE SETTINGS THAT ARE _NECESSARY_ FOR REPRODUCING THE ISSUE
_G.setup = function()
  require("nvim-tree").setup {}
end

Steps to reproduce

In Windows

The content of the opened file will be empty and before the ( of (test) will be a \ missing.

Expected behavior

the path should look something like this parentFolder\(test)\test.txt

Actual behavior

the backslash before (test) is removed (I susspect some sort of escaping issue), leading to the new name of the file being parentFolder(test)\test.txt Since the folder parentFolder(test) does not exist, Nvim opens a new file instead of the choosen one.

alex-courtis commented 1 year ago

I don't have expertise in or access to windows, so we will need to run some experiments.

:lua print(vim.fn.fnamemodify("C:\\src\\routes(publiconly)", ":p")) :lua print(vim.fn.fnamemodify("routes(publiconly)", ":p")) while in src directory

Please enable logging and navigate inside the problem folder.

    log = {
      enable = true,
      truncate = true,
      types = {
        git = true,
        profile = true,
        watcher = true,
      },
    },
ghost commented 1 year ago

Thanks for the reply, unfortunatly logging under windows does not seem to work. There is no XDG_CACHE_HOME EnvVar. If I define one (via set XDG_CACHE_HOME=/tmp) it creates an nvim folder in it, but no log files.

The Nvim's output for both of your commands is: Screenshot 2022-12-23 051559

Update: Please ignore the following, while it works, it breaks a lot of other stuff. I`ve looked around the code of nvim-tree.lua and may have found a solution.

In lua/nvim-tree/explorer/node-builders.lua In the function M.file(parent, absolute_path, name) Line 52 replacing absolute_path = absolute_path, with absolute_path = absolute_path:gsub("%\\", '/'), will convert all \ to /. (Windows can handle / as directory separators, they just don't replace the sepearator by default, because Micro$oft and legacy code I guess.)

Could you please check, if this would interfere with any functionality under linux? If it is working, then the fix may needs to be applyed to M.link too.

alex-courtis commented 1 year ago

Thanks for the reply, unfortunatly logging under windows does not seem to work. There is no XDG_CACHE_HOME EnvVar. If I define one (via set XDG_CACHE_HOME=/tmp) it creates an nvim folder in it, but no log files.

Another powershell user encountered that problem and added to the wiki

I`ve looked around the code of nvim-tree.lua and may have found a solution.

Please create a PR, see wiki. The functionality should run only when windows feature flags are set: :help has(. There are several examples in the codebase.

This code should probably only be called under powershell, not WSL, so you'll need to check for has("wsl"). Please test WSL too.

ghost commented 1 year ago

Hmm just checked something. Unfortunately, the fix breaks some other functionality. Like, refreshing the view now makes a lot of files disappear (visually). 😱 I'm not going to pretend that I know what I'm doing. My LUA is quite rusty (like 6 years worth of rust).

The initial issue with the missing \ still exists, but I don't think I'll be able to help. Sorry 😞

ljani commented 1 year ago

I'm having the same problem on Windows 11.

Here's the log with all = true, but it is emptyish:

[2022-12-23 15:29:23] [config] default config + user
{
  actions = {
    change_dir = {
      enable = true,
      global = false,
      restrict_above_cwd = false
    },
    expand_all = {
      max_folder_discovery = 300
    },
    open_file = {
      quit_on_open = false,
      resize_window = true,
      window_picker = {
        chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890",
        enable = true,
        exclude = {
          buftype = { "nofile", "terminal", "help" },
          filetype = { "notify", "packer", "qf", "diff", "fugitive", "fugitiveblame" }
        }
      }
    },
    remove_file = {
      close_window = true
    },
    use_system_clipboard = true
  },
  auto_reload_on_write = true,
  create_in_closed_folder = false,
  diagnostics = {
    enable = false,
    icons = {
      error = "",
      hint = "",
      info = "",
      warning = ""
    },
    show_on_dirs = false
  },
  disable_netrw = false,
  filesystem_watchers = {
    enable = false,
    interval = 100
  },
  filters = {
    custom = {},
    dotfiles = false,
    exclude = {}
  },
  git = {
    enable = true,
    ignore = true,
    timeout = 400
  },
  hijack_cursor = false,
  hijack_directories = {
    auto_open = true,
    enable = true
  },
  hijack_netrw = true,
  hijack_unnamed_buffer_when_opening = false,
  ignore_buffer_on_setup = false,
  ignore_ft_on_setup = {},
  live_filter = {
    always_show_folders = true,
    prefix = "[FILTER]: "
  },
  log = {
    enable = true,
    truncate = true,
    types = {
      all = true,
      config = false,
      copy_paste = false,
      diagnostics = false,
      git = false,
      profile = false,
      watcher = false
    }
  },
  open_on_setup = false,
  open_on_setup_file = false,
  open_on_tab = false,
  prefer_startup_root = false,
  reload_on_bufenter = false,
  renderer = {
    add_trailing = false,
    full_name = false,
    group_empty = false,
    highlight_git = false,
    highlight_opened_files = "none",
    icons = {
      git_placement = "before",
      glyphs = {
        default = "",
        folder = {
          arrow_closed = "",
          arrow_open = "",
          default = "",
          empty = "",
          empty_open = "",
          open = "",
          symlink = "",
          symlink_open = ""
        },
        git = {
          deleted = "",
          ignored = "◌",
          renamed = "➜",
          staged = "✓",
          unmerged = "",
          unstaged = "✗",
          untracked = "★"
        },
        symlink = ""
      },
      padding = " ",
      show = {
        file = true,
        folder = true,
        folder_arrow = true,
        git = true
      },
      symlink_arrow = " ➛ ",
      webdev_colors = true
    },
    indent_markers = {
      enable = false,
      icons = {
        corner = "└ ",
        edge = "│ ",
        item = "│ ",
        none = "  "
      }
    },
    root_folder_modifier = ":~",
    special_files = { "Cargo.toml", "Makefile", "README.md", "readme.md" }
  },
  respect_buf_cwd = false,
  root_dirs = {},
  sort_by = "name",
  system_open = {
    args = {},
    cmd = ""
  },
  trash = {
    cmd = "gio trash",
    require_confirm = true
  },
  update_cwd = false,
  update_focused_file = {
    enable = false,
    ignore_list = {},
    update_cwd = false,
    update_root = false
  },
  view = {
    adaptive_size = false,
    centralize_selection = false,
    height = 30,
    hide_root_folder = false,
    mappings = {
      custom_only = false,
      list = {}
    },
    number = false,
    preserve_window_proportions = false,
    relativenumber = false,
    side = "left",
    signcolumn = "yes",
    width = 30
  }
}
[2022-12-23 15:29:23] [config] active mappings
{ {
    action = "edit",
    desc = "open a file or folder; root will cd to the above directory",
    key = { "<CR>", "o", "<2-LeftMouse>" }
  }, {
    action = "edit_in_place",
    desc = "edit the file in place, effectively replacing the tree explorer",
    key = "<C-e>"
  }, {
    action = "edit_no_picker",
    desc = "same as (edit) with no window picker",
    key = "O"
  }, {
    action = "cd",
    desc = "cd in the directory under the cursor",
    key = { "<C-]>", "<2-RightMouse>" }
  }, {
    action = "vsplit",
    desc = "open the file in a vertical split",
    key = "<C-v>"
  }, {
    action = "split",
    desc = "open the file in a horizontal split",
    key = "<C-x>"
  }, {
    action = "tabnew",
    desc = "open the file in a new tab",
    key = "<C-t>"
  }, {
    action = "prev_sibling",
    desc = "navigate to the previous sibling of current file/directory",
    key = "<"
  }, {
    action = "next_sibling",
    desc = "navigate to the next sibling of current file/directory",
    key = ">"
  }, {
    action = "parent_node",
    desc = "move cursor to the parent directory",
    key = "P"
  }, {
    action = "close_node",
    desc = "close current opened directory or parent",
    key = "<BS>"
  }, {
    action = "preview",
    desc = "open the file as a preview (keeps the cursor in the tree)",
    key = "<Tab>"
  }, {
    action = "first_sibling",
    desc = "navigate to the first sibling of current file/directory",
    key = "K"
  }, {
    action = "last_sibling",
    desc = "navigate to the last sibling of current file/directory",
    key = "J"
  }, {
    action = "toggle_git_ignored",
    desc = "toggle visibility of files/folders hidden via |git.ignore| option",
    key = "I"
  }, {
    action = "toggle_dotfiles",
    desc = "toggle visibility of dotfiles via |filters.dotfiles| option",
    key = "H"
  }, {
    action = "toggle_custom",
    desc = "toggle visibility of files/folders hidden via |filters.custom| option",
    key = "U"
  }, {
    action = "refresh",
    desc = "refresh the tree",
    key = "R"
  }, {
    action = "create",
    desc = "add a file; leaving a trailing `/` will add a directory",
    key = "a"
  }, {
    action = "remove",
    desc = "delete a file (will prompt for confirmation)",
    key = "d"
  }, {
    action = "trash",
    desc = "trash a file via |trash| option",
    key = "D"
  }, {
    action = "rename",
    desc = "rename a file",
    key = "r"
  }, {
    action = "full_rename",
    desc = "rename a file and omit the filename on input",
    key = "<C-r>"
  }, {
    action = "cut",
    desc = "add/remove file/directory to cut clipboard",
    key = "x"
  }, {
    action = "copy",
    desc = "add/remove file/directory to copy clipboard",
    key = "c"
  }, {
    action = "paste",
    desc = "paste from clipboard; cut clipboard has precedence over copy; will prompt for confirmation",
    key = "p"
  }, {
    action = "copy_name",
    desc = "copy name to system clipboard",
    key = "y"
  }, {
    action = "copy_path",
    desc = "copy relative path to system clipboard",
    key = "Y"
  }, {
    action = "copy_absolute_path",
    desc = "copy absolute path to system clipboard",
    key = "gy"
  }, {
    action = "prev_git_item",
    desc = "go to next git item",
    key = "[c"
  }, {
    action = "next_git_item",
    desc = "go to prev git item",
    key = "]c"
  }, {
    action = "dir_up",
    desc = "navigate up to the parent directory of the current file/directory",
    key = "-"
  }, {
    action = "system_open",
    desc = "open a file with default system application or a folder with default file manager, using |system_open| option",
    key = "s"
  }, {
    action = "live_filter",
    desc = "live filter nodes dynamically based on regex matching.",
    key = "f"
  }, {
    action = "clear_live_filter",
    desc = "clear live filter",
    key = "F"
  }, {
    action = "close",
    desc = "close tree window",
    key = "q"
  }, {
    action = "collapse_all",
    desc = "collapse the whole tree",
    key = "W"
  }, {
    action = "expand_all",
    desc = "expand the whole tree, stopping after expanding |actions.expand_all.max_folder_discovery| folders; this might hang neovim for a while if running on a big folder",
    key = "E"
  }, {
    action = "search_node",
    desc = "prompt the user to enter a path and then expands the tree to match the path",
    key = "S"
  }, {
    action = "run_file_command",
    desc = "enter vim command mode with the file the cursor is on",
    key = "."
  }, {
    action = "toggle_file_info",
    desc = "toggle a popup with file infos about the file under the cursor",
    key = "<C-k>"
  }, {
    action = "toggle_help",
    desc = "toggle help",
    key = "g?"
  } }
[2022-12-23 15:29:23] [profile] START git job C:\devel\my-site
[2022-12-23 15:29:23] [git] running job with timeout 400ms
[2022-12-23 15:29:23] [git] git --no-optional-locks status --porcelain=v1 --ignored=matching -u
 M package-lock.json
 M package.json
!! .svelte-kit/
!! node_modules/
[2022-12-23 15:29:23] [git] done
[2022-12-23 15:29:23] [profile] END   git job C:\devel\my-site  42ms
[2022-12-23 15:29:23] [git] job success
[2022-12-23 15:29:23] [profile] START draw
[2022-12-23 15:29:23] [profile] END   draw  0ms
[2022-12-23 15:29:23] [profile] START draw
[2022-12-23 15:29:23] [profile] END   draw  0ms
[2022-12-23 15:29:25] [profile] START draw
[2022-12-23 15:29:25] [profile] END   draw  0ms
[2022-12-23 15:29:26] [profile] START draw
[2022-12-23 15:29:26] [profile] END   draw  0ms
[2022-12-23 15:29:26] [profile] START draw
[2022-12-23 15:29:26] [profile] END   draw  1ms

EDIT: to be precise, my issue is that I cannot open any files under eg. (app), but I guess it is for the same reason. EDIT2: I think the problem is the cmd here, because the path should be escaped if it is evaluated that way. Ie. :vertical split src\routes\\(app)\test.txt where as it is currently :vertical split src\routes\(app)\test.txt. Running those commands manually in nvim, the former works correctly while the latter does not. EDIT3: Oh, there is escape, but it only escapes for path, not for evaluation call?

ljani commented 1 year ago

Changing this line to the following fixes the issue:

local fname = vim.fn.escape(vim.fn.fnameescape(filename), "\\")

However, it is probably wise to see how other plugins have solved the issue?

ghost commented 1 year ago

That is the right track. The issue seems to be with the (. A similar thing happens with the search and replace command. normal ( are seen as characters while \( serves some special function in the regular expression.

Nvim-tree passes the file back to vim in form of a :edit {fname} command. Until then everthing looks fine. My idea is, that Vim sees the \ in front of the ( and thus uses it as part of the command rather than the character.

The solution would be to escape the ( but only on windows. I've created a pull-request. Since the fix only happens in the final moment, before the command is passed back to vim, there should be zero chance of it influencing other functions this time.

Tested on Nvim-qt, Windows CMD and PowerShell. Pull Request

ljani commented 1 year ago

By the way, I think this applies to [ as well ([lang] matches any of the characters between the brackets unless escaped), and probably other characters too.

It's confusing indeed for a viml newbie like me 😄 I'm not sure if the escape character should be escaped or the wildcards, and thus I suggested checking other plugins.

A better fix would be probably use something other than vim.cmd, which accepts a path as an argument, to avoid evaluating escape characters, but it's more work and I have no idea if that is possible.

Anyway, here are some test cases (literal paths):

gegoune commented 1 year ago

A better fix would be probably use something other than vim.cmd, which accepts a path as an argument, to avoid evaluating escape characters, but it's more work and I have no idea if that is possible.

That should be possible and not too difficult. As a matter of fact that should be preferred way as this is intention behind nvim_cmd and nvim_parse_cmd. Instead of trying to find all possible characters that need escaping we should look into whether it can be fixed by using aforementioned functions.

gegoune commented 1 year ago

Or maybe vim.loop.fs_realpath(fname)?

https://github.com/neovim/nvim-lspconfig/pull/2343

alex-courtis commented 1 year ago

Please don't interpret this question as trite or aggressive @DoodlingTurtle and @ljani

What is the use case for PowerShell over WSL or cygwin?

I do see several users using PowerShell for cmake: #1822 https://github.com/nvim-tree/nvim-tree.lua/issues/1606#issuecomment-1287671105 Is there some requirement of CMake for PowerShell? Is there some type of development environment that requires PowerShell?

As a Linux (prev macOS) user WSL seems the obvious choice: it is a POSIX environment with all the Linux tools available and will closely match your live service enviroment such as linux in docker.

Your insights would be very useful for our future understanding of how and why PowerShell is used. It is a mystery box right now.

ghost commented 1 year ago

@alex-courtis Your question makes senses. Don't worry. So here is my reasoning.

1.) I prefer Linux over windows too, but in my Job, I'm often 'provided' a PC for data security reasons. Those PCs mostly run Windows, because Management does not care what programmers want, and even the cheapest Admin knows how to handle Windows.

2.) WSL is a Virtual Machine, that works well but, like most things Micro$oft does, it is half-assed. For example. I can install Apache2, MySQL, NodeJS, etc. but then, to access them, I need to add some exception Rules to the firewall (done via Powershell). Otherwise, I can't access the Servers with the Browsers on the Windows side.

That alone is not a big deal, BUT. The IP for the WSL Machine changes after every restart, so, I would have to look inside WSL for its IP, then use Powershell to remove the old Firewall rules and add new ones with the new IP for every single Port on every restart. You can't give WSL a fixed IP. See what I mean by Micro$oft "half assessing" things? (Together with point 1. Imagine having to call the Admin to enter his password after every restart)

3.) In WSL, accessing/mounting USB Media is not easy. You also don't get access to shared/samba drives mounted in windows. (sure, you get access to all build in HDD-Partitions available, when WSL starts, but anything else requires extra work.)

4.)It is also not guaranteed, that all file changes inside WSL will make it over to the windows side. I had WSL crash once and lost progress on some files, while others were empty after the crash. That one may be an edge case, but I would not want to risk it ever again.

5.) The PCs I get to use are often cheap, underpowered office machines and WSL can get laggy on those since Windows is not only a huge performance hog, but now it also has to simulate an entirely different OS on top of that.

Sure I can run things like XAMPP and Node on Windows, to solve issues 2 and 3 but then WSL also needs its own Node Install, so that NVIM can run plugins like COC. So now I have to configure 2 different node installations, just so I can use WSL as a glorified IDE. Add to that, that you can start Windows Executables from within WSL and it all gets quite confusing.

alex-courtis commented 1 year ago

1.) I prefer Linux over windows too, but in my Job, I'm often 'provided' a PC for data security reasons. Those PCs mostly run Windows, because Management does not care what programmers want, and even the cheapest Admin knows how to handle Windows.

I see. I have not worked in such an environment for over a decade and forgot they exist. I guess Mordac and the BOFH are still alive.

PowerShell is thus a hard requirement, with my incorrect assumption being that WSL completely replaces cygwin and powershell.

I think I'd always bring my own machine or use cygwin in such situations.

2.) WSL is a Virtual Machine, that works well but, like most things Micro$oft does, it is half-assed. For example. I can install Apache2, MySQL, NodeJS, etc. but then, to access them, I need to add some exception Rules to the firewall (done via Powershell). Otherwise, I can't access the Servers with the Browsers on the Windows side.

That makes sense. I've never used WSL however assumed it was more useful.

4.)It is also not guaranteed, that all file changes inside WSL will make it over to the windows side.

Wow....

Add to that, that you can start Windows Executables from within WSL and it all gets quite confusing.

Can not? That's terrible.

I'm often 'provided' a PC for data security reasons.

That may go a long way to explaining all the strange permission dramas with files underneath your home directory.

ljani commented 1 year ago

What is the use case for PowerShell over WSL or cygwin?

I don't think it's PowerShell vs WSL/cygwin, but native-Windows vs WSL1/WSL2/cygwin, and PowerShell over cmd.exe. I used to be a Cygwin user until WSL came out, but Cygwin was always a little painful. WSL2 is pretty good, but I still need a editor on the native Windows side, and I'm using Neovim for that. Also, I use nvim-qt.exe ("gvim"), not nvim.exe inside PowerShell.

Does PowerShell even play a role here? I thought the behaviour would be the same even if I was not a PowerShell user, but used cmd.exe.

As for Windows vs Linux, try doing native Windows development on Linux 😜 Thus I use Neovim on Windows.

alex-courtis commented 1 year ago

Does PowerShell even play a role here? I thought the behaviour would be the same even if I was not a PowerShell user, but used cmd.exe.

The difference is in the file paths: cygwin/wsl uses POSIX style paths and native uses C:\bleh etc.

vim.fn.has("win32") is true for native, false for WSL.

The paths are the only point of difference here.

alex-courtis commented 1 year ago

PR was closed.