-
Notifications
You must be signed in to change notification settings - Fork 24
/
bar.lua
774 lines (732 loc) · 24.7 KB
/
bar.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
local configs = require('dropbar.configs')
local utils = require('dropbar.utils')
---@alias dropbar_symbol_range_t lsp_range_t
---@class dropbar_symbol_t
---@field _ dropbar_symbol_t
---@field name string
---@field icon string
---@field name_hl string?
---@field icon_hl string?
---@field win integer? the source window the symbol is shown in
---@field buf integer? the source buffer the symbol is defined in
---@field view table? original view of the source window
---@field bar dropbar_t? the winbar the symbol belongs to, if the symbol is shown inside a winbar
---@field menu dropbar_menu_t? menu associated with the winbar symbol, if the symbol is shown inside a winbar
---@field entry dropbar_menu_entry_t? the dropbar entry the symbol belongs to, if the symbol is shown inside a menu
---@field children dropbar_symbol_t[]? children of the symbol
---@field siblings dropbar_symbol_t[]? siblings of the symbol
---@field bar_idx integer? index of the symbol in the winbar
---@field entry_idx integer? index of the symbol in the menu entry
---@field sibling_idx integer? index of the symbol in its siblings
---@field range dropbar_symbol_range_t?
---@field on_click fun(this: dropbar_symbol_t, min_width: integer?, n_clicks: integer?, button: string?, modifiers: string?)|false|nil force disable on_click when false
---@field callback_idx integer? idx of the on_click callback in `_G.dropbar.callbacks[buf][win]`, use this to index callback function because `bar_idx` could change after truncate
---@field opts dropbar_symbol_opts_t? options passed to `dropbar_symbol_t:new()` when the symbols is created
---@field data table? any data associated with the symbol
---@field cache table caches string representation, length, etc. for the symbol
local dropbar_symbol_t = {}
function dropbar_symbol_t:__index(k)
return self._[k] or dropbar_symbol_t[k]
end
function dropbar_symbol_t:__newindex(k, v)
if k == 'name' or k == 'icon' then
self.cache.decorated_str = nil
self.cache.plain_str = nil
self.cache.displaywidth = nil
self.cache.bytewidth = nil
elseif k == 'name_hl' or k == 'icon_hl' then
self.cache.decorated_str = nil
end
self._[k] = v
end
---Create a new dropbar symbol instance with merged options
---@param opts dropbar_symbol_opts_t
---@return dropbar_symbol_t
function dropbar_symbol_t:merge(opts)
return dropbar_symbol_t:new(
setmetatable(
vim.tbl_deep_extend('force', self._, opts),
getmetatable(self._)
) --[[@as dropbar_symbol_opts_t]]
)
end
---@class dropbar_symbol_opts_t
---@field _ dropbar_symbol_t?
---@field name string?
---@field icon string?
---@field name_hl string?
---@field icon_hl string?
---@field win integer? the source window the symbol is shown in
---@field buf integer? the source buffer the symbol is defined in
---@field view table? original view of the source window
---@field bar dropbar_t? the winbar the symbol belongs to, if the symbol is shown inside a winbar
---@field menu dropbar_menu_t? menu associated with the winbar symbol, if the symbol is shown inside a winbar
---@field entry dropbar_menu_entry_t? the dropbar entry the symbol belongs to, if the symbol is shown inside a menu
---@field children dropbar_symbol_t[]? children of the symbol
---@field siblings dropbar_symbol_t[]? siblings of the symbol
---@field bar_idx integer? index of the symbol in the winbar
---@field entry_idx integer? index of the symbol in the menu entry
---@field sibling_idx integer? index of the symbol in its siblings
---@field range dropbar_symbol_range_t?
---@field on_click fun(this: dropbar_symbol_t, min_width: integer?, n_clicks: integer?, button: string?, modifiers: string?)|false|nil force disable on_click when false
---@field data table? any data associated with the symbol
---@field cache table? caches string representation, length, etc. for the symbol
---Default `on_click()` callback for dropbar symbols
---@param symbol dropbar_symbol_t
---@return nil
local function default_on_click(symbol, _, _, _, _)
-- Update current context highlights if the symbol
-- is shown inside a menu
if symbol.entry and symbol.entry.menu then
symbol.entry.menu:update_current_context_hl(symbol.entry.idx)
elseif symbol.bar then
symbol.bar:update_current_context_hl(symbol.bar_idx)
end
-- Determine menu configs
local prev_win = nil ---@type integer?
local entries_source = nil ---@type dropbar_symbol_t[]?
local init_cursor = nil ---@type integer[]?
local win_configs = {}
if symbol.bar then -- If symbol inside a dropbar
prev_win = symbol.bar.win
entries_source = symbol.opts.siblings
init_cursor = symbol.opts.sibling_idx and { symbol.opts.sibling_idx, 0 }
---@param tbl number[]
local function _sum(tbl)
local sum = 0
for _, v in ipairs(tbl) do
sum = sum + v
end
return sum
end
if symbol.bar.in_pick_mode then
win_configs.relative = 'win'
win_configs.win = vim.api.nvim_get_current_win()
win_configs.row = 0
win_configs.col = symbol.bar.padding.left
+ _sum(vim.tbl_map(
function(component)
return component:displaywidth()
+ symbol.bar.separator:displaywidth()
end,
vim.tbl_filter(function(component)
return component.bar_idx < symbol.bar_idx
end, symbol.bar.components)
))
end
elseif symbol.entry and symbol.entry.menu then -- If inside a menu
prev_win = symbol.entry.menu.win
entries_source = symbol.opts.children
end
-- Toggle existing menu
if symbol.menu then
symbol.menu:toggle({
prev_win = prev_win,
win_configs = win_configs,
})
return
end
-- Create a new menu for the symbol
if not entries_source or vim.tbl_isempty(entries_source) then
return
end
local menu = require('dropbar.menu')
symbol.menu = menu.dropbar_menu_t:new({
prev_win = prev_win,
cursor = init_cursor,
win_configs = win_configs,
---@param sym dropbar_symbol_t
entries = vim.tbl_map(function(sym)
local menu_indicator_icon = configs.opts.icons.ui.menu.indicator
local menu_indicator_on_click = nil
if not sym.children or vim.tbl_isempty(sym.children) then
menu_indicator_icon =
string.rep(' ', vim.fn.strdisplaywidth(menu_indicator_icon))
menu_indicator_on_click = false
end
return menu.dropbar_menu_entry_t:new({
components = {
sym:merge({
name = '',
icon = menu_indicator_icon,
icon_hl = 'dropbarIconUIIndicator',
on_click = menu_indicator_on_click,
}),
sym:merge({
on_click = function()
local current_menu = symbol.menu
while current_menu and current_menu.prev_menu do
current_menu = current_menu.prev_menu
end
if current_menu then
current_menu:close(false)
end
sym:jump()
end,
}),
},
})
end, entries_source),
})
symbol.menu:toggle()
end
---Create a dropbar symbol instance
---@param opts dropbar_symbol_opts_t? dropbar symbol structure
---@return dropbar_symbol_t
function dropbar_symbol_t:new(opts)
return setmetatable({
_ = setmetatable(
vim.tbl_deep_extend('force', {
name = '',
icon = '',
cache = {},
opts = opts,
on_click = opts and default_on_click,
}, opts or {}),
getmetatable(opts or {})
),
}, self)
end
---Delete a dropbar symbol instance
---@return nil
function dropbar_symbol_t:del()
if self.menu then
self.menu:del()
end
end
---Concatenate inside a dropbar symbol to get the final string
---@param plain boolean?
---@return string
function dropbar_symbol_t:cat(plain)
if self.cache.plain_str and plain then
return self.cache.plain_str
elseif self.cache.decorated_str and not plain then
return self.cache.decorated_str
end
if plain then
self.cache.plain_str = self.icon .. self.name
return self.cache.plain_str
end
-- Escape `%` characters to prevent unintended statusline evaluation
local icon_escaped = self.icon:gsub('%%', '%%%%')
local name_escaped = self.name:gsub('%%', '%%%%')
local icon_highlighted = utils.bar.hl(icon_escaped, self.icon_hl)
local name_highlighted = utils.bar.hl(name_escaped, self.name_hl)
if self.on_click and self.bar_idx then
self.cache.decorated_str = utils.bar.make_clickable(
icon_highlighted .. name_highlighted,
string.format(
'v:lua.dropbar.callbacks.buf%s.win%s.fn%s',
self.bar.buf,
self.bar.win,
self.callback_idx
)
)
return self.cache.decorated_str
end
self.cache.decorated_str = icon_highlighted .. name_highlighted
return self.cache.decorated_str
end
---Get the display length of the dropbar symbol
---@return number
function dropbar_symbol_t:displaywidth()
if self.cache.displaywidth then
return self.cache.displaywidth
end
self.cache.displaywidth = vim.fn.strdisplaywidth(self:cat(true))
return self.cache.displaywidth
end
---Get the byte length of the dropbar symbol
---@return number
function dropbar_symbol_t:bytewidth()
if self.cache.bytewidth then
return self.cache.bytewidth
end
self.cache.bytewidth = #self:cat(true)
return self.cache.bytewidth
end
---Jump to the start of the symbol associated with the winbar symbol
---@param reorient boolean? whether to set view after jumping, default true
---@return nil
function dropbar_symbol_t:jump(reorient)
if not self.range or not self.win then
return
end
vim.cmd.normal({ "m'", bang = true })
vim.api.nvim_win_set_cursor(self.win, {
self.range.start.line + 1,
self.range.start.character,
})
if reorient ~= false then
vim.api.nvim_win_call(self.win, function()
configs.opts.symbol.jump.reorient(self.win, self.range)
end)
end
end
---Preview the symbol in the source window
---@param orig_view table? use this view as original view
---@return nil
function dropbar_symbol_t:preview(orig_view)
if not self.range or not self.win or not self.buf then
return
end
self.view = orig_view or vim.api.nvim_win_call(self.win, vim.fn.winsaveview)
utils.hl.range_single(self.buf, 'DropBarPreview', self.range)
vim.api.nvim_win_set_cursor(self.win, {
self.range.start.line + 1,
self.range.start.character,
})
vim.api.nvim_win_call(self.win, function()
configs.opts.symbol.preview.reorient(self.win, self.range)
end)
end
---Clear the preview highlights in the source window
---@return nil
function dropbar_symbol_t:preview_restore_hl()
if self.buf then
utils.hl.range_single(self.buf, 'DropBarPreview')
end
end
---Restore the source window to its original view
---@return nil
function dropbar_symbol_t:preview_restore_view()
if self.view and self.win then
vim.api.nvim_win_call(self.win, function()
vim.fn.winrestview(self.view)
end)
end
end
---Temporarily change the content of a dropbar symbol
---@param field string
---@param new_val any
---@return nil
function dropbar_symbol_t:swap_field(field, new_val)
self.data = self.data or {}
self.data.swap = self.data.swap or {}
self.data.swapped = self.data.swapped or {}
self.data.swap[field] = self.data.swap[field] or self[field]
table.insert(self.data.swapped, field)
self[field] = new_val
end
---Restore the content of a dropbar symbol
---@return nil
function dropbar_symbol_t:restore()
if not self.data or not self.data.swap then
return
end
for _, field in ipairs(self.data.swapped) do
self[field] = self.data.swap[field]
end
self.data.swap = nil
self.data.swapped = nil
end
---@class dropbar_opts_t
---@field buf integer?
---@field win integer?
---@field sources (dropbar_source_t[]|fun(buf: integer, win: integer):dropbar_source_t[])?
---@field separator dropbar_symbol_t?
---@field extends dropbar_symbol_t?
---@field padding {left: integer, right: integer}?
---@class dropbar_t
---@field buf integer
---@field win integer
---@field sources dropbar_source_t[]
---@field separator dropbar_symbol_t
---@field padding {left: integer, right: integer}
---@field extends dropbar_symbol_t
---@field components dropbar_symbol_t[]
---@field string_cache string
---@field in_pick_mode boolean?
---@field symbol_on_hover dropbar_symbol_t?
---@field last_update_request_time number? timestamp of the last update request in ms, see :h uv.now()
local dropbar_t = {}
dropbar_t.__index = dropbar_t
---Create a dropbar instance
---@param opts dropbar_opts_t?
---@return dropbar_t
function dropbar_t:new(opts)
local dropbar = setmetatable(
vim.tbl_deep_extend('force', {
buf = vim.api.nvim_get_current_buf(),
win = vim.api.nvim_get_current_win(),
components = {},
string_cache = '',
sources = {},
separator = dropbar_symbol_t:new({
icon = configs.opts.icons.ui.bar.separator,
icon_hl = 'DropBarIconUISeparator',
}),
extends = dropbar_symbol_t:new({
icon = configs.opts.icons.ui.bar.extends,
}),
padding = configs.opts.bar.padding,
}, opts or {}),
self
)
-- vim.tbl_deep_extend drops metatables
setmetatable(dropbar.separator, dropbar_symbol_t)
setmetatable(dropbar.extends, dropbar_symbol_t)
return dropbar
end
---Delete a dropbar instance
---@return nil
function dropbar_t:del()
_G.dropbar.bars[self.buf][self.win] = nil
_G.dropbar.callbacks['buf' .. self.buf]['win' .. self.win] = nil
for _, component in ipairs(self.components) do
component:del()
end
end
---Get the display length of the dropbar
---@return integer
function dropbar_t:displaywidth()
return vim.fn.strdisplaywidth(self:cat(true))
end
---Truncate the dropbar to fit the window width
---Side effect: change dropbar.components
---@return nil
function dropbar_t:truncate()
if not self.win or not vim.api.nvim_win_is_valid(self.win) then
self:del()
return
end
local win_width = vim.api.nvim_win_get_width(self.win)
local len = self:displaywidth()
local delta = len - win_width
for _, component in ipairs(self.components) do
if delta <= 0 then
break
end
local name_len = vim.fn.strdisplaywidth(component.name)
local min_len =
vim.fn.strdisplaywidth(component.name:sub(1, 1) .. self.extends.icon)
if name_len > min_len then
component.name = vim.fn.strcharpart(
component.name,
0,
math.max(1, name_len - delta - 1)
) .. self.extends.icon
delta = delta - name_len + vim.fn.strdisplaywidth(component.name)
end
end
-- Return if dropbar already fits in the window
-- or there's only one or less symbol in dropbar (cannot truncate)
if delta <= 0 or #self.components <= 1 then
return
end
-- Consider replacing symbols at the start of the winbar with an extends sign
local sym_extends = dropbar_symbol_t:new({
icon = configs.opts.icons.ui.bar.extends,
icon_hl = 'WinBarIconUIExtends',
on_click = false,
bar = self,
})
local extends_width = sym_extends:displaywidth()
local sep_width = self.separator:displaywidth()
local sym_first = self.components[1]
local wdiff = extends_width - sym_first:displaywidth()
-- Extends width larger than the first symbol, removing the
-- first symbol will not help
if wdiff >= 0 then
return
end
-- Replace the first symbol with the extends sign and update delta
self.components[1] = sym_extends
delta = delta + wdiff
sym_first:del()
-- Keep removing symbols from the start, notice that self.components[1] is
-- the extends symbol
while delta > 0 and #self.components > 1 do
local sym_remove = self.components[2]
table.remove(self.components, 2)
delta = delta - sym_remove:displaywidth() - sep_width
sym_remove:del()
end
-- Update bar_idx of each symbol
for i, component in ipairs(self.components) do
component.bar_idx = i
end
end
---Concatenate dropbar into a string with separator and highlight
---@param plain boolean?
---@return string
function dropbar_t:cat(plain)
if vim.tbl_isempty(self.components) then
return ''
end
local result = nil
for _, component in ipairs(self.components) do
result = result
and result .. self.separator:cat(plain) .. component:cat(plain)
or component:cat(plain)
end
local padding_left = string.rep(' ', self.padding.left)
local padding_right = string.rep(' ', self.padding.right)
result = result and padding_left .. result .. padding_right or ''
-- Must add highlights to padding when `plain` is false, else nvim will
-- automatically truncate it
return plain and result or utils.bar.hl(result, '')
end
---Reevaluate dropbar string from components and redraw dropbar
---@return nil
function dropbar_t:redraw()
if configs.opts.bar.truncate then
self:truncate()
end
local new_str = self:cat()
if new_str ~= self.string_cache then
self.string_cache = new_str
vim.cmd.redrawstatus({
bang = true,
mods = { emsg_silent = true },
})
end
end
---Update dropbar components from sources and redraw dropbar, supposed to be
---called at CursorMoved, CursorMovedI, TextChanged, and TextChangedI
---Not updating when executing a macro
---@return nil
function dropbar_t:update()
local request_time = vim.uv.now()
self.last_update_request_time = request_time
vim.defer_fn(function()
-- Cancel current update if
-- 1. another update request is sent within the update interval
-- 2. is inside pick mode
-- 3. is executing a macro
if
self.last_update_request_time ~= request_time
or self.in_pick_mode
or vim.fn.reg_executing() ~= ''
then
return
end
if
not self.win
or not self.buf
or not vim.api.nvim_win_is_valid(self.win)
or not vim.api.nvim_buf_is_valid(self.buf)
then
self:del()
return
end
local cursor = vim.api.nvim_win_get_cursor(self.win)
for _, component in ipairs(self.components) do
component:del()
end
self.components = {}
_G.dropbar.callbacks['buf' .. self.buf]['win' .. self.win] = {}
for _, source in ipairs(self.sources) do
local symbols = source.get_symbols(self.buf, self.win, cursor)
for _, symbol in ipairs(symbols) do
symbol.bar_idx = #self.components + 1
symbol.callback_idx = symbol.bar_idx
symbol.bar = self
table.insert(self.components, symbol)
-- Register on_click callback for each symbol
if symbol.on_click then
---@param min_width integer 0 if no N specified
---@param n_clicks integer number of clicks
---@param button string mouse button used
---@param modifiers string modifiers used
---@return nil
_G.dropbar.callbacks['buf' .. self.buf]['win' .. self.win]['fn' .. symbol.callback_idx] = function(
min_width,
n_clicks,
button,
modifiers
)
symbol:on_click(min_width, n_clicks, button, modifiers)
end
end
end
end
self:redraw()
end, configs.opts.bar.update_debounce)
end
---Execute a function in pick mode
---Side effect: change dropbar.in_pick_mode
---@generic T
---@param fn fun(...): T?
---@return T?
function dropbar_t:pick_mode_wrap(fn, ...)
local pick_mode = self.in_pick_mode
self.in_pick_mode = true
local results = { fn(...) }
self.in_pick_mode = pick_mode
return unpack(results)
end
---Pick a component from dropbar
---Side effect: change dropbar.in_pick_mode, dropbar.components
---@param idx integer? index of the component to pick
---@return nil
function dropbar_t:pick(idx)
self:pick_mode_wrap(function()
if #self.components == 0 then
return
end
if idx then
if self.components[idx] then
self.components[idx]:on_click()
end
return
end
---Clickable symbols
---@type dropbar_symbol_t[]
local clickables = vim.tbl_filter(function(component)
return component.on_click
end, self.components)
local n_clickables = #clickables
-- If has only one component, pick it directly
if n_clickables == 1 then
clickables[1]:on_click()
return
end
-- Else assign the chars on each component and wait for user input to pick
local shortcuts = {}
local pivots = {}
for i = 1, #configs.opts.bar.pick.pivots do
table.insert(pivots, configs.opts.bar.pick.pivots:sub(i, i))
end
local n_pivots = #pivots
local n_chars = math.ceil(math.log(n_clickables, n_pivots))
for exp = 0, n_chars - 1 do
for i = 1, n_clickables do
local new_char =
pivots[math.floor((i - 1) / n_pivots ^ exp) % n_pivots + 1]
shortcuts[i] = new_char .. (shortcuts[i] or '')
end
end
-- Display the chars on each component
for i, component in ipairs(clickables) do
local shortcut = shortcuts[i]
local icon_width = vim.fn.strdisplaywidth(component.icon)
component:swap_field(
'icon',
shortcut .. string.rep(' ', icon_width - #shortcut)
)
component:swap_field('icon_hl', 'DropBarIconUIPickPivot')
end
self:redraw()
-- Read the input from user
local shortcut_read = ''
for _ = 1, n_chars do
shortcut_read = shortcut_read .. vim.fn.nr2char(vim.fn.getchar())
end
-- Restore the original content of each component
for _, component in ipairs(clickables) do
component:restore()
end
self:redraw()
-- Execute the on_click callback of the component
for i, shortcut in ipairs(shortcuts) do
local sym = clickables[i]
if shortcut == shortcut_read and sym.on_click then
sym:on_click()
break
end
end
end)
end
---Get the component at the given position in the winbar
---@param col integer 0-indexed, byte-indexed
---@param look_ahead boolean? whether to look ahead for the next component if the given position does not contain a component
---@return dropbar_symbol_t?
---@return {start: integer, end: integer}? range of the component in the menu, byte-indexed, 0-indexed, start-inclusive, end-exclusive
function dropbar_t:get_component_at(col, look_ahead)
local col_offset = self.padding.left
for _, component in ipairs(self.components) do
-- Use display width instead of byte width here because
-- vim.fn.getmousepos().wincol is the display width of the mouse position
-- and also the menu window needs to be opened with relative to the
-- display position of the winbar symbol to be aligned with the symbol
-- on the screen
local component_len = component:displaywidth()
if
(look_ahead or col >= col_offset) and col < col_offset + component_len
then
return component,
{
start = col_offset,
['end'] = col_offset + component_len,
}
end
col_offset = col_offset + component_len + self.separator:displaywidth()
end
return nil, nil
end
---Highlight the symbol at bar_idx as current context
---@param bar_idx integer see dropbar_symbol_t.bar_idx
---@return nil
function dropbar_t:update_current_context_hl(bar_idx)
local symbol = self.components[bar_idx]
if not symbol then
return
end
local hl_currentcontext_icon = '_DropBarIconCurrentContext'
local hl_currentcontext_name = '_DropBarCurrentContext'
symbol:restore()
vim.api.nvim_set_hl(
0,
hl_currentcontext_icon,
utils.hl.merge('WinBarNC', symbol.icon_hl, 'DropBarCurrentContext')
)
vim.api.nvim_set_hl(
0,
hl_currentcontext_name,
utils.hl.merge('WinBarNC', symbol.name_hl, 'DropBarCurrentContext')
)
symbol:swap_field('icon_hl', hl_currentcontext_icon)
symbol:swap_field('name_hl', hl_currentcontext_name)
self:redraw()
end
---Highlight the symbol at col as if the mouse is hovering on it
---@param col integer? displaywidth-indexed, 0-indexed mouse position, nil to clear the hover highlights
---@return nil
function dropbar_t:update_hover_hl(col)
local symbol = col and self:get_component_at(col)
if not symbol then
if self.symbol_on_hover then
self.symbol_on_hover:restore()
self.symbol_on_hover = nil
self:redraw()
end
return
end
if not symbol.on_click or symbol == self.symbol_on_hover then
return
end
local hl_hover_icon = '_DropBarIconHover'
local hl_hover_name = '_DropBarHover'
local hl_winbar = vim.api.nvim_get_current_win() == self.win and 'WinBar'
or 'WinbarNC'
vim.api.nvim_set_hl(
0,
hl_hover_icon,
utils.hl.merge(hl_winbar, symbol.icon_hl, 'DropBarHover')
)
vim.api.nvim_set_hl(
0,
hl_hover_name,
utils.hl.merge(hl_winbar, symbol.name_hl, 'DropBarHover')
)
symbol:swap_field('icon_hl', hl_hover_icon)
symbol:swap_field('name_hl', hl_hover_name)
if self.symbol_on_hover then
self.symbol_on_hover:restore()
end
self.symbol_on_hover = symbol
self:redraw()
end
---Get the string representation of the dropbar
---@return string
function dropbar_t:__tostring()
if vim.tbl_isempty(self.components) then
self:update()
end
return self.string_cache
end
return {
dropbar_t = dropbar_t,
dropbar_symbol_t = dropbar_symbol_t,
}