@@ -32,10 +32,6 @@ local tabid_to_tabnr = function(tabid)
3232 return vim .api .nvim_tabpage_is_valid (tabid ) and vim .api .nvim_tabpage_get_number (tabid )
3333end
3434
35- local buffer_is_usable = function (bufnr )
36- return vim .api .nvim_buf_is_valid (bufnr ) and vim .api .nvim_buf_is_loaded (bufnr )
37- end
38-
3935local cleaned_up = false
4036--- Clean up invalid neotree buffers (e.g after a session restore)
4137--- @param force boolean if true , force cleanup. Otherwise only cleanup once
@@ -75,6 +71,7 @@ local start_resize_monitor = function()
7571 local windows_exist = false
7672 local success , err = pcall (manager ._for_each_state , nil , function (state )
7773 if state .win_width and M .tree_is_visible (state ) then
74+ --- @cast state neotree.StateWithTree
7875 windows_exist = true
7976 local current_size = utils .get_inner_win_width (state .winid )
8077 if current_size ~= state .win_width then
658655--- Functions to save and restore the focused node.
659656M .position = {}
660657
658+ local visual_modes = {
659+ " v" ,
660+ " V" ,
661+ utils .keycode (" <C-v>" ),
662+ }
663+
664+ --- @param a [integer , integer , integer , integer]
665+ --- @param b [integer , integer , integer , integer]
666+ local position_sorter = function (a , b )
667+ if a [2 ] == b [2 ] then
668+ return a [3 ] < b [3 ]
669+ else
670+ return a [2 ] < b [2 ]
671+ end
672+ end
673+ --- @generic T
674+ --- @param positions T
675+ --- @return T positions
676+ local sort_positions = function (positions )
677+ table.sort (positions , position_sorter )
678+ return positions
679+ end
680+
681+ --- @class neotree.State.Position.VisualSelection
682+ --- @field [ 1] [integer , integer , integer , integer]
683+ --- @field [ 2] [integer , integer , integer , integer]
684+
685+ --- @class neotree.State.Position
686+ --- @field topline integer ?
687+ --- @field lnum integer ?
688+ --- @field node_id string ?
689+ --- @field visual_selection neotree.State.Position.VisualSelection ?
690+
661691--- Saves a window position to be restored later
662692--- @param state neotree.State
663693--- @param force boolean ?
@@ -666,12 +696,25 @@ M.position.save = function(state, force)
666696 log .debug (" There's already a position saved to be restored. Cannot save another." )
667697 return
668698 end
669- if state .tree and M .window_exists (state ) then
670- local win_state = vim .api .nvim_win_call (state .winid , vim .fn .winsaveview )
671- state .position .topline = win_state .topline
672- state .position .lnum = win_state .lnum
673- log .debug (" Saved cursor position with lnum: " .. state .position .lnum )
674- log .debug (" Saved window position with topline: " .. state .position .topline )
699+ if not state .tree then
700+ return
701+ end
702+ if not M .window_exists (state ) then
703+ return
704+ end
705+
706+ local win_state = vim .api .nvim_win_call (state .winid , vim .fn .winsaveview )
707+ state .position .topline = win_state .topline
708+ state .position .lnum = win_state .lnum
709+ log .debug (" Saved cursor position with lnum:" , state .position .lnum )
710+ log .debug (" Saved window position with topline:" , state .position .topline )
711+
712+ -- Save last visual selection in the neo-tree buffer
713+ local curbuf = vim .api .nvim_get_current_buf ()
714+ if state .bufnr == curbuf and vim .tbl_contains (visual_modes , vim .api .nvim_get_mode ().mode ) then
715+ local a = vim .fn .getpos (" ." )
716+ local b = vim .fn .getpos (" v" )
717+ state .position .visual_selection = { a , b }
675718 end
676719end
677720
@@ -716,9 +759,25 @@ M.position.restore = function(state)
716759 M .focus_node (state , state .position .node_id , true )
717760 end
718761
762+ M .position .restore_selection (state )
763+
719764 M .position .clear (state )
720765end
721766
767+ --- @param state neotree.State
768+ M .position .restore_selection = function (state )
769+ if state .winid ~= vim .api .nvim_get_current_win () then
770+ return
771+ end
772+ if not state .position .visual_selection then
773+ return
774+ end
775+ -- assertion unneeded but lua-ls isn't narrowing properly
776+ local selection = assert (sort_positions (state .position .visual_selection ))
777+ vim .fn .setpos ([[ '<]] , selection [1 ])
778+ vim .fn .setpos ([[ '>]] , selection [2 ])
779+ end
780+
722781--- Redraw the tree without reloading from the source.
723782--- @param state neotree.State State of the tree.
724783M .redraw = function (state )
@@ -804,9 +863,10 @@ M.set_expanded_nodes = function(tree, expanded_nodes)
804863 end
805864end
806865
866+ --- @param state neotree.State
807867create_tree = function (state )
808868 if state .tree and state .tree .bufnr == state .bufnr then
809- if buffer_is_usable (state .tree .bufnr ) then
869+ if vim . api . nvim_buf_is_loaded (state .tree .bufnr ) then
810870 log .debug (" Tree already exists and buffer is valid, skipping creation" , state .name , state .id )
811871 state .tree .winid = state .winid
812872 return
@@ -825,17 +885,18 @@ create_tree = function(state)
825885 })
826886end
827887
888+ --- @param state neotree.StateWithTree
828889--- @return NuiTree.Node[] ?
829890local get_selected_nodes = function (state )
830891 if state .winid ~= vim .api .nvim_get_current_win () then
831892 return nil
832893 end
833- local start_pos = vim .fn .getpos (" '<" )[2 ]
834- local end_pos = vim .fn .getpos (" '>" )[2 ]
835- if end_pos < start_pos then
836- -- I'm not sure if this could actually happen, but just in case
837- start_pos , end_pos = end_pos , start_pos
894+ if not state .position .visual_selection then
895+ return nil
838896 end
897+ sort_positions (state .position .visual_selection )
898+ local start_pos = state .position .visual_selection [1 ][2 ]
899+ local end_pos = state .position .visual_selection [2 ][2 ]
839900 local selected_nodes = {}
840901 while start_pos <= end_pos do
841902 local node = state .tree :get_node (start_pos )
@@ -1103,14 +1164,17 @@ M.acquire_window = function(state)
11031164 vim .api .nvim_buf_set_name (state .bufnr , bufname )
11041165 vim .api .nvim_set_current_win (state .winid )
11051166 -- Used to track the position of the cursor within the tree as it gains and loses focus
1106- win :on ({ " CursorMoved" }, function ()
1167+ win :on ({ " CursorMoved" , " ModeChanged " }, function ()
11071168 if win .winid == vim .api .nvim_get_current_win () then
11081169 M .position .save (state , true )
11091170 end
11101171 end )
11111172 win :on ({ " BufDelete" }, function ()
11121173 M .position .save (state )
11131174 end )
1175+ win :on ({ " WinEnter" }, function ()
1176+ M .position .restore_selection (state )
1177+ end )
11141178 win :on ({ " BufDelete" }, function ()
11151179 vim .schedule (function ()
11161180 win :unmount ()
@@ -1195,13 +1259,40 @@ M.tree_is_visible = function(state)
11951259 return M .window_exists (state ) and vim .api .nvim_win_get_buf (state .winid ) == state .bufnr
11961260end
11971261
1262+ --- @alias neotree.renderer.MarkedNodes table<vim.fn.getmarklist.ret.item , NuiTree.Node>
1263+
1264+ --- @param state neotree.StateWithTree
1265+ --- @return neotree.renderer.MarkedNodes
1266+ local save_marks = function (state )
1267+ local marks = vim .fn .getmarklist (state .bufnr )
1268+ local marked_nodes = {}
1269+
1270+ for _ , mark in ipairs (marks ) do
1271+ local node = state .tree :get_node (mark [2 ])
1272+ if node then
1273+ marked_nodes [mark ] = node
1274+ end
1275+ end
1276+ return marked_nodes
1277+ end
1278+
1279+ --- @param state neotree.StateWithTree
1280+ --- @param marks neotree.renderer.MarkedNodes
1281+ local restore_marks = function (state , marks )
1282+ for mark , node in pairs (marks ) do
1283+ vim .fn .setpos (mark .mark , mark .pos )
1284+ end
1285+ end
1286+
11981287--- Renders the given tree and expands window width if needed
1199- --- @param state neotree.State The state containing tree to render. Almost same as state.tree : render ()
1288+ --- @param state neotree.StateWithTree The state containing tree to render. Almost same as state.tree : render ()
12001289render_tree = function (state )
12011290 local add_blank_line_at_top = nt .config .add_blank_line_at_top
12021291 local should_auto_expand = state .window .auto_expand_width and state .current_position ~= " float"
12031292 local should_pre_render = should_auto_expand or state .current_position == " current"
12041293
1294+ local marks = save_marks (state )
1295+
12051296 if should_pre_render and M .tree_is_visible (state ) then
12061297 log .trace (" pre-rendering tree" )
12071298 state ._in_pre_render = true
@@ -1220,6 +1311,7 @@ render_tree = function(state)
12201311 state .win_width = desired_width
12211312 end
12221313 end
1314+
12231315 if M .tree_is_visible (state ) then
12241316 if add_blank_line_at_top then
12251317 state .tree :render (2 )
@@ -1230,6 +1322,7 @@ render_tree = function(state)
12301322
12311323 log .debug (" render_tree: Restoring position" )
12321324 M .position .restore (state )
1325+ restore_marks (state , marks )
12331326end
12341327
12351328--- Draws the given nodes on the screen.
@@ -1263,6 +1356,7 @@ draw = function(nodes, state, parent_id)
12631356 M .acquire_window (state )
12641357 create_tree (state )
12651358 end
1359+ --- @cast state neotree.StateWithTree
12661360
12671361 -- draw the given nodes
12681362 local success , msg = pcall (state .tree .set_nodes , state .tree , nodes , parent_id )
@@ -1313,7 +1407,7 @@ end
13131407
13141408--- Shows the given items as a tree.
13151409--- @param sourceItems table ? The list of items to transform.
1316- --- @param state neotree.State The current state of the plugin.
1410+ --- @param state neotree.StateWithTree The current state of the plugin.
13171411--- @param parentId string ? The id of the parent node to display these nodes at
13181412--- @param callback function ? The id of the parent node to display these nodes at
13191413M .show_nodes = function (sourceItems , state , parentId , callback )
0 commit comments