Closed rebelot closed 2 years ago
Thank your for the new design. I can see it is headed for the right direction.
The priority is getting confused in some cases. In the following example, the icon and the filetype on the right (blue) have priority of 1
:
heirline.make_elastic_component(1, {
function(self)
return " " .. (self.icon or "") .. " " .. string.upper(vim.bo.filetype)
end,
function(self)
return " " .. (self.icon or "")
end,
"",
}),
And the filename priority at the centre of the status line is 1
, 2
and 4
.
1
2
4
As you can see, the filename when it has the highest priority is not retracted.
1) fixed issue where priorities had to be continuous. Now can be any integer (except 0)
2) make_elastic_component
now accepts components
Below some examples taking advantage of some other utility functions and general heirline inheritance-based functionality.
Notes:
1) you can wrap elastic components in other components providing some scope for providers
2) when you pass components to make_elastic_components
as arguments, their condition
, init
and static
will be affected. To avoid troubles, it's best to shield your actual component by enclosing them into a "dummy" parent. Like this: make_elastic_component(3, {my_component}, {my_other_component})
(see expandable_2
in the example, where condition
is protected)
Perhaps the most common usecase wuold be expandable_3
in this example.
local long = { provider = string.rep("A", 40) }
local medium = { provider = string.rep("B", 20) }
local short = { provider = string.rep("C", 5) }
local null = { provider = "" }
local static = { provider = string.rep("S", 10) }
local fill = { provider = "%=" }
local expandable_1 = u.make_elastic_component(1, long, medium, short)
local expandable_2 = u.make_elastic_component(2)
expandable_2 = u.insert(expandable_2, {
u.clone(long, {
condition = function()
return true
end,
hl = { bg = "green" },
}),
}, null)
local expandable_3 = {
condition = function(self)
return vim.bo.filetype == "lua"
end,
static = { icon = "!" },
u.make_elastic_component(3, {
provider = function(self)
return self.icon .. "loooooooooooong"
end,
}, {
provider = function(self)
return self.icon .. "short"
end,
}),
}
local statusline = {
static,
fill,
{ hl = { fg = "blue" }, expandable_1 },
fill,
expandable_2,
fill,
expandable_3,
fill,
static,
}
require("heirline").setup(statusline, { before = u.elastic_before })
IMPORTANT: in the experimental branch, stop_at_first
has become stop_when(self, out)
.
I am not sure I will keep this. To retain the same functionality, you should change
...
stop_at_first = true
...
to this:
...
stop_when = function(self, out)
return out ~= ''
end
...
Nice work!
The overall functionality works really well. However there are a few things that can be worked on:
As you can see the components retract when there is still enough space left.
there's a current bug where all elastic components contribute to the "decision making" process of whether they would fit or not, disregarding the fact that the component might be in an hidden statusline (stop_at_first/stop_when).
If you try the example script you'll see that the spacing works correctly, but if you implement, say, an inactive statusline containing expandable components you'll see the wrong spacing behavior.
I am working on it, shouldn't be too too hard.
for the nesting, keep in mind that the statusline structure is a recursive inheritance hell and my brain hurts at thinking what would happen if you nest them.
The problem:
nvim_eval_statusline
which returns the actual string printed in the statusline, so the result might be a truncated string! (>
). If we don't use this function, there's no way to expand %m
, %3.10(something%)
, and other statusline special characters.current_window_width - statusline_length
will never be negative.current_window_width - statusline_length > 0
is met! And this is a performance problem. Implement caching for a general case, could also result in unnecessary overhead, where re-evaluating the statusline could generally be faster than retrieving the cached results.One option could be pretending to forget about this feature and call it bloat. Or I could just fix it piece-by-piece, until "it works", but I can already see it's becoming spaghetti. I would be happy already with some working spaghetti, but I am considering a third way, using vim.loop
libuv asynchronous events and allow some kind of async communication between a main event loop and each component.
Fair enough.
How do you feel about keeping a table of elastic component in descending order of priorities and calculate the length in two passes? Something like this.
try it now. ;)
483c56a2736e4775862f9b44820ce605df12dd87
it is now possible to nest expandable components. Just make sure that the inner expandable component has a lower priority than the outer (i.e. it needs to be updated before its own container).
performance should also be improved as the calls to eval
have been significantly reduced.
I am still thinking about small optimisations/refactoring, but the API should remain unchanged.
make_elastic_component
behaves just like any other componentstatic
, init
and restrict
fields of the expandable component itself should not be overridden. If you need to do so, just wrap the expandable component into another modifier component.5828b3d4d5fef336bf918145956ba131aa6cb2ac
Thanks a lot @arsham as your feedback greatly improved this plugin.
No problem mate, I'm glad to help.
So, everything is fine, here is the results (the same setup):
This setup doesn't have any nested components, I will refactor and test it.
The performance is noticeably better, although can be improved.
Thank you for the amazing work!
I found a couple of new things:
Thanks again for the feedback, I know what's causing this and I can solve it already. It is a bit complicated, but has to do with the fact that the current logic is to always evaluate the longest options and then try to contract. For this reason expandable components that are not at index = 1 will never have the chance to notify their existence. The other option is to try to contract or expand instead of always try to contract. I discarded the first option after some benchmarkin showing the execution time was 0.5 times higher and I could not foresee benefits. I need to clean the code a bit then I can push that. It shouldn't take long and hopefully this is solved (for good???).
update: we're almost there. Evaluation time has sped up again, as now the total evaluations per cycle are: 1 full evaluation and at most N evaluations of just the expandable components.
ATM I'm trying to make sense out of sorting priorities in case of nesting. It is becoming confusing.
update: It should now be possible to nest expandable components indefinitely.
local a = { provider = string.rep("A", 40) }
local b = { provider = string.rep("B", 30) }
local c = { provider = string.rep("C", 20) }
local d = { provider = string.rep("D", 10) }
local e = { provider = string.rep("E", 8) }
local f = { provider = string.rep("F", 4) }
local nest_madness = {
u.make_elastic_component(1,
a,
u.make_elastic_component(nil,
b,
u.make_elastic_component(nil, c, d),
e
),
f
),
{ provider = "%=" },
u.make_elastic_component(4,
a,
u.make_elastic_component(nil,
b,
u.make_elastic_component(nil, c, d),
e
),
f
),
}
require("heirline").setup(nest_madness, { before = u.elastic_before, after = u.elastic_after })
tl;dr Only care about the outer expandable component priority and use large number gaps between expandable components.
You can test expandable components on branch experimental. The following is an example to illustrate the basic usage.
The factory function is
make_elastic_component(priority, providers)
. it accepts the priority integer value and a list of providers which can be either strings or functions. Providers must be in decreasing length.lowest priority: first to contract, last to expand highest priority: last to contract, first to expand components with same priority are contracted/expanded together.
You also need to plug the
elastic_before
callback when callingsetup
.I need some feedback on this to test performance and usability.
Cheers!