Skip to content
This repository was archived by the owner on Sep 20, 2023. It is now read-only.

Commit 677ce7d

Browse files
authored
feat: allow updating providers through triggers (#232)
Providers can now be updated by autocmd and function triggers in order to prevent re-running a function everytime the statusline is generated. Closes #227
1 parent 28ee9b8 commit 677ce7d

File tree

4 files changed

+160
-19
lines changed

4 files changed

+160
-19
lines changed

USAGE.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,32 @@ end
164164

165165
If you omit the provider value, it will be set to an empty string. A component with no provider or an empty provider may be useful for things like [applying a highlight to section gaps](#highlight-section-gaps) or just having an icon or separator as a component.
166166

167+
##### Update provider value using triggers
168+
169+
Sometimes the provider value has to do some heavy operations, which makes it undesirable to run the provider function every time the statusline is generated. Feline allows you to conditionally re-run the provider function by triggering an update to the provider string through either an autocmd or a function. Until the provider function is run again, the value from the previous execution of the provider function is used as the provider string.
170+
171+
Updating provider value through triggers is achieved through the `update` key in the `provider` table. `update` can be either a boolean value, a table or a function that returns a boolean value or a table. If it's a boolean value, then the provider will be updated if value is `true`. For example:
172+
173+
```lua
174+
provider = {
175+
name = 'my_provider',
176+
-- Only update provider if there are less than 4 windows in the current tabpage
177+
update = function()
178+
return #vim.api.nvim_tabpage_list_wins(0) < 4
179+
end
180+
}
181+
```
182+
183+
If it's a table, it must contain a list of autocmds that will trigger an update for the provider. For example:
184+
185+
```lua
186+
provider = {
187+
name = 'my_provider',
188+
-- Only update provider if a window is closed or if a buffer is deleted
189+
update = { 'WinClosed', 'BufDelete' }
190+
}
191+
```
192+
167193
#### Component name
168194

169195
A component can optionally be given a name. While the component is not required to have a name and the name is mostly useless, it can be used to check if the component has been [truncated](#truncation). To give a component a name, just set its `name` value to a `string`, shown below:

lua/feline/generator.lua

Lines changed: 126 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ local bo = vim.bo
22
local api = vim.api
33

44
local feline = require('feline')
5+
local utils = require('feline.utils')
56

67
local M = {
78
-- Cached highlights
@@ -12,6 +13,15 @@ local M = {
1213
component_hidden = {},
1314
}
1415

16+
-- Cached provider strings for providers that are updated through a trigger
17+
local provider_cache = {}
18+
19+
-- Cached provider strings for short providers that are updated through a trigger
20+
local short_provider_cache = {}
21+
22+
-- Flags to check if the autocmd for a provider update trigger has been created
23+
local provider_autocmd = {}
24+
1525
-- Return true if any pattern in tbl matches provided value
1626
local function find_pattern_match(tbl, val)
1727
return tbl and next(vim.tbl_filter(function(pattern)
@@ -231,7 +241,7 @@ local function parse_icon(icon, parent_hl, is_component_empty)
231241
end
232242

233243
-- Parse component provider to return the provider string and default icon
234-
local function parse_provider(provider, component)
244+
local function parse_provider(provider, component, is_short, winid, section_nr, component_nr)
235245
local str = ''
236246
local icon
237247

@@ -242,11 +252,17 @@ local function parse_provider(provider, component)
242252
else
243253
str = provider
244254
end
245-
-- If provider is a function, just evaluate it normally
246-
elseif type(provider) == 'function' then
255+
256+
return str, icon
257+
end
258+
259+
-- If provider is a function, just evaluate it normally
260+
if type(provider) == 'function' then
247261
str, icon = provider(component)
248262
-- If provider is a table, get the provider name and opts and evaluate the provider
249263
elseif type(provider) == 'table' then
264+
local provider_fn, provider_opts
265+
250266
if not provider.name then
251267
api.nvim_err_writeln("Provider table doesn't have the provider name")
252268
elseif type(provider.name) ~= 'string' then
@@ -256,7 +272,85 @@ local function parse_provider(provider, component)
256272
elseif not feline.providers[provider.name] then
257273
api.nvim_err_writeln(string.format("Provider with name '%s' doesn't exist", provider.name))
258274
else
259-
str, icon = feline.providers[provider.name](component, provider.opts or {})
275+
provider_fn = feline.providers[provider.name]
276+
provider_opts = provider.opts or {}
277+
end
278+
279+
local update = evaluate_if_function(provider.update)
280+
281+
if update == nil then
282+
str, icon = provider_fn(component, provider_opts)
283+
else
284+
local provider_cache_tbl
285+
286+
if is_short then
287+
provider_cache_tbl = short_provider_cache
288+
else
289+
provider_cache_tbl = provider_cache
290+
end
291+
292+
-- Initialize provider cache tables
293+
if not provider_cache[winid] then
294+
provider_cache[winid] = {}
295+
end
296+
297+
if not provider_cache[winid][section_nr] then
298+
provider_cache[winid][section_nr] = {}
299+
end
300+
301+
if not short_provider_cache[winid] then
302+
short_provider_cache[winid] = {}
303+
end
304+
305+
if not short_provider_cache[winid][section_nr] then
306+
short_provider_cache[winid][section_nr] = {}
307+
end
308+
309+
-- If `update` is true or provider string is not cached, call the provider function
310+
-- and cache it
311+
-- Use == true for comparison to prevent the condition being true if `update` is a table
312+
if update == true or not provider_cache_tbl[winid][section_nr][component_nr] then
313+
local cache_str, cache_icon = provider_fn(component, provider_opts)
314+
315+
provider_cache_tbl[winid][section_nr][component_nr] = {
316+
str = cache_str,
317+
icon = cache_icon,
318+
}
319+
end
320+
321+
-- If `update` is a table, it means that the provider update is triggered through
322+
-- autocmds
323+
if type(update) == 'table' then
324+
-- Initialize autocmd table structure
325+
if not provider_autocmd[winid] then
326+
provider_autocmd[winid] = {}
327+
end
328+
329+
if not provider_autocmd[winid][section_nr] then
330+
provider_autocmd[winid][section_nr] = {}
331+
end
332+
333+
-- If an autocmd hasn't been created for the provider update trigger, create it
334+
if not provider_autocmd[winid][section_nr][component_nr] then
335+
provider_autocmd[winid][section_nr][component_nr] = true
336+
337+
utils.create_augroup({
338+
{
339+
table.concat(update, ','),
340+
'*',
341+
string.format(
342+
'lua require("feline.generator").trigger_provider_update(%d, %d, %d)',
343+
winid,
344+
section_nr,
345+
component_nr
346+
),
347+
},
348+
}, 'feline', true)
349+
end
350+
end
351+
352+
str = provider_cache_tbl[winid][section_nr][component_nr].str
353+
icon = provider_cache_tbl[winid][section_nr][component_nr].icon
260354
end
261355
end
262356

@@ -269,7 +363,7 @@ local function parse_provider(provider, component)
269363
return str, icon
270364
end
271365

272-
local function parse_component(component, use_short_provider)
366+
local function parse_component(component, use_short_provider, winid, section_nr, component_nr)
273367
local enabled
274368

275369
if component.enabled ~= nil then
@@ -305,7 +399,7 @@ local function parse_component(component, use_short_provider)
305399
end
306400

307401
if provider then
308-
str, icon = parse_provider(provider, component)
402+
str, icon = parse_provider(provider, component, use_short_provider, winid, section_nr, component_nr)
309403
else
310404
str = ''
311405
end
@@ -324,21 +418,21 @@ end
324418
-- Wrapper around parse_component that handles any errors that happen while parsing the components
325419
-- and points to the location of the component in case of any errors
326420
local function parse_component_handle_errors(
327-
winid,
328421
component,
329422
use_short_provider,
330-
statusline_type,
331-
component_section,
332-
component_number
423+
winid,
424+
section_nr,
425+
component_nr,
426+
statusline_type
333427
)
334-
local ok, result = pcall(parse_component, component, use_short_provider)
428+
local ok, result = pcall(parse_component, component, use_short_provider, winid, section_nr, component_nr)
335429

336430
if not ok then
337431
api.nvim_err_writeln(
338432
string.format(
339433
"Feline: error while processing component number %d on section %d of type '%s' for window %d: %s",
340-
component_number,
341-
component_section,
434+
section_nr,
435+
component_nr,
342436
statusline_type,
343437
winid,
344438
result
@@ -362,6 +456,11 @@ local function get_component_width(component_str)
362456
return api.nvim_eval_statusline(component_str, eval_statusline_opts).width
363457
end
364458

459+
function M.trigger_provider_update(winid, section_nr, component_nr)
460+
provider_cache[winid][section_nr][component_nr] = nil
461+
short_provider_cache[winid][section_nr][component_nr] = nil
462+
end
463+
365464
-- Generate statusline by parsing all components and return a string
366465
function M.generate_statusline(is_active)
367466
local components
@@ -435,7 +534,7 @@ function M.generate_statusline(is_active)
435534
M.component_hidden[winid][component.name] = false
436535
end
437536

438-
local component_str = parse_component_handle_errors(winid, component, false, statusline_type, i, j)
537+
local component_str = parse_component_handle_errors(component, false, winid, i, j, statusline_type)
439538
local component_width = get_component_width(component_str)
440539

441540
component_strs[i][j] = component_str
@@ -468,12 +567,12 @@ function M.generate_statusline(is_active)
468567

469568
if component.short_provider then
470569
local component_str = parse_component_handle_errors(
471-
winid,
472570
component,
473571
true,
474-
statusline_type,
572+
winid,
475573
section,
476-
number
574+
number,
575+
statusline_type
477576
)
478577

479578
local component_width = get_component_width(component_str)
@@ -536,4 +635,14 @@ function M.generate_statusline(is_active)
536635
return table.concat(section_strs, '%=')
537636
end
538637

638+
-- Clear statusline generator state in order to do a clean reinitialization of it
639+
function M.clear_state()
640+
M.highlights = {}
641+
M.component_hidden = {}
642+
M.component_truncated = {}
643+
provider_cache = {}
644+
short_provider_cache = {}
645+
provider_autocmd = {}
646+
end
647+
539648
return M

lua/feline/init.lua

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,9 @@ function M.setup(config)
217217
-- Ensures custom quickfix statusline isn't loaded
218218
g.qf_disable_statusline = true
219219

220+
-- Clear statusline generator state
221+
gen.clear_state()
222+
220223
-- Set the value of the statusline option to Feline's statusline generation function
221224
opt.statusline = "%{%v:lua.require'feline'.statusline()%}"
222225

lua/feline/utils.lua

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,12 @@ local cmd = vim.api.nvim_command
44
local M = {}
55

66
-- Utility function to create augroups
7-
function M.create_augroup(autocmds, name)
7+
function M.create_augroup(autocmds, name, no_clear)
88
cmd('augroup ' .. name)
9-
cmd('autocmd!')
9+
10+
if no_clear == nil or no_clear == false then
11+
cmd('autocmd!')
12+
end
1013

1114
for _, autocmd in ipairs(autocmds) do
1215
cmd('autocmd ' .. table.concat(autocmd, ' '))

0 commit comments

Comments
 (0)