Skip to content
Merged
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
24 changes: 19 additions & 5 deletions doc/nvim-tree-lua.txt
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,9 @@ CONTENTS *nvim-tree*
5.16 Opts: Notify |nvim-tree-opts-notify|
5.17 Opts: Help |nvim-tree-opts-help|
5.18 Opts: UI |nvim-tree-opts-ui|
5.19 Opts: Experimental |nvim-tree-opts-experimental|
5.20 Opts: Log |nvim-tree-opts-log|
5.19 Opts: Bookmarks |nvim-tree-opts-bookmarks|
5.20 Opts: Experimental |nvim-tree-opts-experimental|
5.21 Opts: Log |nvim-tree-opts-log|
6. API |nvim-tree-api|
6.1 API Tree |nvim-tree-api.tree|
6.2 API File System |nvim-tree-api.fs|
Expand Down Expand Up @@ -639,6 +640,9 @@ Following is the default configuration. See |nvim-tree-opts| for details. >lua
default_yes = false,
},
},
bookmarks = {
persist = false,
},
experimental = {
},
log = {
Expand Down Expand Up @@ -1401,7 +1405,6 @@ delete/wipe. A reload or filesystem event will result in an update.
*nvim-tree.filters.no_bookmark*
Do not show files that are not bookmarked.
Toggle via |nvim-tree-api.tree.toggle_no_bookmark_filter()|, default `M`
Enabling this is not useful as there is no means yet to persist bookmarks.
Type: `boolean`, Default: `false`

*nvim-tree.filters.custom*
Expand Down Expand Up @@ -1657,14 +1660,24 @@ Confirmation prompts.
Type: `boolean`, Default: `false`

==============================================================================
5.19 OPTS: EXPERIMENTAL *nvim-tree-opts-experimental*
5.19 OPTS: BOOKMARKS *nvim-tree-opts-bookmarks*

*nvim-tree.bookmarks.persist*
Persist bookmarks to a json file containing a list of absolute paths.
Type: `boolean` | `string`, Default: `false`

`true`: use default: `stdpath("data") .. "/nvim-tree-bookmarks.json"`
`string`: absolute path of your choice.

==============================================================================
5.20 OPTS: EXPERIMENTAL *nvim-tree-opts-experimental*

*nvim-tree.experimental*
Experimental features that may become default or optional functionality.
In the event of a problem please disable the experiment and raise an issue.

==============================================================================
5.20 OPTS: LOG *nvim-tree-opts-log*
5.21 OPTS: LOG *nvim-tree-opts-log*

Configuration for diagnostic logging.

Expand Down Expand Up @@ -3189,6 +3202,7 @@ highlight group is not, hard linking as follows: >
|nvim-tree.actions.remove_file.close_window|
|nvim-tree.actions.use_system_clipboard|
|nvim-tree.auto_reload_on_write|
|nvim-tree.bookmarks.persist|
|nvim-tree.diagnostics.debounce_delay|
|nvim-tree.diagnostics.diagnostic_opts|
|nvim-tree.diagnostics.enable|
Expand Down
8 changes: 7 additions & 1 deletion lua/nvim-tree.lua
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,9 @@ local DEFAULT_OPTS = { -- BEGIN_DEFAULT_OPTS
default_yes = false,
},
},
bookmarks = {
persist = false,
},
experimental = {
},
log = {
Expand All @@ -530,7 +533,7 @@ local DEFAULT_OPTS = { -- BEGIN_DEFAULT_OPTS
watcher = false,
},
},
} -- END_DEFAULT_OPTS
}-- END_DEFAULT_OPTS

local function merge_options(conf)
return vim.tbl_deep_extend("force", DEFAULT_OPTS, conf or {})
Expand Down Expand Up @@ -581,6 +584,9 @@ local ACCEPTED_TYPES = {
},
},
},
bookmarks = {
persist = { "boolean", "string" },
},
}

