@@ -12,6 +12,7 @@ local fn = vim.fn
1212local ROWS_INDENT_RETCODE = indentHelper .ROWS_INDENT_RETCODE
1313
1414--- @class IndentMetaInfo : MetaInfo
15+ --- @field pre_leftcol number
1516--- @field cache Cache
1617
1718local constructor = function (self , conf , meta )
@@ -21,8 +22,9 @@ local constructor = function(self, conf, meta)
2122 hl_base_name = " HLIndent" ,
2223 ns_id = api .nvim_create_namespace (" indent" ),
2324 shiftwidth = fn .shiftwidth (),
25+ pre_leftcol = 0 ,
2426 leftcol = fn .winsaveview ().leftcol ,
25- cache = Cache (),
27+ cache = Cache (" bufnr " , " line " ),
2628 }
2729
2830 BaseMod .init (self , conf , meta )
3335--- @class IndentMod : BaseMod
3436--- @field conf IndentConf
3537--- @field meta IndentMetaInfo
36- --- @field render fun ( self : IndentMod , range : Scope )
37- --- @field renderLine function
38+ --- @field render fun ( self : IndentMod , range : Scope , opts : { lazy : boolean })
39+ --- @field narrowRange fun ( self : IndentMod , range : Scope ): Scope
40+ --- @field calcRenderInfo fun ( self : IndentMod , range : Scope ): table<number , string>
41+ --- @field setmark function
3842--- @overload fun ( conf ?: UserIndentConf , meta ?: MetaInfo ): IndentMod
3943local IndentMod = class (BaseMod , constructor )
4044
41- function IndentMod : render ( range )
42- self : clear ( range )
45+ local pos2info = Cache ( " bufnr " , " line " , " col " )
46+ local pos2id = Cache ( " bufnr " , " line " , " col " )
4347
44- -- narrow the range that should get indent
45- local non_cached_start = range .start
46- local non_cached_finish = range .finish
48+ function IndentMod :disable ()
49+ pos2info :clear ()
50+ pos2id :clear ()
51+ BaseMod .disable (self )
52+ end
53+
54+ function IndentMod :narrowRange (range )
55+ local start = range .start
56+ local finish = range .finish
4757 for i = range .start , range .finish do
4858 if not self .meta .cache :has (range .bufnr , i ) then
49- non_cached_start = i
59+ start = i
5060 break
5161 end
5262 end
53- for i = non_cached_start , range .finish do
54- if self .meta .cache :has (range .bufnr , i ) then
55- non_cached_finish = i - 1
63+ for i = range . finish , range .start , - 1 do
64+ if not self .meta .cache :has (range .bufnr , i ) then
65+ finish = i
5666 break
5767 end
5868 end
69+ return Scope (range .bufnr , start , finish )
70+ end
5971
60- -- calculate indent
61- local retcode , rows_indent = indentHelper .get_rows_indent (Scope (range .bufnr , non_cached_start , non_cached_finish ), {
62- use_treesitter = self .conf .use_treesitter ,
63- virt_indent = true ,
64- })
65- if retcode == ROWS_INDENT_RETCODE .NO_TS and self .conf .use_treesitter then
66- if self .conf .notify then
67- self :notify (" [hlchunk.indent]: no parser for " .. vim .bo .filetype , nil , { once = true })
68- end
69- return
70- end
71-
72- -- update cache
73- for lnum , indent in pairs (rows_indent ) do
74- self .meta .cache :set (range .bufnr , lnum , indent )
75- end
76-
72+ function IndentMod :calcRenderInfo (range )
7773 -- calc render info
78- local row_opts = {
79- virt_text_pos = " overlay" ,
80- hl_mode = " combine" ,
81- priority = self .conf .priority ,
82- }
8374 local char_num = # self .conf .chars
8475 local style_num = # self .meta .hl_name_list
8576 local leftcol = self .meta .leftcol
8677 local sw = self .meta .shiftwidth
8778 local render_info = {}
8879 for lnum = range .start , range .finish do
89- local blankLen = self .meta .cache :get (range .bufnr , lnum )
80+ local blankLen = self .meta .cache :get (range .bufnr , lnum ) --[[ @as string ]]
9081 local render_char_num , offset , shadow_char_num = indentHelper .calc (blankLen , leftcol , sw )
9182 for i = 1 , render_char_num do
83+ local win_col = offset + (i - 1 ) * sw
9284 local char = self .conf .chars [(i - 1 + shadow_char_num ) % char_num + 1 ]
9385 local style = self .meta .hl_name_list [(i - 1 + shadow_char_num ) % style_num + 1 ]
9486 table.insert (render_info , {
9587 lnum = lnum ,
96- virt_text_win_col = offset + leftcol + ( i - 1 ) * sw ,
88+ virt_text_win_col = win_col ,
9789 virt_text = { { char , style } },
9890 })
91+ pos2info :set (range .bufnr , lnum , win_col , { char , style })
9992 end
10093 end
10194
95+ return render_info
96+ end
97+
98+ function IndentMod :setmark (bufnr , render_info )
10299 -- render
100+ local row_opts = {
101+ virt_text_pos = " overlay" ,
102+ hl_mode = " combine" ,
103+ priority = self .conf .priority ,
104+ }
103105 for _ , v in pairs (render_info ) do
104106 row_opts .virt_text = v .virt_text
105107 row_opts .virt_text_win_col = v .virt_text_win_col
106- api .nvim_buf_set_extmark (range .bufnr , self .meta .ns_id , v .lnum , 0 , row_opts )
108+ if not pos2id :get (bufnr , v .lnum , v .virt_text_win_col ) then
109+ local id = api .nvim_buf_set_extmark (bufnr , self .meta .ns_id , v .lnum , 0 , row_opts )
110+ pos2id :set (bufnr , v .lnum , v .virt_text_win_col , id )
111+ end
107112 end
108113end
109114
115+ function IndentMod :render (range , opts )
116+ opts = opts or { lazy = false }
117+ if not opts .lazy then
118+ self :clear (Scope (range .bufnr , 0 , api .nvim_buf_line_count (range .bufnr )))
119+ self .meta .cache :clear (range .bufnr )
120+ pos2id :clear (range .bufnr )
121+ pos2info :clear (range .bufnr )
122+ end
123+
124+ local narrowed_range = self :narrowRange (range )
125+ -- calculate indent
126+ local retcode , rows_indent = indentHelper .get_rows_indent (narrowed_range , {
127+ use_treesitter = self .conf .use_treesitter ,
128+ virt_indent = true ,
129+ })
130+ if retcode == ROWS_INDENT_RETCODE .NO_TS and self .conf .use_treesitter then
131+ if self .conf .notify then
132+ self :notify (" [hlchunk.indent]: no parser for " .. vim .bo .filetype , nil , { once = true })
133+ end
134+ return
135+ end
136+
137+ -- update cache
138+ for lnum , indent in pairs (rows_indent ) do
139+ self .meta .cache :set (range .bufnr , lnum , indent )
140+ end
141+ local render_info = self :calcRenderInfo (narrowed_range )
142+ self :setmark (range .bufnr , render_info )
143+ end
144+
110145function IndentMod :createAutocmd ()
111146 BaseMod .createAutocmd (self )
112- local render_cb = function (event )
147+ local render_cb = function (event , opts )
148+ opts = opts or { lazy = false }
113149 local bufnr = event .buf
114150 if not (api .nvim_buf_is_valid (bufnr ) and self :shouldRender (bufnr )) then
115151 return
@@ -122,29 +158,35 @@ function IndentMod:createAutocmd()
122158 range .finish = math.min (api .nvim_buf_line_count (bufnr ) - 1 , range .finish + ahead_lines )
123159 api .nvim_win_call (winid , function ()
124160 self .meta .shiftwidth = cFunc .get_sw (bufnr )
161+ self .meta .pre_leftcol = self .meta .leftcol
125162 self .meta .leftcol = fn .winsaveview ().leftcol
126- self :render (range )
163+ if self .meta .pre_leftcol ~= self .meta .leftcol then
164+ opts .lazy = false
165+ end
166+ self :render (range , opts )
127167 end )
128168 end
129169 end
130170 local throttle_render_cb = throttle (render_cb , self .conf .delay )
131- local throttle_render_cb_with_pre_hook = function (event )
171+ local throttle_render_cb_with_pre_hook = function (event , opts )
172+ opts = opts or { lazy = false }
132173 local bufnr = event .buf
133174 if not (api .nvim_buf_is_valid (bufnr ) and self :shouldRender (bufnr )) then
134175 return
135176 end
136- throttle_render_cb (event )
177+ throttle_render_cb (event , opts )
137178 end
138179
139180 api .nvim_create_autocmd ({ " WinScrolled" }, {
140181 group = self .meta .augroup_name ,
141- callback = throttle_render_cb_with_pre_hook ,
182+ callback = function (e )
183+ throttle_render_cb_with_pre_hook (e , { lazy = true })
184+ end ,
142185 })
143186 api .nvim_create_autocmd ({ " TextChanged" , " TextChangedI" }, {
144187 group = self .meta .augroup_name ,
145188 callback = function (e )
146- self .meta .cache :clear (e .buf )
147- throttle_render_cb_with_pre_hook (e )
189+ throttle_render_cb_with_pre_hook (e , { lazy = false })
148190 end ,
149191 })
150192 api .nvim_create_autocmd ({ " BufWinEnter" }, {
0 commit comments