nvim-neo-tree / neo-tree.nvim

Neovim plugin to manage the file system and other tree like structures.
MIT License
3.79k stars 220 forks source link

BUG: Shadow on editor edge that randomly appears #1575

Open Ajaymamtora opened 2 weeks ago

Ajaymamtora commented 2 weeks ago

Did you check docs and existing issues?

Neovim Version (nvim -v)

NVIM v0.10.2 Build type: Release LuaJIT 2.1.1713484068 Run "nvim -V1 -v" for more info

Operating System / Version

macos 15.0

Describe the Bug

theres a drop shadow along the edge which flickers when used with edgy.nvim

Screenshots, Traceback

image

Steps to Reproduce

Not actually sure what causes it, think its a win highlight and removing local highlights does nothing.

Expected Behavior

No drop shadow

Your Configuration

local M = {}

local git_utils = require("utils.git")

function M.set_neo_tree_root(new_root)
  if new_root then
    local neo_tree_loaded, _ = pcall(require, "neo-tree")
    if neo_tree_loaded then
      local manager = require("neo-tree.sources.manager")
      local renderer = require("neo-tree.ui.renderer")

      -- Check if the root has changed
      local current_state = manager.get_state("filesystem")
      if current_state and current_state.path == new_root then
        -- Root hasn't changed, no need to update
        return
      end

      -- Update the root for all tabs
      for _, tabpage in ipairs(vim.api.nvim_list_tabpages()) do
        local state = manager.get_state("filesystem", tabpage)
        if state and state.path ~= new_root then
          state.path = new_root
          state.dirty = true
        end
      end

      -- Refresh Neo-tree if it's visible in the current tab
      local current_tabpage = vim.api.nvim_get_current_tabpage()
      local state = manager.get_state("filesystem", current_tabpage)
      if state and renderer.window_exists(state) and state.path ~= new_root then
        manager.navigate("filesystem", new_root)
      end

      vim.g.cached_lsp_root = new_root
      print("Neo-tree root set to: " .. new_root)
    else
      print("Neo-tree is not loaded")
    end
  end
end

local augroup = vim.api.nvim_create_augroup("TabGroupSwitch", { clear = true })
vim.api.nvim_create_autocmd("TabEnter", {
  group = augroup,
  callback = function()
    vim.schedule(function()
      if vim.g.cached_lsp_root then
        M.set_neo_tree_root(vim.g.cached_lsp_root)
      end
    end)
  end,
})

