forked from nvim-mini/mini.nvim
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathstatusline.lua
More file actions
575 lines (507 loc) · 21.9 KB
/
Copy pathstatusline.lua
File metadata and controls
575 lines (507 loc) · 21.9 KB
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
-- MIT License Copyright (c) 2021 Evgeni Chasnovski
-- Documentation ==============================================================
--- Minimal and fast statusline with opinionated default look.
---
--- Features:
--- - Define own custom statusline structure for active and inactive windows.
--- This is done with a function which should return string appropriate for
--- |statusline|. Its code should be similar to default one with structure:
--- - Compute string data for every section you want to be displayed.
--- - Combine them in groups with |MiniStatusline.combine_groups()|.
--- - Built-in active mode indicator with colors.
--- - Sections can hide information when window is too narrow (specific window
--- width is configurable per section).
---
--- # Dependencies~
---
--- Suggested dependencies (provide extra functionality, statusline will work
--- without them):
--- - Nerd font (to support extra icons).
--- - Plugin 'lewis6991/gitsigns.nvim' for Git information in
--- |MiniStatusline.section_git|. If missing, no section will be shown.
--- - Plugin 'nvim-tree/nvim-web-devicons' for filetype icons in
--- `MiniStatusline.section_fileinfo`. If missing, no icons will be shown.
---
--- # Setup~
---
--- This module needs a setup with `require('mini.statusline').setup({})`
--- (replace `{}` with your `config` table). It will create global Lua table
--- `MiniStatusline` which you can use for scripting or manually (with
--- `:lua MiniStatusline.*`).
---
--- See |MiniStatusline.config| for `config` structure and default values. For
--- some content examples, see |MiniStatusline-example-content|.
---
--- You can override runtime config settings locally to buffer inside
--- `vim.b.ministatusline_config` which should have same structure as
--- `MiniStatusline.config`. See |mini.nvim-buffer-local-config| for more details.
---
--- # Highlight groups~
---
--- Highlight depending on mode (second output from |MiniStatusline.section_mode|):
--- * `MiniStatuslineModeNormal` - Normal mode.
--- * `MiniStatuslineModeInsert` - Insert mode.
--- * `MiniStatuslineModeVisual` - Visual mode.
--- * `MiniStatuslineModeReplace` - Replace mode.
--- * `MiniStatuslineModeCommand` - Command mode.
--- * `MiniStatuslineModeOther` - other modes (like Terminal, etc.).
---
--- Highlight used in default statusline:
--- * `MiniStatuslineDevinfo` - for "dev info" group
--- (|MiniStatusline.section_git| and |MiniStatusline.section_diagnostics|).
--- * `MiniStatuslineFilename` - for |MiniStatusline.section_filename| section.
--- * `MiniStatuslineFileinfo` - for |MiniStatusline.section_fileinfo| section.
---
--- Other groups:
--- * `MiniStatuslineInactive` - highliting in not focused window.
---
--- To change any highlight group, modify it directly with |:highlight|.
---
--- # Disabling~
---
--- To disable (show empty statusline), set `g:ministatusline_disable`
--- (globally) or `b:ministatusline_disable` (for a buffer) to `v:true`.
--- Considering high number of different scenarios and customization
--- intentions, writing exact rules for disabling module's functionality is
--- left to user. See |mini.nvim-disabling-recipes| for common recipes.
---@tag mini.statusline
---@tag MiniStatusline
--- Example content
---
--- # Default content~
---
--- This function is used as default value for active content:
--- >
--- function()
--- local mode, mode_hl = MiniStatusline.section_mode({ trunc_width = 120 })
--- local git = MiniStatusline.section_git({ trunc_width = 75 })
--- local diagnostics = MiniStatusline.section_diagnostics({ trunc_width = 75 })
--- local filename = MiniStatusline.section_filename({ trunc_width = 140 })
--- local fileinfo = MiniStatusline.section_fileinfo({ trunc_width = 120 })
--- local location = MiniStatusline.section_location({ trunc_width = 75 })
---
--- return MiniStatusline.combine_groups({
--- { hl = mode_hl, strings = { mode } },
--- { hl = 'MiniStatuslineDevinfo', strings = { git, diagnostics } },
--- '%<', -- Mark general truncate point
--- { hl = 'MiniStatuslineFilename', strings = { filename } },
--- '%=', -- End left alignment
--- { hl = 'MiniStatuslineFileinfo', strings = { fileinfo } },
--- { hl = mode_hl, strings = { location } },
--- })
--- end
--- <
--- # Show boolean options~
---
--- To compute section string for boolean option use variation of this code
--- snippet inside content function (you can modify option itself, truncation
--- width, short and long displayed names):
--- >
--- local spell = vim.wo.spell and (MiniStatusline.is_truncated(120) and 'S' or 'SPELL') or ''
--- <
--- Here `x and y or z` is a common Lua way of doing ternary operator: if `x`
--- is `true`-ish then return `y`, if not - return `z`.
---@tag MiniStatusline-example-content
---@alias __statusline_args table Section arguments.
---@alias __statusline_section string Section string.
-- Module definition ==========================================================
local MiniStatusline = {}
local H = {}
--- Module setup
---
---@param config table|nil Module config table. See |MiniStatusline.config|.
---
---@usage `require('mini.statusline').setup({})` (replace `{}` with your `config` table)
MiniStatusline.setup = function(config)
-- TODO: Remove after Neovim<=0.6 support is dropped
if vim.fn.has('nvim-0.7') == 0 then
vim.notify(
'(mini.statusline) Neovim<0.7 is soft deprecated (module works but not supported).'
.. ' It will be deprecated after Neovim 0.9.0 release (module will not work).'
.. ' Please update your Neovim version.'
)
end
-- Export module
_G.MiniStatusline = MiniStatusline
-- Setup config
config = H.setup_config(config)
-- Apply config
H.apply_config(config)
-- Module behavior
vim.api.nvim_exec(
[[augroup MiniStatusline
au!
au WinEnter,BufEnter * setlocal statusline=%!v:lua.MiniStatusline.active()
au WinLeave,BufLeave * setlocal statusline=%!v:lua.MiniStatusline.inactive()
augroup END]],
false
)
-- Create highlighting
vim.api.nvim_exec(
[[hi default link MiniStatuslineModeNormal Cursor
hi default link MiniStatuslineModeInsert DiffChange
hi default link MiniStatuslineModeVisual DiffAdd
hi default link MiniStatuslineModeReplace DiffDelete
hi default link MiniStatuslineModeCommand DiffText
hi default link MiniStatuslineModeOther IncSearch
hi default link MiniStatuslineDevinfo StatusLine
hi default link MiniStatuslineFilename StatusLineNC
hi default link MiniStatuslineFileinfo StatusLine
hi default link MiniStatuslineInactive StatusLineNC]],
false
)
end
--- Module config
---
--- Default values:
---@eval return MiniDoc.afterlines_to_code(MiniDoc.current.eval_section)
MiniStatusline.config = {
-- Content of statusline as functions which return statusline string. See
-- `:h statusline` and code of default contents (used instead of `nil`).
content = {
-- Content for active window
active = nil,
-- Content for inactive window(s)
inactive = nil,
},
-- Whether to use icons by default
use_icons = true,
-- Whether to set Vim's settings for statusline (make it always shown with
-- 'laststatus' set to 2). To use global statusline in Neovim>=0.7.0, set
-- this to `false` and 'laststatus' to 3.
set_vim_settings = true,
}
--minidoc_afterlines_end
-- Module functionality =======================================================
--- Compute content for active window
MiniStatusline.active = function()
if H.is_disabled() then return '' end
return (H.get_config().content.active or H.default_content_active)()
end
--- Compute content for inactive window
MiniStatusline.inactive = function()
if H.is_disabled() then return '' end
return (H.get_config().content.inactive or H.default_content_inactive)()
end
--- Combine groups of sections
---
--- Each group can be either a string or a table with fields `hl` (group's
--- highlight group) and `strings` (strings representing sections).
---
--- General idea of this function is as follows;
--- - String group is used as is (useful for special strings like `%<` or `%=`).
--- - Each table group has own highlighting in `hl` field (if missing, the
--- previous one is used) and string parts in `strings` field. Non-empty
--- strings from `strings` are separated by one space. Non-empty groups are
--- separated by two spaces (one for each highlighting).
---
---@param groups table Array of groups.
---
---@return string String suitable for 'statusline'.
MiniStatusline.combine_groups = function(groups)
local parts = vim.tbl_map(function(s)
--stylua: ignore start
if type(s) == 'string' then return s end
if type(s) ~= 'table' then return '' end
local string_arr = vim.tbl_filter(function(x) return type(x) == 'string' and x ~= '' end, s.strings or {})
local str = table.concat(string_arr, ' ')
-- Use previous highlight group
if s.hl == nil then
return (' %s '):format(str)
end
-- Allow using this highlight group later
if str:len() == 0 then
return string.format('%%#%s#', s.hl)
end
return string.format('%%#%s# %s ', s.hl, str)
--stylua: ignore end
end, groups)
return table.concat(parts, '')
end
--- Decide whether to truncate
---
--- This basically computes window width and compares it to `trunc_width`: if
--- window is smaller then truncate; otherwise don't. Don't truncate by
--- default.
---
--- Use this to manually decide if section needs truncation or not.
---
---@param trunc_width number|nil Truncation width. If `nil`, output is `false`.
---
---@return boolean Whether to truncate.
MiniStatusline.is_truncated = function(trunc_width)
-- Use -1 to default to 'not truncated'
local cur_width = vim.o.laststatus == 3 and vim.o.columns or vim.api.nvim_win_get_width(0)
return cur_width < (trunc_width or -1)
end
-- Sections ===================================================================
-- Functions should return output text without whitespace on sides.
-- Return empty string to omit section.
--- Section for Vim |mode()|
---
--- Short output is returned if window width is lower than `args.trunc_width`.
---
---@param args __statusline_args
---
---@return ... Section string and mode's highlight group.
MiniStatusline.section_mode = function(args)
local mode_info = H.modes[vim.fn.mode()]
local mode = MiniStatusline.is_truncated(args.trunc_width) and mode_info.short or mode_info.long
return mode, mode_info.hl
end
--- Section for Git information
---
--- Normal output contains name of `HEAD` (via |b:gitsigns_head|) and chunk
--- information (via |b:gitsigns_status|). Short output - only name of `HEAD`.
--- Note: requires 'lewis6991/gitsigns' plugin.
---
--- Short output is returned if window width is lower than `args.trunc_width`.
---
---@param args __statusline_args Use `args.icon` to supply your own icon.
---
---@return __statusline_section
MiniStatusline.section_git = function(args)
if H.isnt_normal_buffer() then return '' end
local head = vim.b.gitsigns_head or '-'
local signs = MiniStatusline.is_truncated(args.trunc_width) and '' or (vim.b.gitsigns_status or '')
local icon = args.icon or (H.get_config().use_icons and '' or 'Git')
if signs == '' then
if head == '-' or head == '' then return '' end
return string.format('%s %s', icon, head)
end
return string.format('%s %s %s', icon, head, signs)
end
--- Section for Neovim's builtin diagnostics
---
--- Shows nothing if there is no attached LSP clients or for short output.
--- Otherwise uses builtin Neovim capabilities to compute and show number of
--- errors ('E'), warnings ('W'), information ('I'), and hints ('H').
---
--- Short output is returned if window width is lower than `args.trunc_width`.
---
---@param args __statusline_args Use `args.icon` to supply your own icon.
---
---@return __statusline_section
MiniStatusline.section_diagnostics = function(args)
-- Assumption: there are no attached clients if table
-- `vim.lsp.buf_get_clients()` is empty
local hasnt_attached_client = next(vim.lsp.buf_get_clients()) == nil
local dont_show_lsp = MiniStatusline.is_truncated(args.trunc_width) or H.isnt_normal_buffer() or hasnt_attached_client
if dont_show_lsp then return '' end
-- Construct diagnostic info using predefined order
local t = {}
for _, level in ipairs(H.diagnostic_levels) do
local n = H.get_diagnostic_count(level.id)
-- Add level info only if diagnostic is present
if n > 0 then table.insert(t, string.format(' %s%s', level.sign, n)) end
end
local icon = args.icon or (H.get_config().use_icons and '' or 'LSP')
if vim.tbl_count(t) == 0 then return ('%s -'):format(icon) end
return string.format('%s%s', icon, table.concat(t, ''))
end
--- Section for file name
---
--- Show full file name or relative in short output.
---
--- Short output is returned if window width is lower than `args.trunc_width`.
---
---@param args __statusline_args
---
---@return __statusline_section
MiniStatusline.section_filename = function(args)
-- In terminal always use plain name
if vim.bo.buftype == 'terminal' then
return '%t'
elseif MiniStatusline.is_truncated(args.trunc_width) then
-- File name with 'truncate', 'modified', 'readonly' flags
-- Use relative path if truncated
return '%f%m%r'
else
-- Use fullpath if not truncated
return '%F%m%r'
end
end
--- Section for file information
---
--- Short output contains only extension and is returned if window width is
--- lower than `args.trunc_width`.
---
---@param args __statusline_args
---
---@return __statusline_section
MiniStatusline.section_fileinfo = function(args)
local filetype = vim.bo.filetype
-- Don't show anything if can't detect file type or not inside a "normal
-- buffer"
if (filetype == '') or H.isnt_normal_buffer() then return '' end
-- Add filetype icon
local icon = H.get_filetype_icon()
if icon ~= '' then filetype = string.format('%s %s', icon, filetype) end
-- Construct output string if truncated
if MiniStatusline.is_truncated(args.trunc_width) then return filetype end
-- Construct output string with extra file info
local encoding = vim.bo.fileencoding or vim.bo.encoding
local format = vim.bo.fileformat
local size = H.get_filesize()
return string.format('%s %s[%s] %s', filetype, encoding, format, size)
end
--- Section for location inside buffer
---
--- Show location inside buffer in the form:
--- - Normal: '<cursor line>|<total lines>│<cursor column>|<total columns>'.
--- - Short: '<cursor line>│<cursor column>'.
---
--- Short output is returned if window width is lower than `args.trunc_width`.
---
---@param args __statusline_args
---
---@return __statusline_section
MiniStatusline.section_location = function(args)
-- Use virtual column number to allow update when past last column
if MiniStatusline.is_truncated(args.trunc_width) then return '%l│%2v' end
-- Use `virtcol()` to correctly handle multi-byte characters
return '%l|%L│%2v|%-2{virtcol("$") - 1}'
end
--- Section for current search count
---
--- Show the current status of |searchcount()|. Empty output is returned if
--- window width is lower than `args.trunc_width`, search highlighting is not
--- on (see |v:hlsearch|), or if number of search result is 0.
---
--- `args.options` is forwarded to |searchcount()|. By default it recomputes
--- data on every call which can be computationally expensive (although still
--- usually same order of magnitude as 0.1 ms). To prevent this, supply
--- `args.options = {recompute = false}`.
---
---@param args __statusline_args
---
---@return __statusline_section
MiniStatusline.section_searchcount = function(args)
if vim.v.hlsearch == 0 or MiniStatusline.is_truncated(args.trunc_width) then return '' end
-- `searchcount()` can return errors because it is evaluated very often in
-- statusline. For example, when typing `/` followed by `\(`, it gives E54.
local ok, s_count = pcall(vim.fn.searchcount, (args or {}).options or { recompute = true })
if not ok or s_count.current == nil or s_count.total == 0 then return '' end
if s_count.incomplete == 1 then return '?/?' end
local too_many = ('>%d'):format(s_count.maxcount)
local current = s_count.current > s_count.maxcount and too_many or s_count.current
local total = s_count.total > s_count.maxcount and too_many or s_count.total
return ('%s/%s'):format(current, total)
end
-- Helper data ================================================================
-- Module default config
H.default_config = MiniStatusline.config
-- Showed diagnostic levels
H.diagnostic_levels = {
{ id = vim.diagnostic.severity.ERROR, sign = 'E' },
{ id = vim.diagnostic.severity.WARN, sign = 'W' },
{ id = vim.diagnostic.severity.INFO, sign = 'I' },
{ id = vim.diagnostic.severity.HINT, sign = 'H' },
}
-- Helper functionality =======================================================
-- Settings -------------------------------------------------------------------
H.setup_config = function(config)
-- General idea: if some table elements are not present in user-supplied
-- `config`, take them from default config
vim.validate({ config = { config, 'table', true } })
config = vim.tbl_deep_extend('force', H.default_config, config or {})
-- Validate per nesting level to produce correct error message
vim.validate({
content = { config.content, 'table' },
set_vim_settings = { config.set_vim_settings, 'boolean' },
use_icons = { config.use_icons, 'boolean' },
})
vim.validate({
['content.active'] = { config.content.active, 'function', true },
['content.inactive'] = { config.content.inactive, 'function', true },
})
return config
end
H.apply_config = function(config)
MiniStatusline.config = config
-- Set settings to ensure statusline is displayed properly
if config.set_vim_settings then
vim.o.laststatus = 2 -- Always show statusline
end
end
H.is_disabled = function() return vim.g.ministatusline_disable == true or vim.b.ministatusline_disable == true end
H.get_config = function(config)
return vim.tbl_deep_extend('force', MiniStatusline.config, vim.b.ministatusline_config or {}, config or {})
end
-- Mode -----------------------------------------------------------------------
-- Custom `^V` and `^S` symbols to make this file appropriate for copy-paste
-- (otherwise those symbols are not displayed).
local CTRL_S = vim.api.nvim_replace_termcodes('<C-S>', true, true, true)
local CTRL_V = vim.api.nvim_replace_termcodes('<C-V>', true, true, true)
-- stylua: ignore start
H.modes = setmetatable({
['n'] = { long = 'Normal', short = 'N', hl = 'MiniStatuslineModeNormal' },
['v'] = { long = 'Visual', short = 'V', hl = 'MiniStatuslineModeVisual' },
['V'] = { long = 'V-Line', short = 'V-L', hl = 'MiniStatuslineModeVisual' },
[CTRL_V] = { long = 'V-Block', short = 'V-B', hl = 'MiniStatuslineModeVisual' },
['s'] = { long = 'Select', short = 'S', hl = 'MiniStatuslineModeVisual' },
['S'] = { long = 'S-Line', short = 'S-L', hl = 'MiniStatuslineModeVisual' },
[CTRL_S] = { long = 'S-Block', short = 'S-B', hl = 'MiniStatuslineModeVisual' },
['i'] = { long = 'Insert', short = 'I', hl = 'MiniStatuslineModeInsert' },
['R'] = { long = 'Replace', short = 'R', hl = 'MiniStatuslineModeReplace' },
['c'] = { long = 'Command', short = 'C', hl = 'MiniStatuslineModeCommand' },
['r'] = { long = 'Prompt', short = 'P', hl = 'MiniStatuslineModeOther' },
['!'] = { long = 'Shell', short = 'Sh', hl = 'MiniStatuslineModeOther' },
['t'] = { long = 'Terminal', short = 'T', hl = 'MiniStatuslineModeOther' },
}, {
-- By default return 'Unknown' but this shouldn't be needed
__index = function()
return { long = 'Unknown', short = 'U', hl = '%#MiniStatuslineModeOther#' }
end,
})
-- stylua: ignore end
-- Default content ------------------------------------------------------------
H.default_content_active = function()
-- stylua: ignore start
local mode, mode_hl = MiniStatusline.section_mode({ trunc_width = 120 })
local git = MiniStatusline.section_git({ trunc_width = 75 })
local diagnostics = MiniStatusline.section_diagnostics({ trunc_width = 75 })
local filename = MiniStatusline.section_filename({ trunc_width = 140 })
local fileinfo = MiniStatusline.section_fileinfo({ trunc_width = 120 })
local location = MiniStatusline.section_location({ trunc_width = 75 })
-- Usage of `MiniStatusline.combine_groups()` ensures highlighting and
-- correct padding with spaces between groups (accounts for 'missing'
-- sections, etc.)
return MiniStatusline.combine_groups({
{ hl = mode_hl, strings = { mode } },
{ hl = 'MiniStatuslineDevinfo', strings = { git, diagnostics } },
'%<', -- Mark general truncate point
{ hl = 'MiniStatuslineFilename', strings = { filename } },
'%=', -- End left alignment
{ hl = 'MiniStatuslineFileinfo', strings = { fileinfo } },
{ hl = mode_hl, strings = { location } },
})
-- stylua: ignore end
end
H.default_content_inactive = function() return '%#MiniStatuslineInactive#%F%=' end
-- Utilities ------------------------------------------------------------------
H.isnt_normal_buffer = function()
-- For more information see ":h buftype"
return vim.bo.buftype ~= ''
end
H.get_filesize = function()
local size = vim.fn.getfsize(vim.fn.getreg('%'))
if size < 1024 then
return string.format('%dB', size)
elseif size < 1048576 then
return string.format('%.2fKiB', size / 1024)
else
return string.format('%.2fMiB', size / 1048576)
end
end
H.get_filetype_icon = function()
-- Skip if NerdFonts is disabled
if not H.get_config().use_icons then return '' end
-- Have this `require()` here to not depend on plugin initialization order
local has_devicons, devicons = pcall(require, 'nvim-web-devicons')
if not has_devicons then return '' end
local file_name, file_ext = vim.fn.expand('%:t'), vim.fn.expand('%:e')
return devicons.get_icon(file_name, file_ext, { default = true })
end
H.get_diagnostic_count = function(id) return #vim.diagnostic.get(0, { severity = id }) end
return MiniStatusline