@@ -3,7 +3,10 @@ local events = require("neo-tree.events")
33local log = require (" neo-tree.log" )
44local Job = require (" plenary.job" )
55local uv = vim .uv or vim .loop
6+ local can_create_presized_table , new_table = pcall (require , " table.new" )
67local co = coroutine
8+ --- @type metatable
9+ local weak_kv = { __mode = " kv" }
710
811local M = {}
912local gsplit_plain = vim .fn .has (" nvim-0.9" ) == 1 and { plain = true } or true
@@ -23,9 +26,7 @@ local has_porcelain_v2 = git_version and git_version.major >= 2 and git_version.
2326M ._supported_porcelain_version = has_porcelain_v2 and 2 or 1
2427
2528--- @type table<string , neotree.git.Status>
26- M .status_cache = setmetatable ({}, {
27- __mode = " kv" ,
28- })
29+ M .statuses = setmetatable ({}, weak_kv )
2930
3031--- @class (exact ) neotree.git.Context : neotree.Config.GitStatusAsync
3132--- @field git_status neotree.git.Status ?
@@ -37,7 +38,8 @@ M.status_cache = setmetatable({}, {
3738--- @param git_status neotree.git.Status ?
3839local update_git_status = function (context , git_status )
3940 context .git_status = git_status
40- M .status_cache [context .git_root ] = git_status
41+ M ._root_dir_cache = setmetatable ({}, { __mode = " kv" })
42+ M .statuses [context .git_root ] = git_status
4143 vim .schedule (function ()
4244 events .fire_event (events .GIT_STATUS_CHANGED , {
4345 git_root = context .git_root ,
@@ -261,26 +263,39 @@ M._parse_porcelain = function(
261263 local typechanged = {}
262264 local renamed = {}
263265 local copied = {}
266+ local flattened_len = # statuses
264267 for i , s in ipairs (statuses ) do
268+ -- simplify statuses to the highest priority ones
265269 if s :find (" U" , 1 , true ) then
270+ statuses [i ] = " U"
266271 conflicts [# conflicts + 1 ] = i
267272 elseif s :find (" ?" , 1 , true ) then
273+ statuses [i ] = " ?"
268274 untracked [# untracked + 1 ] = i
269275 elseif s :find (" M" , 1 , true ) then
276+ statuses [i ] = " M"
270277 modified [# modified + 1 ] = i
271278 elseif s :find (" A" , 1 , true ) then
279+ statuses [i ] = " A"
272280 added [# added + 1 ] = i
273281 elseif s :find (" D" , 1 , true ) then
282+ statuses [i ] = " D"
274283 deleted [# deleted + 1 ] = i
275284 elseif s :find (" T" , 1 , true ) then
285+ statuses [i ] = " T"
276286 typechanged [# typechanged + 1 ] = i
277287 elseif s :find (" R" , 1 , true ) then
288+ statuses [i ] = " R"
278289 renamed [# renamed + 1 ] = i
279290 elseif s :find (" C" , 1 , true ) then
291+ statuses [i ] = " C"
280292 copied [# copied + 1 ] = i
293+ else
294+ flattened_len = flattened_len - 1
281295 end
282296 end
283- local flattened = {}
297+ local bubbleable_statuses_by_prio = can_create_presized_table and new_table (flattened_len , 0 )
298+ or {}
284299
285300 for _ , list in ipairs ({
286301 conflicts ,
@@ -292,13 +307,19 @@ M._parse_porcelain = function(
292307 renamed ,
293308 copied ,
294309 }) do
295- require (" neo-tree.utils._compat" ).table_move (list , 1 , # list , # flattened + 1 , flattened )
310+ require (" neo-tree.utils._compat" ).table_move (
311+ list ,
312+ 1 ,
313+ # list ,
314+ # bubbleable_statuses_by_prio + 1 ,
315+ bubbleable_statuses_by_prio
316+ )
296317 end
297318
319+ -- bubble them up
298320 local parent_statuses = {}
299321 do
300- local dot_byte = (" ." ):byte (1 , 1 )
301- for _ , i in ipairs (flattened ) do
322+ for _ , i in ipairs (bubbleable_statuses_by_prio ) do
302323 local path = paths [i ]
303324 local status = statuses [i ]
304325 local parent
@@ -322,8 +343,7 @@ M._parse_porcelain = function(
322343 break
323344 end
324345
325- parent_statuses [parent ] = status :byte (1 , 1 ) == dot_byte and status :sub (1 , 1 )
326- or status :sub (2 , 2 )
346+ parent_statuses [parent ] = status
327347 path = parent
328348 until false
329349
@@ -350,7 +370,7 @@ M._parse_porcelain = function(
350370 end
351371 end
352372
353- M .status_cache [git_root ] = git_status
373+ M .statuses [git_root ] = git_status
354374 return git_status
355375end
356376
@@ -535,7 +555,7 @@ M.status_async = function(path, base, opts)
535555 batch_delay = opts .batch_delay or 10 ,
536556 max_lines = opts .max_lines or 100000 ,
537557 }
538- if not M .status_cache [ctx .git_root ] then
558+ if not M .statuses [ctx .git_root ] then
539559 -- do a fast scan first to get basic things in, then a full scan with untracked files
540560 async_git_status_job (
541561 ctx ,
@@ -564,33 +584,37 @@ end
564584
565585--- @param state neotree.State
566586--- @param items neotree.FileItem[]
567- M .mark_ignored = function (state , items )
568- local gs = state .git_status_lookup
569- if not gs then
570- return
587+ M .mark_gitignored = function (state , items )
588+ local statuses = {}
589+ for git_root , git_status in pairs (M .statuses ) do
590+ if utils .is_subpath (git_root , state .path ) or utils .is_subpath (state .path , git_root ) then
591+ statuses [# statuses + 1 ] = git_status
592+ end
571593 end
572594 for _ , i in ipairs (items ) do
573- local direct_lookup = gs [i .path ]
574- if direct_lookup == " !" then
575- i .filtered_by = i .filtered_by or {}
576- i .filtered_by .gitignored = true
595+ for _ , git_status in ipairs (statuses ) do
596+ local status = git_status [i .path ]
597+ if status == " !" then
598+ i .filtered_by = i .filtered_by or {}
599+ i .filtered_by .gitignored = true
600+ break
601+ end
577602 end
578603 end
579604end
580605
581- local sp = utils .split_path
582606--- Invalidate cache for path and parents, updating trees as needed
583607--- @param path string
584608local invalidate_cache = function (path )
585609 --- @type string ?
586- local parent = sp (path )
610+ local parent = utils . split_path (path )
587611
588612 while parent do
589- local cache_entry = M .status_cache [parent ]
613+ local cache_entry = M .statuses [parent ]
590614 if cache_entry ~= nil then
591615 update_git_status ({ git_root = parent }, nil )
592616 end
593- parent = sp (parent )
617+ parent = utils . split_path (parent )
594618 end
595619end
596620
@@ -688,4 +712,27 @@ M.get_git_dir = function(path, callback)
688712 return git_root
689713end
690714
715+ --- @type table<string , string | false>
716+ M ._root_dir_cache = setmetatable ({}, weak_kv )
717+ --- Given a path, find whether a git status exists for it.
718+ --- @param path string
719+ --- @return string | false ? worktree_root
720+ --- @return neotree.git.Status ? status
721+ M .find_existing_status = function (path )
722+ local cached = M ._root_dir_cache [path ]
723+ if cached == false then
724+ return cached , nil
725+ end
726+ if cached ~= nil then
727+ return cached , M .statuses [cached ]
728+ end
729+ for root_dir , status in pairs (M .statuses ) do
730+ if utils .is_subpath (root_dir , path , true ) then
731+ M ._root_dir_cache [path ] = root_dir
732+ return root_dir , status
733+ end
734+ end
735+ M ._root_dir_cache [path ] = false
736+ end
737+
691738return M
0 commit comments