M.neo_tree = function()
  local icons = require("core.ui.icons")

  -- Helper function to check if a path is excluded
  local function is_path_excluded(path, exclusions)
    for _, exclusion in ipairs(exclusions) do
      -- Trim whitespace and remove trailing slash if present
      local pattern = exclusion:match("^%s*(.-)%s*/?$")
      -- Escape all special regex characters
      pattern = pattern:gsub("([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1")
      -- Replace escaped '*' with '.-' for wildcard matching
      pattern = pattern:gsub("%%%*", ".-")

      -- Check if the pattern matches the full path (for absolute path patterns)
      if pattern:sub(1, 2) == "%/" then
        if path:match("^" .. pattern .. "$") then
          return true
        end
      else
        -- For relative patterns, match against path segments
        local segments = {}
        for segment in path:gmatch("[^/]+") do
          table.insert(segments, segment)
        end

        -- Check if the pattern matches any complete segment
        for i = #segments, 1, -1 do
          if segments[i]:match("^" .. pattern .. "$") then
            return true
          end
        end
      end
    end
    return false
  end

  local windows_bindings = {
    ["zO"] = "expand_all_nodes",
    ["zC"] = "close_all_nodes",
    ["zZ"] = "toggle_auto_expand_width",
    ["zz"] = {
      "toggle_node",
    },
    ["<C-w>s"] = "open_split",
    ["<C-w>v"] = "open_vsplit",
    ["<C-w>T"] = "open_tabnew",
    ["<C-w>p"] = "open_with_window_picker",
    ["<M-y>"] = "copy_path_from_content_root",
    ["e"] = false,
    ["z"] = false,
    ["<Leader>"] = false,
    ["<M-u>"] = "refresh",
    ["<M-f>"] = "live_grep_at_path",
    ["<M-d>"] = "diffview_node",
    ["<M-g>"] = "lazy_git_at_path",
    ["<M-e>"] = "explorer_at_path",
    ["<M-o>"] = "telescope_find_and_open_file",
    ["<M-r>"] = "grug_replace",
    ["<M-t>"] = "open_in_terminal",
    ["E"] = "toggle_exclude_item",
    ["[g"] = "prev_git_modified",
    ["]g"] = "next_git_modified",
    ["_"] = "reset_root_to_lsp_common",
  }
  local user_exclusions = require("neoconf").get("exclusions")
  local permanent_exclusions = require("utils.files").lua_converted_exclusion_patterns

  local function custom_renderer(config, node, state)
    local name = node.name
    local path = node:get_id()

    -- Check if the node is excluded or filtered
    local is_user_excluded = is_path_excluded(path, user_exclusions)
    local is_permanent_excluded = is_path_excluded(path, permanent_exclusions)
    local is_filtered = node.filtered_by and next(node.filtered_by) ~= nil

    -- Apply custom highlight if excluded or filtered
    if is_permanent_excluded then
      return {
        {
          text = name,
          highlight = "NeoTreePermanentExcludedFileName",
        },
      }
    elseif is_user_excluded or is_filtered then
      return {
        {
          text = name,
          highlight = "NeoTreeExcludedFileName",
        },
      }
    end

    -- Use the default renderer for non-excluded and non-filtered nodes
    local default_renderer = require("neo-tree.sources.common.components").name
    local default_result = default_renderer(config, node, state)

    -- Ensure we're returning a table to prevent potential issues
    if type(default_result) ~= "table" then
      return { { text = tostring(default_result), highlight = "NeoTreeFileName" } }
    end

    return default_result
  end

  -- Update the toggle_exclude_item function
  local function toggle_exclude_item(state)
    local node = state.tree:get_node()
    local path = node:get_id()

    local is_permanent_excluded = is_path_excluded(path, permanent_exclusions)

    if is_permanent_excluded then
      vim.notify("Permanent exclusion found at " .. tostring(path), vim.log.levels.WARN)
      return
    end

    require("utils.settings").toggle_string_in_table(path, "exclusions")

    print("Toggle exclude item at " .. tostring(path))

    pcall(function()
      vim.cmd("Neotree close")
    end)
    vim.defer_fn(function()
      require("neoconf.settings").refresh()
      pcall(function()
        vim.cmd("Lazy reload neo-tree.nvim")
      end)
    end, 100)
  end

  local event_handlers = {
    {
      event = "vim_buffer_changed",
      handler = function(_)
        vim.schedule(function()
          if vim.g.cached_lsp_root then
            M.set_neo_tree_root(vim.g.cached_lsp_root)
          end
        end)
      end,
    },
    {
      event = "neo_tree_buffer_enter",
      handler = function(_)
        vim.schedule(function()
          vim.wo.statuscolumn = " "
        end)
      end,
    },
    {
      event = "neo_tree_window_before_open",
      handler = function(_) end,
    },
    {
      event = "neo_tree_window_after_close",
      handler = function(args)
        if args.position == "left" or args.position == "right" then
          vim.cmd("wincmd =")
        end
      end,
    },
    {
      event = "neo_tree_window_after_open",
      handler = function(args)
        if args.position == "left" or args.position == "right" then
          vim.cmd("wincmd =")
        end
        vim.schedule(function()
          vim.wo.statuscolumn = " "
          --       vim.g.minianimate_disable = false
        end)
      end,
    },
    {
      event = "file_opened",
      handler = function(file_path)
        -- auto close
        require("neo-tree.command").execute({ action = "close" })
      end,
    },
    {
      event = "neo_tree_buffer_leave",
      handler = function()
        local shown_buffers = {}
        for _, win in ipairs(vim.api.nvim_list_wins()) do
          shown_buffers[vim.api.nvim_win_get_buf(win)] = true
        end
        for _, buf in ipairs(vim.api.nvim_list_bufs()) do
          if
            not shown_buffers[buf]
            and vim.api.nvim_buf_get_option(buf, "buftype") == "nofile"
            and vim.api.nvim_buf_get_option(buf, "filetype") == "neo-tree"
          then
            vim.api.nvim_buf_delete(buf, {})
          end
        end
      end,
    },
  }

  local filesystem_commands = {
    telescope_find_and_open_file = function(state)
      local node = state.tree:get_node()
      local path = node:get_id()

      -- Check if the path is a file or a directory
      local is_file = vim.fn.filereadable(path) == 1

      -- If it's a file, get the parent directory
      if is_file then
        path = vim.fn.fnamemodify(path, ":h")
      end

      require("utils.telescope_utils").find_files({
        paths = {
          path,
        },
        cwd = path,
      })
      print("Searching for files in " .. tostring(path))
    end,

    copy_path_from_content_root = function(state)
      local node = state.tree:get_node()
      local absolute_path = node:get_id()

      vim.ui.select({ "Absolute Path", "Relative Path" }, {
        prompt = "Select path type to copy:",
        format_item = function(item)
          return item
        end,
      }, function(choice)
        local result

        if choice == "Absolute Path" then
          result = absolute_path
        else
          -- Get the git root directory
          local git_root = vim.fn.systemlist(
            "git -C " .. vim.fn.escape(vim.fn.fnamemodify(absolute_path, ":h"), " ") .. " rev-parse --show-toplevel"
          )[1]

          if git_root and git_root ~= "" then
            -- File is in a git repository
            result = vim.fn.fnamemodify(absolute_path, ":p"):gsub(git_root .. "/", "")
          else
            -- File is not in a git repository, use full path
            result = vim.fn.fnamemodify(absolute_path, ":p")
          end
        end

        -- Copy the result to the clipboard
        vim.fn.setreg("+", result)

        -- Print a message to confirm the action
        print("Copied to clipboard: " .. result)
      end)
    end,

    live_grep_at_path = function(state)
      local node = state.tree:get_node()
      local path = node:get_id()

      local exclusions = require("utils.files").get_rg_exclusions()
      require("telescope").extensions.egrepify.search_given_files({ exclusions = exclusions, search_dirs = { path } })
      print("Searching in " .. tostring(path))
    end,

    lazy_git_at_path = function(state)
      local node = state.tree:get_node()
      local path = node:get_id()

      -- Use git_utils to find the closest Git directory
      local git_dir = git_utils.get_closest_git_dir_to(path)

      if git_dir then
        require("lazygit").lazygit(git_dir)
        print("Lazygit opened at " .. tostring(git_dir))
      else
        print("No Git repository found. Using the original path.")
        require("lazygit").lazygit(path)
        print("Lazygit opened at " .. tostring(path))
      end
    end,

    explorer_at_path = function(state)
      local node = state.tree:get_node()
      local path = node:get_id()

      require("yazi").yazi(nil, path)
      print("Opening explorer at " .. tostring(path))
    end,

    diffview_node = function(state)
      local node = state.tree:get_node()
      local path = node:get_id()

      -- Ensure we have a valid path
      if not path then
        print("Error: Unable to get path from node")
        return
      end

      -- Get the full absolute path
      local full_path = vim.fn.fnamemodify(path, ":p")

      -- Check if the path exists
      if vim.fn.isdirectory(full_path) == 1 or vim.fn.filereadable(full_path) == 1 then
        -- Construct the DiffviewFileHistory command
        local cmd = string.format("DiffviewFileHistory %s", vim.fn.fnameescape(full_path))

        -- Execute the Diffview command
        vim.cmd(cmd)

        print("Opening Diffview File History for: " .. full_path)
      else
        print("Path does not exist or is not accessible: " .. full_path)
      end
    end,

    grug_replace = function(state)
      local node = state.tree:get_node()
      local path = node:get_id()
      vim.cmd(":Neotree close")

      -- Check if the path is a file or directory
      local is_single = false
      local is_file = vim.fn.filereadable(path) == 1
      local is_directory = vim.fn.isdirectory(path) == 1

      if is_file then
        is_single = true
      elseif is_directory then
        is_single = false
      end

      require("utils.search_utils").grug_search({
        default_text = "",
        paths = { path },
        single_file_mode = is_single,
      })
      print("Replacing in " .. (is_single and "file: " or "directory: ") .. tostring(path))
    end,

    open_in_terminal = function(state)
      local toggleterm = require("toggleterm")
      local node = state.tree:get_node()
      local path = node:get_id()

      -- Use vim.fn.shellescape to properly escape the path
      local escaped_path = vim.fn.shellescape(path)

      -- Construct the command
      local cmd = string.format("cd %s", escaped_path)

      -- Use the toggleterm API to execute the command
      toggleterm.exec(cmd, 99)

      -- Focus the terminal
      require("utils.terminal").focus_main_term()
      -- Enter insert mode
      vim.schedule(function()
        vim.cmd("startinsert")
      end)

      print("Opening terminal in " .. tostring(path))
    end,

    toggle_exclude_item = toggle_exclude_item,

    reset_root_to_lsp_common = require("utils.lsp").set_neo_tree_dir_to_lsp_common_root,
  }

  local full_opts = {
    hide_root_node = true,
    retain_hidden_root_indent = true,
    sort_case_insensitive = true,
    open_files_do_not_replace_types = { "terminal", "Trouble", "qf", "edgy", "neo-tree" },
    use_popups_for_input = false,
    popup_border_style = "single",
    enable_git_status = false,
    enable_diagnostics = false,
    auto_clean_after_session_restore = true,
    sources = {
      "filesystem",
      -- "buffers",
      -- "git_status",
      --"document_symbols",
    },
    event_handlers = event_handlers,
    source_selector = {
      winbar = true,
      separator = "",
      content_layout = "center",
      sources = {
        {
          source = "filesystem",
          display_name = icons.common.folder .. " DIR  ",
        },
        -- {
        --   source = "buffers",
        --   display_name = icons.common.buffer .. " BUF  ",
        -- },
        -- {
        --   source = "git_status",
        --   display_name = icons.common.git .. " GIT  ",
        -- },
        --        {
        --         source = "document_symbols",
        --        display_name = icons.lsp.Module .. " DOC  ",
        --     },
      },
    },
    default_component_configs = {
      container = {
        enable_character_fade = true,
      },
      indent = {
        with_markers = false,
        with_expanders = true,
        expander_collapsed = "",
        expander_expanded = "",
      },
      icon = {
        folder_closed = icons.common.folder_close,
        folder_open = icons.common.folder_open,
        folder_empty = icons.common.folder_empty,
        highlight = "NeoTreeFileIcon",
      },
      modified = {
        symbol = icons.common.dot,
      },
      git_status = {
        symbols = icons.git_status,
        align = "right",
      },
      name = {
        use_git_status_colors = false,
        highlight = "NeoTreeFileName",
      },
    },
    window = {
      position = "left",
      width = 40,
    },
    filesystem = {
      components = {
        name = custom_renderer,
      },
      window = {
        mappings = windows_bindings,
      },
      commands = filesystem_commands,
      bind_to_cwd = false,
      -- netrw disabled, opening a directory opens neo-tree
      -- in whatever position is specified in window.position
      -- "open_current",  -- netrw disabled, opening a directory opens within the
      -- window like netrw would, regardless of window.position
      -- "disabled",    -- netrw left alone, neo-tree does not handle opening dirs
      -- hijack_netrw_behavior = "disabled",
      follow_current_file = {
        enabled = true,
        leave_dirs_open = true,
      },
      filtered_items = {
        hide_dotfiles = false,
        hide_gitignored = true,
        hide_by_name = {
          ".git",
          ".idea",
          ".DS_Store",
        },
        always_show = {
          ".nvim.json",
        },
        show_hidden_count = false,
        never_show = {
          ".DS_Store",
        },
      },
      use_libuv_file_watcher = not _G.__os.is_windows,
    },
    diagnostics = {
      autopreview = false,
      autopreview_config = {},
      autopreview_event = "neo_tree_buffer_enter",
      bind_to_cwd = true,
      diag_sort_function = "severity",
      follow_behavior = {
        always_focus_file = true,
        expand_followed = true,
        collapse_others = true,
      },
      follow_current_file = false,
      group_dirs_and_files = true,
      group_empty_dirs = true,
      show_unloaded = true,
    },
    git_status = {
      indent = {
        padding = 0,
      },
    },
    -- nesting_rules = require("neotree-file-nesting-config").nesting_rules,
  }
  return full_opts
end

M.neo_tree_setup = function()
  require("neo-tree").setup(M.neo_tree())
end

return M
Ajaymamtora commented 2 weeks ago

It appears after a short delay on open.