Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,10 @@ return {
},
opts = {
show_icons = true,
icon_provider = 'nvim-web-devicons', -- or 'mini.icons'
Copy link

Copilot AI Nov 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The icon_provider value 'nvim-web-devicons' does not match the implementation in lua/arrow/integration/icons.lua which expects 'web_dev_icons' (line 31). This mismatch will cause the icon provider selection to fail. Change to 'web_dev_icons' to match the implementation.

Suggested change
icon_provider = 'nvim-web-devicons', -- or 'mini.icons'
icon_provider = 'web_dev_icons', -- or 'mini.icons'

Copilot uses AI. Check for mistakes.
leader_key = ';', -- Recommended to be a single key
buffer_leader_key = 'm', -- Per Buffer Mappings

}
}
```
Expand Down Expand Up @@ -81,6 +83,7 @@ Just press the leader_key set on setup and follow you heart. (Is that easy)
remove = "x", -- only used if separate_save_and_remove is true
next_item = "]",
prev_item = "["
Copy link

Copilot AI Nov 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing comma after prev_item = "[" on the previous line. This will cause a syntax error in the Lua table definition.

Suggested change
prev_item = "["
prev_item = "[",

Copilot uses AI. Check for mistakes.
toggle_bookmark_type = '<TAB>', -- toggle between file and dir bookmark
},
custom_actions = {
open = function(target_file_name, current_file_name) end, -- target_file_name = file selected to be open, current_file_name = filename from where this was called
Expand All @@ -105,6 +108,12 @@ Just press the leader_key set on setup and follow you heart. (Is that easy)
zindex = 10, --default 50
treesitter_context = nil, -- it can be { line_shift_down = 2 }, currently not usable, for detail see https://github.com/otavioschwanck/arrow.nvim/pull/43#issue-2236320268
},
dir_bookmark_config = {
-- required to open dir bookmarks
open_action = function(dir_path, _) -- action to take when opening a dir in an oil floating window
require('oil').open_float(dir_path)
end,
},
separate_save_and_remove = false, -- if true, will remove the toggle and create the save/remove keymaps.
leader_key = ";",
save_key = "cwd", -- what will be used as root to save the bookmarks. Can be also `git_root` and `git_root_bare`.
Expand Down
28 changes: 17 additions & 11 deletions lua/arrow/buffer_ui.lua
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ local preview_buffers = {}

local persist = require("arrow.buffer_persist")
local config = require("arrow.config")
local namespace = vim.api.nvim_create_namespace("arrow_buffers")

local lastRow = 0
local has_current_line = false
Expand Down Expand Up @@ -92,10 +93,10 @@ function M.spawn_preview_window(buffer, index, bookmark, bookmark_count)
extra_title = "(Current)"
end

vim.api.nvim_win_set_option(win, "scrolloff", 999)
vim.api.nvim_set_option_value("scrolloff", 999, { win = win })
vim.api.nvim_win_set_cursor(win, { bookmark.line, 0 })
vim.api.nvim_win_set_config(win, { title = displayIndex .. " " .. extra_title })
vim.api.nvim_win_set_option(win, "number", true)
vim.api.nvim_set_option_value("number", true, { win = win })

table.insert(preview_buffers, { buffer = buffer, win = win, index = index })

Expand All @@ -104,7 +105,7 @@ function M.spawn_preview_window(buffer, index, bookmark, bookmark_count)
local shift = ctx_config.line_shift_down

local win_view = vim.fn.winsaveview()
vim.api.nvim_win_set_option(win, "scrolloff", 0)
vim.api.nvim_set_option_value("scrolloff", 0, { win = win })
vim.fn.winrestview({ topline = win_view.topline - shift })

local ok, _ = pcall(require, "treesitter-context")
Expand Down Expand Up @@ -163,16 +164,22 @@ local function go_to_window()
end

local function render_highlights(buffer)
vim.api.nvim_buf_clear_namespace(buffer, -1, 0, -1)
vim.api.nvim_buf_clear_namespace(buffer, namespace, 0, -1)

local buffer_line_count = vim.api.nvim_buf_line_count(buffer)

for i = 0, buffer_line_count - 1 do
vim.api.nvim_buf_add_highlight(buffer, -1, "ArrowFileIndex", i, 0, 3)
vim.api.nvim_buf_set_extmark(buffer, namespace, i, 0, {
end_col = 3,
hl_group = "ArrowFileIndex",
})

-- if line contains Delete Mode
if string.match(vim.fn.getline(i + 1), "Delete Mode") and delete_mode then
vim.api.nvim_buf_add_highlight(buffer, -1, "ArrowDeleteMode", i, 0, 3)
vim.api.nvim_buf_set_extmark(buffer, namespace, i, 0, {
end_col = 3,
hl_group = "ArrowDeleteMode",
})
end
end
end
Expand Down Expand Up @@ -224,8 +231,8 @@ local function toggle_delete_mode(action_buffer)
else
delete_mode = true

current_highlight = vim.api.nvim_get_hl_by_name("FloatBorder", true)
local arrow_delete_mode = vim.api.nvim_get_hl_by_name("ArrowDeleteMode", true)
current_highlight = vim.api.nvim_get_hl(0, { name = "FloatBorder" })
local arrow_delete_mode = vim.api.nvim_get_hl(0, { name = "ArrowDeleteMode" })

vim.api.nvim_set_hl(0, "FloatBorder", { fg = arrow_delete_mode.bg or "red" })
end
Expand Down Expand Up @@ -355,8 +362,7 @@ function M.spawn_action_windows(call_buffer, bookmarks, line_nr, col_nr, call_wi

local menuKeymapOpts = { noremap = true, silent = true, buffer = actions_buffer, nowait = true }

vim.api.nvim_buf_set_option(actions_buffer, "modifiable", true)

vim.api.nvim_set_option_value("modifiable", true, { buf = actions_buffer })
vim.api.nvim_buf_set_lines(actions_buffer, 0, -1, false, lines)

vim.keymap.set("n", config.getState("leader_key"), function()
Expand Down Expand Up @@ -485,7 +491,7 @@ function M.openMenu(bufnr)
M.spawn_preview_window(opt[1], opt[2], opt[3], #bookmarks)
end

M.spawn_action_windows(bufnr, bookmarks, line_nr, col_nr, cur_win)
M.spawn_action_windows(bufnr, bookmarks, line_nr, col_nr)
end

return M
61 changes: 60 additions & 1 deletion lua/arrow/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ function M.setup(opts)
vim.cmd("highlight default link ArrowCurrentFile SpecialChar")
vim.cmd("highlight default link ArrowAction Character")
vim.cmd("highlight default link ArrowDeleteMode DiagnosticError")
vim.cmd("highlight default link ArrowSplitMode Character")
vim.cmd("highlight default link ArrowFileBorder FloatBorder")
vim.cmd("highlight default link ArrowDirBorder FloatBorder")

opts = opts or {}

Expand All @@ -35,6 +38,7 @@ function M.setup(opts)
remove = "x",
next_item = "]",
prev_item = "[",
toggle_bookmark_type = "<TAB>",
}

local default_window_config = {
Expand Down Expand Up @@ -85,13 +89,31 @@ function M.setup(opts)
config.setState("global_bookmarks", opts.global_bookmarks or false)
config.setState("relative_path", opts.relative_path or false)
config.setState("separate_save_and_remove", opts.separate_save_and_remove or false)
config.setState("icon_provider", opts.icon_provider or "web_dev_icons")

-- Directory bookmarks configuration
local default_dir_bookmark_config = {
open_action = function(_, _)
print("no dir action found")
end,
}
config.setState(
"dir_bookmark_config",
utils.join_two_keys_tables(default_dir_bookmark_config, opts.dir_bookmark_config or {})
)
config.setState("current_bookmark_type", "file")

config.setState("save_key", save_keys[opts.save_key] or save_keys.cwd)
config.setState("save_key_name", opts.save_key or "cwd")
config.setState("save_key_cached", config.getState("save_key")())

if leader_key then
vim.keymap.set("n", leader_key, ui.openMenu, { noremap = true, silent = true, nowait = true, desc = "Arrow File Mappings" })
vim.keymap.set(
"n",
leader_key,
ui.openMenu,
{ noremap = true, silent = true, nowait = true, desc = "Arrow File Mappings" }
)
end

if buffer_leader_key then
Expand Down Expand Up @@ -180,17 +202,54 @@ function M.setup(opts)
"reset",
}

local default_dir_full_path_list = {
"src",
"lib",
"library",
"utils",
"bin",
"cmd",
"docs",
"doc",
"tests",
"specs",
"configs",
"config",
"settings",
"images",
"themes",
"assets",
"static",
"public",
"scripts",
"examples",
"modules",
"development",
"dev",
"lua",
"tools",
".github",
".vscode",
".config",
}

config.setState("mappings", utils.join_two_keys_tables(default_mappings, opts.mappings or {}))
config.setState("full_path_list", utils.join_two_arrays(default_full_path_list, opts.full_path_list or {}))
config.setState(
"dir_full_path_list",
utils.join_two_arrays(default_dir_full_path_list, opts.dir_full_path_list or {})
)

persist.load_cache_file()
persist.load_dir_cache_file()

vim.api.nvim_create_augroup("arrow", { clear = true })

vim.api.nvim_create_autocmd({ "DirChanged", "SessionLoadPost" }, {
callback = function()
git.refresh_git_branch()
persist.load_cache_file()
persist.load_dir_cache_file()
config.setState("save_key_cached", config.getState("save_key")())
end,
desc = "load cache file on DirChanged",
Expand Down
43 changes: 29 additions & 14 deletions lua/arrow/integration/icons.lua
Original file line number Diff line number Diff line change
@@ -1,37 +1,52 @@
local config = require("arrow.config")

local M = {}

local get_icon_from_web_dev_icons = function(file_name)
local webdevicons = require("nvim-web-devicons")
local extension = vim.fn.fnamemodify(file_name, ":e")
local icon, hl_group = webdevicons.get_icon(file_name, extension, { default = true })
if vim.fn.isdirectory(file_name) == 1 then
return "H", "Normal"
Copy link

Copilot AI Nov 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The hardcoded directory icon 'H' seems inconsistent with typical folder icons. Consider using a proper folder icon (e.g., '' or webdevicons' folder icon) or making it configurable. The choice of 'H' is unclear and may not visually represent a directory well.

Suggested change
return "H", "Normal"
local icon, hl_group = webdevicons.get_icon(file_name, "", { default = true })
return icon, hl_group

Copilot uses AI. Check for mistakes.
else
local extension = vim.fn.fnamemodify(file_name, ":e")
local icon, hl_group = webdevicons.get_icon(file_name, extension, { default = true })

return icon, hl_group
return icon, hl_group
end
end

local get_icon_from_mini = function(file_name)
local icons = require("mini.icons")
return icons.get("extension", file_name)
if vim.fn.isdirectory(file_name) == 1 then
return icons.get("directory", file_name)
else
return icons.get("extension", file_name)
end
end

--- Gets file icon from either `nvim-web-devicons` or `mini.icons`.
--- @param file_name string
M.get_file_icon = function(file_name)
if vim.fn.isdirectory(file_name) == 1 then
return "", "Normal"
end
local provider = config.getState("icon_provider")

local use_web_dev_icons = pcall(require, "nvim-web-devicons")
local use_mini_icons = pcall(require, "mini.icons")
if provider == "web_dev_icons" then
local use_web_dev_icons = pcall(require, "nvim-web-devicons")

if not (use_web_dev_icons or use_mini_icons) then
error("No icon provider found", vim.log.levels.ERROR)
end
if not use_web_dev_icons then
error("No icon provider found", vim.log.levels.ERROR)
end

if use_web_dev_icons then
return get_icon_from_web_dev_icons(file_name)
end

return get_icon_from_mini(file_name)
if provider == "mini" then
local use_mini_icons = pcall(require, "mini.icons")

if not use_mini_icons then
error("No icon provider found", vim.log.levels.ERROR)
end

return get_icon_from_mini(file_name)
end
Copy link

Copilot AI Nov 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function does not return a value when provider is neither 'web_dev_icons' nor 'mini'. This can lead to nil values being returned unexpectedly. Add an else clause at the end that either throws an error about invalid provider or provides a default fallback.

Suggested change
end
end
error("Invalid icon provider: " .. tostring(provider), vim.log.levels.ERROR)

Copilot uses AI. Check for mistakes.
end

return M
Loading