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
584 lines (526 loc) · 20.8 KB
/
Copy pathstatusline.lua
File metadata and controls
584 lines (526 loc) · 20.8 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
576
577
578
579
580
581
582
583
584
-- MIT License Copyright (c) 2021 Evgeni Chasnovski
-- Documentation ==============================================================
---@brief [[
--- Custom minimal and fast statusline module with opinionated default look.
--- Special features: change color depending on current mode and compact
--- version of sections activated when window width is small enough.
---
--- Features:
--- - Built-in active mode indicator with colors.
--- - Sections can hide information when window is too narrow (specific window
--- width is configurable per section).
--- - 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()|.
---
--- # 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 'kyazdani42/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.*`).
---
--- Default `config`:
--- <code>
--- {
--- -- Content of statusline as functions which return statusline string. See `:h
--- -- statusline` and code of default contents (used when `nil` is supplied).
--- content = {
--- -- Content for active window
--- active = nil,
---
--- -- Content for inactive window(s)
--- inactive = nil,
--- },
---
--- -- Whether to set Vim's settings for statusline (make it always shown)
--- set_vim_settings = true,
--- }
--- </code>
--- # Example content
---
--- ## Default content
---
--- This function is used as default value for active content:
--- <code>
--- 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
--- </code>
--- ## 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):
--- <code>
--- local spell = vim.wo.spell and (MiniStatusline.is_truncated(120) and 'S' or 'SPELL') or ''
--- </code>
--- 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`.
---
--- # Highlight groups
---
--- 1. Highlighting depending on mode (returned as second value from
--- |MiniStatusline.section_mode|):
--- - `MiniStatuslineModeNormal` - normal mode.
--- - `MiniStatuslineModeInsert` - insert mode.
--- - `MiniStatuslineModeVisual` - visual mode.
--- - `MiniStatuslineModeReplace` - replace mode.
--- - `MiniStatuslineModeCommand` - command mode.
--- - `MiniStatuslineModeOther` - other mode (like terminal, etc.).
--- 2. Highlight groups used in default statusline:
--- - `MiniStatuslineDevinfo` - highlighting of "dev info" group
--- (|MiniStatusline.section_git| and
--- |MiniStatusline.section_diagnostics|).
--- - `MiniStatuslineFilename` - highliting of
--- |MiniStatusline.section_filename| section.
--- - `MiniStatuslineFileinfo` - highliting of
--- |MiniStatusline.section_fileinfo| section.
--- 3. `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`.
---@brief ]]
---@tag MiniStatusline mini.statusline
-- Module definition ==========================================================
local MiniStatusline = {}
local H = {}
--- Module setup
---
---@param config table: Module config table.
---@usage `require('mini.statusline').setup({})` (replace `{}` with your `config` table)
function MiniStatusline.setup(config)
-- 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
MiniStatusline.config = {
-- Content of statusline as functions which return statusline string. See `:h
-- statusline` and code of default contents (used when `nil` is supplied).
content = {
-- Content for active window
active = nil,
-- Content for inactive window(s)
inactive = nil,
},
-- Whether to set Vim's settings for statusline
set_vim_settings = true,
}
-- Module functionality =======================================================
--- Compute content for active window
function MiniStatusline.active()
if H.is_disabled() then
return ''
end
return (MiniStatusline.config.content.active or H.default_content_active)()
end
--- Compute content for inactive window
function MiniStatusline.inactive()
if H.is_disabled() then
return ''
end
return (MiniStatusline.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 group defined by table
--- has own highlighting (if not supplied explicitly, the previous one is
--- used). Non-empty strings inside group 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'.
function MiniStatusline.combine_groups(groups)
local t = vim.tbl_map(function(s)
if not s then
return ''
end
if type(s) == 'string' then
return s
end
local t = vim.tbl_filter(function(x)
return not (x == nil or x == '')
end, s.strings)
-- Return highlight group to allow inheritance from later sections
if vim.tbl_count(t) == 0 then
return string.format('%%#%s#', s.hl or '')
end
return string.format('%%#%s# %s ', s.hl or '', table.concat(t, ' '))
end, groups)
return table.concat(t, '')
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: Truncation width. If `nil`, output is `false`.
---@return boolean: Whether to truncate.
function MiniStatusline.is_truncated(trunc_width)
-- Use -1 to default to 'not truncated'
return vim.api.nvim_win_get_width(0) < (trunc_width or -1)
end
-- Sections ===================================================================
-- Functions should return output text without whitespace on sides or empty
-- string to omit section
-- 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
MiniStatusline.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
--- Section for Vim |mode()|
---
--- Short output is returned if window width is lower than `args.trunc_width`.
---
---@param args table: Section arguments.
---@return tuple: Section string and mode's highlight group.
function MiniStatusline.section_mode(args)
local mode_info = MiniStatusline.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 table: Section arguments. Use `args.icon` to supply your own icon.
---@return string: Section string.
function MiniStatusline.section_git(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 ''
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 |vim.lsp.diagnostic.get_count()| to 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 table: Section arguments. Use `args.icon` to supply your own icon.
---@return string: Section string.
function MiniStatusline.section_diagnostics(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 'ﯭ'
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 table: Section arguments.
---@return string: Section string.
function MiniStatusline.section_filename(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 table: Section arguments.
---@return string: Section string.
function MiniStatusline.section_fileinfo(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 table: Section arguments.
---@return string: Section string.
function MiniStatusline.section_location(args)
-- Use virtual column number to allow update when paste last column
if MiniStatusline.is_truncated(args.trunc_width) then
return '%l│%2v'
end
return '%l|%L│%2v|%-2{col("$") - 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 table: Section arguments.
---@return string: Section string.
function MiniStatusline.section_searchcount(args)
if vim.v.hlsearch == 0 or MiniStatusline.is_truncated(args.trunc_width) then
return ''
end
local s_count = vim.fn.searchcount((args or {}).options or { recompute = true })
if s_count.current == nil or s_count.total == 0 then
return ''
end
if s_count.incomplete == 1 then
return '?/?'
end
local total_sign = s_count.total > s_count.maxcount and '>' or ''
local current_sign = s_count.current > s_count.maxcount and '>' or ''
return ('%s%d/%s%d'):format(current_sign, s_count.current, total_sign, s_count.total)
end
-- Helper data ================================================================
-- Module default config
H.default_config = MiniStatusline.config
-- Showed diagnostic levels
H.diagnostic_levels = nil
if vim.fn.has('nvim-0.6') == 1 then
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' },
}
else
H.diagnostic_levels = {
{ id = 'Error', sign = 'E' },
{ id = 'Warning', sign = 'W' },
{ id = 'Information', sign = 'I' },
{ id = 'Hint', sign = 'H' },
}
end
-- Helper functionality =======================================================
-- Settings -------------------------------------------------------------------
function H.setup_config(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 {})
vim.validate({
content = { config.content, 'table' },
['content.active'] = { config.content.active, 'function', true },
['content.inactive'] = { config.content.inactive, 'function', true },
set_vim_settings = { config.set_vim_settings, 'boolean' },
})
return config
end
function H.apply_config(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
function H.is_disabled()
return vim.g.ministatusline_disable == true or vim.b.ministatusline_disable == true
end
-- Default content ------------------------------------------------------------
function H.default_content_active()
-- 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
function H.default_content_inactive()
return '%#MiniStatuslineInactive#%F%='
end
-- Utilities ------------------------------------------------------------------
function H.isnt_normal_buffer()
-- For more information see ":h buftype"
return vim.bo.buftype ~= ''
end
function H.get_filesize()
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
function H.get_filetype_icon()
-- 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 = nil
if vim.fn.has('nvim-0.6') == 1 then
H.get_diagnostic_count = function(id)
return #vim.diagnostic.get(0, { severity = id })
end
else
H.get_diagnostic_count = function(id)
return vim.lsp.diagnostic.get_count(0, id)
end
end
return MiniStatusline