@@ -85,6 +85,47 @@ local function wrap_explorer_member(explorer_member, member_method)
8585 end
8686end
8787
88+ --- Exit visual mode synchronously.
89+ local function exit_visual_mode ()
90+ local esc = vim .api .nvim_replace_termcodes (" <Esc>" , true , false , true )
91+ vim .api .nvim_feedkeys (esc , " nx" , false )
92+ end
93+
94+ --- Wrap a single-node function to operate on visual selection range.
95+ --- @param fn fun ( node : Node ): any
96+ --- @return fun (): any
97+ local function wrap_visual_range (fn )
98+ return function ()
99+ local explorer = require (" nvim-tree.core" ).get_explorer ()
100+ if not explorer then return end
101+ local start_line = vim .fn .line (" v" )
102+ local end_line = vim .fn .line (" ." )
103+ if start_line > end_line then start_line , end_line = end_line , start_line end
104+ local nodes = explorer :get_nodes_in_range (start_line , end_line )
105+ exit_visual_mode ()
106+ for _ , node in ipairs (nodes ) do
107+ fn (node )
108+ end
109+ end
110+ end
111+
112+ --- Wrap a bulk operation that collects visual nodes and passes them all at once.
113+ --- @param member string explorer member name
114+ --- @param method string method name to invoke on member
115+ --- @return fun (): any
116+ local function wrap_visual_bulk (member , method )
117+ return function ()
118+ local explorer = require (" nvim-tree.core" ).get_explorer ()
119+ if not explorer then return end
120+ local start_line = vim .fn .line (" v" )
121+ local end_line = vim .fn .line (" ." )
122+ if start_line > end_line then start_line , end_line = end_line , start_line end
123+ local nodes = explorer :get_nodes_in_range (start_line , end_line )
124+ exit_visual_mode ()
125+ explorer [member ][method ](explorer [member ], nodes )
126+ end
127+ end
128+
88129--- @class NodeEditOpts
89130--- @field quit_on_open boolean | nil default false
90131--- @field focus boolean | nil default true
@@ -254,6 +295,12 @@ function M.hydrate(api)
254295 api .marks .navigate .next = wrap_explorer_member (" marks" , " navigate_next" )
255296 api .marks .navigate .prev = wrap_explorer_member (" marks" , " navigate_prev" )
256297 api .marks .navigate .select = wrap_explorer_member (" marks" , " navigate_select" )
298+ api .marks .toggle_visual = wrap_visual_range (wrap_explorer_member (" marks" , " toggle" ))
299+
300+ api .fs .copy .visual = wrap_visual_range (wrap_explorer_member (" clipboard" , " copy" ))
301+ api .fs .cut_visual = wrap_visual_range (wrap_explorer_member (" clipboard" , " cut" ))
302+ api .fs .remove_visual = wrap_visual_bulk (" marks" , " bulk_delete_nodes" )
303+ api .fs .trash_visual = wrap_visual_bulk (" marks" , " bulk_trash_nodes" )
257304
258305 api .map .keymap .current = keymap .get_keymap
259306
0 commit comments