local ACCEPTED_STRINGS = {
Expand Down
4 changes: 2 additions & 2 deletions lua/nvim-tree/explorer/filters.lua
Original file line number Diff line number Diff line change
Expand Up @@ -208,8 +208,8 @@ function Filters:prepare(project)

local explorer = require("nvim-tree.core").get_explorer()
if explorer then
for _, node in pairs(explorer.marks:list()) do
status.bookmarks[node.absolute_path] = node.type
for _, node in ipairs(explorer.marks:list()) do
status.bookmarks[node.absolute_path] = node
end
end

Expand Down
115 changes: 107 additions & 8 deletions lua/nvim-tree/marks/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,53 @@ local utils = require("nvim-tree.utils")
local Class = require("nvim-tree.classic")
local DirectoryNode = require("nvim-tree.node.directory")

local function get_save_path(opts)
if type(opts.bookmarks.persist) == "string" then
return opts.bookmarks.persist
else
return vim.fn.stdpath("data") .. "/nvim-tree-bookmarks.json"
end
end

local function save_bookmarks(marks, opts)
if not opts.bookmarks.persist then
return
end

local storepath = get_save_path(opts)
local file, errmsg = io.open(storepath, "w")
if file then
local data = {}
for path, _ in pairs(marks) do
table.insert(data, path)
end
file:write(vim.json.encode(data))
file:close()
else
notify.warn(string.format("Invalid bookmarks.persist, disabling persistence: %s", errmsg))
opts.bookmarks.persist = false
end
end

local function load_bookmarks(opts)
local storepath = get_save_path(opts)
local file = io.open(storepath, "r")
if file then
local content = file:read("*all")
file:close()
if content and content ~= "" then
local data = vim.json.decode(content)
local marks = {}
for _, path in ipairs(data) do
-- Store as boolean initially; will be lazily resolved to node on first access
marks[path] = true
end
return marks
end
end
return {}
end

---@class (exact) Marks: Class
---@field private explorer Explorer
---@field private marks table<string, Node> by absolute path
Expand All @@ -26,8 +73,15 @@ local Marks = Class:extend()
---@param args MarksArgs
function Marks:new(args)
self.explorer = args.explorer

self.marks = {}
if self.explorer.opts.bookmarks.persist then
local ok, loaded_marks = pcall(load_bookmarks, self.explorer.opts)
if ok then
self.marks = loaded_marks
else
notify.warn(string.format("Failed to load bookmarks: %s", loaded_marks))
end
end
end

---Clear all marks and reload if watchers disabled
Expand Down Expand Up @@ -59,6 +113,12 @@ function Marks:toggle(node)
self.marks[node.absolute_path] = node
end

if self.explorer.opts.bookmarks.persist then
local ok, err = pcall(save_bookmarks, self.marks, self.explorer.opts)
if not ok then
notify.warn(string.format("Failed to save bookmarks: %s", err))
end
end
self.explorer.renderer:draw()
end

Expand All @@ -67,16 +127,45 @@ end
---@param node Node
---@return Node|nil
function Marks:get(node)
return node and self.marks[node.absolute_path]
if not node or not node.absolute_path then
return nil
end
local mark = self.marks[node.absolute_path]
if mark == true then
-- Lazy resolve: try to find node in explorer tree
local resolved_node = self.explorer:get_node_from_path(node.absolute_path)
if resolved_node then
-- Cache the resolved node
self.marks[node.absolute_path] = resolved_node
return resolved_node
end
return nil
end
return mark
end

---List marked nodes
---@public
---@return Node[]
function Marks:list()
local list = {}
for _, node in pairs(self.marks) do
table.insert(list, node)
for path, mark in pairs(self.marks) do
local node
if mark == true then
-- Lazy resolve: try to find node in explorer tree
node = self.explorer:get_node_from_path(path)
if node then
-- Cache the resolved node for future access
self.marks[path] = node
end
-- If node not found (file deleted/moved), skip it silently
else
-- Already a node object
node = mark
end
if node then
table.insert(list, node)
end
end
return list
end
Expand All @@ -90,7 +179,7 @@ function Marks:bulk_delete()
end

local function execute()
for _, node in pairs(self.marks) do
for _, node in ipairs(self:list()) do
remove_file.remove(node)
end
self:clear_reload()
Expand Down Expand Up @@ -119,7 +208,7 @@ function Marks:bulk_trash()
end

local function execute()
for _, node in pairs(self.marks) do
for _, node in ipairs(self:list()) do
trash.remove(node)
end
self:clear_reload()
Expand Down Expand Up @@ -172,7 +261,7 @@ function Marks:bulk_move()
return
end

for _, node in pairs(self.marks) do
for _, node in ipairs(self:list()) do
local head = vim.fn.fnamemodify(node.absolute_path, ":t")
local to = utils.path_join({ location, head })
rename_file.rename(node, to)
Expand Down Expand Up @@ -259,7 +348,17 @@ function Marks:navigate_select()
if not choice or choice == "" then
return
end
local node = self.marks[choice]
local mark = self.marks[choice]
local node
if mark == true then
-- Lazy resolve
node = self.explorer:get_node_from_path(choice)
if node then
self.marks[choice] = node
end
else
node = mark
end
if node and not node:is(DirectoryNode) and not utils.get_win_buf_from_path(node.absolute_path) then
open_file.fn("edit", node.absolute_path)
elseif node then
Expand Down
Loading