1- local ts_utils = require (' orgmode.utils.treesitter' )
21local config = require (' orgmode.config' )
32
3+ local query = vim .treesitter .query .parse (' org' , ' (stars) @stars' )
4+
45--- @class OrgStarsHighlighter
56--- @field highlighter OrgHighlighter
7+ --- @field private cache table<number , table<number , number>>
8+ --- @field private attached table<number , boolean>
69local OrgStars = {}
710
811--- @param opts { highlighter : OrgHighlighter }
912function OrgStars :new (opts )
1013 local data = {
1114 highlighter = opts .highlighter ,
15+ cache = setmetatable ({}, { __mode = ' k' }),
16+ attached = {},
1217 }
1318 setmetatable (data , self )
1419 self .__index = self
1520 return data
1621end
1722
18- function OrgStars :on_line (bufnr , line )
19- if not config :hide_leading_stars (bufnr ) then
23+ --- Mirror of foldtext._apply_change: drop entries in the changed range and
24+ --- shift entries after the boundary by the row delta. Keeps cached end_col of
25+ --- unchanged headlines valid across edits without a tree query.
26+ --- For single-line edits past the cached `(stars)` end_col, the entry is kept:
27+ --- those edits cannot affect the leading-star span, so dropping it would only
28+ --- cause a one-frame flicker until on_changedtree rebuilds.
29+ --- @param bufnr number
30+ --- @param start_row number 0-based
31+ --- @param start_col number 0-based byte column of the first changed byte
32+ --- @param old_end_row number rows in old range (relative to start_row )
33+ --- @param new_end_row number rows in new range (relative to start_row )
34+ function OrgStars :_apply_change (bufnr , start_row , start_col , old_end_row , new_end_row )
35+ local cache = self .cache [bufnr ]
36+ if not cache then
2037 return
2138 end
2239
23- local node = ts_utils .get_node ({
24- bufnr = bufnr ,
25- pos = { line , 0 },
40+ if old_end_row == new_end_row then
41+ for line = start_row , start_row + old_end_row do
42+ local cached = cache [line ]
43+ if not cached or line ~= start_row or start_col <= cached then
44+ cache [line ] = nil
45+ end
46+ end
47+ return
48+ end
49+
50+ local delta = new_end_row - old_end_row
51+ local boundary = start_row + old_end_row
52+ local shifted = {}
53+ for k , v in pairs (cache ) do
54+ if k < start_row then
55+ shifted [k ] = v
56+ elseif k > boundary then
57+ shifted [k + delta ] = v
58+ end
59+ end
60+ self .cache [bufnr ] = shifted
61+ end
62+
63+ --- Re-query the tree for `(stars)` nodes within each changed range. Fires
64+ --- after the parser produced a new tree, so ranges are in new-tree coordinates
65+ --- and the lookup is authoritative.
66+ --- @param bufnr number
67+ --- @param tree TSTree
68+ --- @param changes Range6[]
69+ function OrgStars :_apply_changedtree (bufnr , tree , changes )
70+ if not self .attached [bufnr ] then
71+ return
72+ end
73+ local cache = self .cache [bufnr ]
74+ if not cache then
75+ cache = {}
76+ self .cache [bufnr ] = cache
77+ end
78+ local root = tree :root ()
79+ for _ , range in ipairs (changes ) do
80+ local start_row , end_row = range [1 ], range [4 ]
81+ for line = start_row , end_row do
82+ cache [line ] = nil
83+ end
84+ for _ , node in query :iter_captures (root , bufnr , start_row , end_row + 1 ) do
85+ local sr , _ , er , ec = node :range ()
86+ if sr == er and ec > 0 then
87+ cache [sr ] = ec
88+ end
89+ end
90+ end
91+ end
92+
93+ --- Attach buffer + language_tree listeners. Idempotent per buffer.
94+ --- @param bufnr number
95+ --- @param language_tree vim.treesitter.LanguageTree
96+ function OrgStars :attach (bufnr , language_tree )
97+ if self .attached [bufnr ] then
98+ return
99+ end
100+ local ok = vim .api .nvim_buf_attach (bufnr , false , {
101+ on_bytes = function (_ , b , _tick , start_row , start_col , _ , old_end_row , _ , _ , new_end_row )
102+ self :_apply_change (b , start_row , start_col , old_end_row , new_end_row )
103+ end ,
104+ on_reload = function (_ , b )
105+ self .cache [b ] = nil
106+ end ,
107+ on_detach = function (_ , b )
108+ self :on_detach (b )
109+ end ,
26110 })
111+ if not ok then
112+ return
113+ end
114+ self .attached [bufnr ] = true
115+ language_tree :register_cbs ({
116+ on_changedtree = function (changes , tree )
117+ if tree then
118+ self :_apply_changedtree (bufnr , tree , changes )
119+ end
120+ end ,
121+ })
122+ end
27123
28- if not node or node :type () ~= ' stars' then
124+ --- @param bufnr number
125+ --- @param line number
126+ function OrgStars :on_line (bufnr , line )
127+ if not config :hide_leading_stars (bufnr ) then
29128 return
30129 end
31130
32- local _ , end_col = node :end_ ()
131+ local cache = self .cache [bufnr ]
132+ if not cache then
133+ return
134+ end
33135
34- if end_col <= 0 then
136+ local end_col = cache [line ]
137+ if not end_col or end_col <= 0 then
35138 return
36139 end
37140
@@ -43,4 +146,10 @@ function OrgStars:on_line(bufnr, line)
43146 })
44147end
45148
149+ --- @param bufnr number
150+ function OrgStars :on_detach (bufnr )
151+ self .cache [bufnr ] = nil
152+ self .attached [bufnr ] = nil
153+ end
154+
46155return OrgStars
0 commit comments