Add buffers and tabs component (#42)

* feat: Add tabs component  
* feat: Add buffers component 

Now you can have traditional tabline/bufferline directly with lualine.

Special thanks to @kdheepak for making it happen.

Co-authored-by: shadmansaleh <13149513+shadmansaleh@users.noreply.github.com>
This commit is contained in:
Dheepak Krishnamurthy 2021-09-20 07:11:41 -06:00 committed by GitHub
parent 9f556fdf37
commit 4ca1a8fd00
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 660 additions and 12 deletions

View File

@ -143,6 +143,7 @@ require'lualine'.setup {
If you want to get your current lualine config. you can
do so with
```lua
require'lualine'.get_config()
@ -180,6 +181,7 @@ require'lualine'.setup{
...
}
```
Theme structure is available [here](https://github.com/hoob3rt/lualine.nvim/blob/master/CONTRIBUTING.md#adding-a-theme)
</details>
@ -223,7 +225,9 @@ sections = {lualine_a = {'mode'}}
<summary><b>Available components</b></summary>
* `branch` (git branch)
* `buffers` (shows currently available buffers)
* `diagnostics` (diagnostics count from your prefered source)
* `diff` (git diff status)
* `encoding` (file encoding)
* `fileformat` (file format)
* `filename`
@ -233,7 +237,7 @@ sections = {lualine_a = {'mode'}}
* `location` (location in file in line:column format)
* `mode` (vim mode)
* `progress` (%progress in file)
* `diff` (git diff status)
* `tabs` (shows currently available tabs)
</details>
@ -279,6 +283,7 @@ You can use any valid lua expression as a component including
```lua
sections = {lualine_c = {"os.date('%a')", 'data', "require'lsp-status'.status()"}}
```
`data` is a global variable in this example.
---
@ -293,6 +298,7 @@ There are two kinds of options:
Global options can be used as local options (can be applied to specific components)
but you cannot use local options as global.
Global option used locally overwrites the global, for example:
```lua
require'lualine'.setup {
options = {fmt = string.lower},
@ -364,6 +370,31 @@ sections = {
<details>
<summary><b>Component specific local options</b></summary>
#### buffers component options
```lua
sections = {
lualine_a = {
{
'buffers',
show_filename_only = true, -- shows shortened relative path when false
show_modified_status = true -- shows indicator then bufder is modified
max_length = vim.o.columns * 2 / 3, -- maximum width of buffers component
filetype_names = {
TelescopePrompt = 'Telescope',
dashboard = 'Dashboard',
packer = 'Packer',
fzf = 'FZF',
}, -- shows specific buffer name for that filetype ( { `filetype` = `buffer_name`, ... } )
buffers_color = {
active = nil, -- color for active buffer
inactive = nil, -- color for inactive buffer
},
}
}
}
```
#### diagnostics component options
```lua
@ -446,6 +477,26 @@ sections = {
}
```
#### tabs component options
```lua
sections = {
lualine_a = {
{
'tabs',
max_length = vim.o.columns / 3, -- maximum width of tabs component
mode = 0, -- 0 shows tab_nr
-- 1 shows tab_name
-- 2 shows tab_nr + tab_name
tabs_color = {
active = nil, -- color for active tab
inactive = nil, -- color for inactive tab
},
}
}
}
```
</details>
---
@ -454,6 +505,7 @@ sections = {
You can use lualine to display components in tabline.
The configuration for tabline sections is exactly the same as for statusline.
```lua
tabline = {
lualine_a = {},
@ -464,9 +516,23 @@ tabline = {
lualine_z = {}
}
```
This will show branch and filename component in top of neovim inside tabline .
You can also completely move your statuline to tabline by configuring
lualine also provides 2 components buffers & tabs that you can use to get more traditional tabline/bufferline.
```lua
tabline = {
lualine_a = {'buffers'},
lualine_b = {'branch'},
lualine_c = {'filename'},
lualine_x = {},
lualine_y = {},
lualine_z = {'tabs'}
}
```
You can also completely move your statusline to tabline by configuring
`lualine.tabline` and disabling `lualine.sections` and `lualine.inactive_sections`.
```lua
@ -477,8 +543,8 @@ sections = {},
inactive_sections = {},
```
If you're looking for bufferline or want to show tabs in tabline . There are
manny awesome plugins that can do that. For example:
If you want a more sophisticated tabline you can use other
tabline plugins with lualine too . For example:
- [nvim-bufferline](https://github.com/akinsho/nvim-bufferline.lua)
- [tabline.nvim](https://github.com/kdheepak/tabline.nvim)
@ -493,8 +559,9 @@ You can find a bigger list [here](https://github.com/rockerBOO/awesome-neovim#ta
Lualine extensions change statusline appearance for a window/buffer with
specified filetypes.
By default no extension are loaded to improve performance.
By default no extensions are loaded to improve performance.
You can load extensions with:
```lua
extensions = {'quickfix'}
```
@ -516,6 +583,7 @@ extensions = {'quickfix'}
<summary><b>Custom extensions</b></summary>
You can define your own extensions. If you think an extension might be useful for others then please submit a pr.
```lua
local my_extension = {sections = {lualine_a = 'mode'}, filetypes = {'lua'}}
require'lualine'.setup {extensions = {my_extension}}

View File

@ -203,7 +203,9 @@ Available components ~
- `branch` (git branch)
- `buffers` (shows currently available buffers)
- `diagnostics` (diagnostics count from your prefered source)
- `diff` (git diff status)
- `encoding` (file encoding)
- `fileformat` (file format)
- `filename`
@ -213,7 +215,7 @@ Available components ~
- `location` (location in file in line:column format)
- `mode` (vim mode)
- `progress` (%progress in file)
- `diff` (git diff status)
- `tabs` (shows currently available tabs)
*lualine-Custom-components*
@ -346,6 +348,32 @@ Local options ~
Component specific local options ~
*lualine-buffers-component-options*
>
sections = {
lualine_a = {
{
'buffers',
show_filename_only = true, -- shows shortened relative path when false
show_modified_status = true -- shows indicator then bufder is modified
max_length = vim.o.columns * 2 / 3, -- maximum width of buffers component
filetype_names = {
TelescopePrompt = 'Telescope',
dashboard = 'Dashboard',
packer = 'Packer',
fzf = 'FZF',
}, -- shows specific buffer name for that filetype ( { `filetype` = `buffer_name`, ... } )
buffers_color = {
active = nil, -- color for active buffer
inactive = nil, -- color for inactive buffer
},
}
}
}
<
*lualine-diagnostics-component-options*
>
@ -432,6 +460,27 @@ Component specific local options ~
<
*lualine-tabs-component-options*
>
sections = {
lualine_a = {
{
'tabs',
max_length = vim.o.columns / 3, -- maximum width of tabs component
mode = 0, -- 0 shows tab_nr
-- 1 shows tab_name
-- 2 shows tab_nr + tab_name
tabs_color = {
active = nil, -- color for active tab
inactive = nil, -- color for inactive tab
},
}
}
}
<
------------------------------------------------------------------------------
TABLINE ~
@ -453,7 +502,22 @@ tabline sections is exactly the same as for statusline.
This will show branch and filename component in top of neovim inside tabline.
You can also completely move your statuline to tabline by configuring
lualine also provides 2 components buffers & tabs that you can use to get more
traditional tabline/bufferline.
>
tabline = {
lualine_a = {'buffers'},
lualine_b = {'branch'},
lualine_c = {'filename'},
lualine_x = {},
lualine_y = {},
lualine_z = {'tabs'}
}
<
You can also completely move your statusline to tabline by configuring
`lualine.tabline` and disabling `lualine.sections` and
`lualine.inactive_sections`.
@ -466,8 +530,8 @@ You can also completely move your statuline to tabline by configuring
<
If youre looking for bufferline or want to show tabs in tabline. There are
manny awesome plugins that can do that. For example:
If you want a more sophisticated tabline you can use other tabline plugins with
lualine too. For example:
- nvim-bufferline <https://github.com/akinsho/nvim-bufferline.lua>
@ -484,7 +548,7 @@ EXTENSIONS ~
Lualine extensions change statusline appearance for a window/buffer with
specified filetypes.
By default no extension are loaded to improve performance. You can load
By default no extensions are loaded to improve performance. You can load
extensions with:
>

40
kkk.lua Normal file
View File

@ -0,0 +1,40 @@
local config = {
options = {
icons_enabled = true,
theme = 'nord',
component_separators = { left = '', right = '' },
section_separators = { left = '', right = '' },
disabled_filetypes = {},
},
sections = {
lualine_a = { 'mode' },
lualine_b = { 'branch', 'diff', { 'diagnostics', sources = { 'nvim_lsp', 'coc' } } },
lualine_c = { 'filename' },
lualine_x = { 'encoding', 'fileformat', 'filetype' },
lualine_y = { 'progress' },
lualine_z = { 'location' },
},
inactive_sections = {
lualine_a = {},
lualine_b = {},
lualine_c = { 'filename' },
lualine_x = { 'location' },
lualine_y = {},
lualine_z = {},
},
tabline = {},
extensions = {},
}
local lualine = require'lualine'
lualine.setup(config)
vim.g.actual_curbuf = tostring(vim.fn.bufnr())
vim.g.actual_curwin = tostring(vim.fn.bufwinid(vim.fn.bufnr()))
local num = 10000
local bench = require('plenary.profile').benchmark
local time = bench(num, function()
lualine.statusline(true)
end)
print(string.format('render %s time : *%s* ms', num, time))
vim.g.actual_curbuf = nil
vim.g.actual_curwin = nil

View File

@ -0,0 +1,263 @@
-- Copyright (c) 2020-2021 shadmansaleh
-- MIT license, see LICENSE for more details.
local Buffers = require('lualine.component'):new()
local highlight = require 'lualine.highlight'
local default_options = {
show_filename_only = true,
show_modified_status = true,
max_length = 0,
filetype_names = {
TelescopePrompt = 'Telescope',
dashboard = 'Dashboard',
packer = 'Packer',
fzf = 'FZF',
},
}
local function get_hl(section, is_active)
local suffix = is_active and '_normal' or '_inactive'
local section_redirects = {
lualine_x = 'lualine_c',
lualine_y = 'lualine_b',
lualine_z = 'lualine_a',
}
if section_redirects[section] then
section = highlight.highlight_exists(section .. suffix) and section or section_redirects[section]
end
return section .. suffix
end
local Buffer = {}
function Buffer:new(buffer)
assert(buffer.bufnr, 'Cannot create Buffer without bufnr')
local newObj = { bufnr = buffer.bufnr, options = buffer.options, highlights = buffer.highlights }
self.__index = self
newObj = setmetatable(newObj, self)
newObj:get_props()
return newObj
end
function Buffer:get_props()
self.file = vim.fn.bufname(self.bufnr)
self.filepath = vim.fn.expand('#' .. self.bufnr .. ':p:~')
self.buftype = vim.api.nvim_buf_get_option(self.bufnr, 'buftype')
self.filetype = vim.api.nvim_buf_get_option(self.bufnr, 'filetype')
local modified = self.options.show_modified_status and vim.api.nvim_buf_get_option(self.bufnr, 'modified')
local modified_icon = self.options.icons_enabled and '' or ' +'
self.modified_icon = modified and modified_icon or ''
self.visible = vim.fn.bufwinid(self.bufnr) ~= -1
self.icon = ''
if self.options.icons_enabled then
local dev
local status, _ = pcall(require, 'nvim-web-devicons')
if not status then
dev, _ = '', ''
elseif self.filetype == 'TelescopePrompt' then
dev, _ = require('nvim-web-devicons').get_icon 'telescope'
elseif self.filetype == 'fugitive' then
dev, _ = require('nvim-web-devicons').get_icon 'git'
elseif self.filetype == 'vimwiki' then
dev, _ = require('nvim-web-devicons').get_icon 'markdown'
elseif self.buftype == 'terminal' then
dev, _ = require('nvim-web-devicons').get_icon 'zsh'
elseif vim.fn.isdirectory(self.file) == 1 then
dev, _ = '', nil
else
dev, _ = require('nvim-web-devicons').get_icon(self.file, vim.fn.expand('#' .. self.bufnr .. ':e'))
end
if dev then
self.icon = dev .. ' '
end
end
return self
end
function Buffer:render()
local name
if self.ellipse then
name = '...'
else
name = string.format(' %s%s%s ', self.icon, self:name(), self.modified_icon)
end
self.len = vim.fn.strchars(name)
local line = string.format('%%%s@LualineSwitchBuffer@%s%%T', self.bufnr, name)
line = highlight.component_format_highlight(self.highlights[(self.current and 'active' or 'inactive')]) .. line
if self.options.self.section < 'lualine_x' and not self.first then
local sep_before = self:separator_before()
line = sep_before .. line
self.len = self.len + vim.fn.strchars(sep_before)
elseif self.options.self.section >= 'lualine_x' and not self.last then
local sep_after = self:separator_after()
line = line .. sep_after
self.len = self.len + vim.fn.strchars(sep_after)
end
return line
end
function Buffer:separator_before()
if self.current or self.aftercurrent then
return '%S{' .. self.options.section_separators.left .. '}'
else
return self.options.component_separators.left
end
end
function Buffer:separator_after()
if self.current or self.beforecurrent then
return '%s{' .. self.options.section_separators.right .. '}'
else
return self.options.component_separators.right
end
end
function Buffer:name()
if self.options.filetype_names[self.filetype] then
return self.options.filetype_names[self.filetype]
elseif self.buftype == 'help' then
return 'help:' .. vim.fn.fnamemodify(self.file, ':t:r')
elseif self.buftype == 'terminal' then
local match = string.match(vim.split(self.file, ' ')[1], 'term:.*:(%a+)')
return match ~= nil and match or vim.fn.fnamemodify(vim.env.SHELL, ':t')
elseif vim.fn.isdirectory(self.file) == 1 then
return vim.fn.fnamemodify(self.file, ':p:.')
elseif self.file == '' then
return '[No Name]'
end
return self.options.show_filename_only and vim.fn.fnamemodify(self.file, ':t')
or vim.fn.pathshorten(vim.fn.fnamemodify(self.file, ':p:.'))
end
function Buffers:new(options, child)
local newObj = self._parent:new(options, child or Buffers)
default_options.buffers_color = {
active = get_hl(options.self.section, true),
inactive = get_hl(options.self.section, false),
}
newObj.options = vim.tbl_deep_extend('keep', newObj.options or {}, default_options)
newObj.highlights = {
active = highlight.create_component_highlight_group(
newObj.options.buffers_color.active,
'buffers_active',
newObj.options
),
inactive = highlight.create_component_highlight_group(
newObj.options.buffers_color.inactive,
'buffers_active',
newObj.options
),
}
return newObj
end
function Buffers:update_status()
local data = {}
local buffers = {}
for b = 1, vim.fn.bufnr '$' do
if vim.fn.buflisted(b) ~= 0 and vim.api.nvim_buf_get_option(b, 'buftype') ~= 'quickfix' then
buffers[#buffers + 1] = Buffer:new { bufnr = b, options = self.options, highlights = self.highlights }
end
end
local current_bufnr = vim.fn.bufnr()
local current = -2
if buffers[1] then
buffers[1].first = true
end
if buffers[#buffers] then
buffers[#buffers].last = true
end
for i, buffer in ipairs(buffers) do
if buffer.bufnr == current_bufnr then
buffer.current = true
current = i
end
end
if buffers[current - 1] then
buffers[current - 1].beforecurrent = true
end
if buffers[current + 1] then
buffers[current + 1].aftercurrent = true
end
local max_length = self.options.max_length
if max_length == 0 then
max_length = math.floor(2 * vim.o.columns / 3)
end
local total_length
for i, buffer in pairs(buffers) do
if buffer.current then
current = i
end
end
if current == -2 then
local b = Buffer:new { bufnr = vim.fn.bufnr(), options = self.options, highlights = self.highlights }
b.current = true
if self.options.self.section < 'lualine_x' then
b.last = true
buffers[#buffers].last = nil
buffers[#buffers + 1] = b
current = #buffers
else
b.first = true
buffers[1].first = nil
table.insert(buffers, 1, b)
current = 1
end
end
local current_buffer = buffers[current]
data[#data + 1] = current_buffer:render()
total_length = current_buffer.len
local i = 0
local before, after
while true do
i = i + 1
before = buffers[current - i]
after = buffers[current + i]
local rendered_before, rendered_after
if before == nil and after == nil then
break
end
if before then
rendered_before = before:render()
total_length = total_length + before.len
end
if after then
rendered_after = after:render()
total_length = total_length + after.len
end
if total_length > max_length then
break
end
if before then
table.insert(data, 1, rendered_before)
end
if after then
data[#data + 1] = rendered_after
end
end
if total_length > max_length then
if before ~= nil then
before.ellipse = true
before.first = true
table.insert(data, 1, before:render())
end
if after ~= nil then
after.ellipse = true
after.last = true
data[#data + 1] = after:render()
end
end
return table.concat(data)
end
vim.cmd [[
function! LualineSwitchBuffer(bufnr, mouseclicks, mousebutton, modifiers)
execute ":buffer " . a:bufnr
endfunction
]]
return Buffers

View File

@ -0,0 +1,213 @@
-- Copyright (c) 2020-2021 shadmansaleh
-- MIT license, see LICENSE for more details.
local Tabs = require('lualine.component'):new()
local highlight = require 'lualine.highlight'
local default_options = {
max_length = 0,
mode = 0,
}
local function get_hl(section, is_active)
local suffix = is_active and '_normal' or '_inactive'
local section_redirects = {
lualine_x = 'lualine_c',
lualine_y = 'lualine_b',
lualine_z = 'lualine_a',
}
if section_redirects[section] then
section = highlight.highlight_exists(section .. suffix) and section or section_redirects[section]
end
return section .. suffix
end
local Tab = {}
function Tab:new(tab)
assert(tab.tabnr, 'Cannot create Tab without tabnr')
local newObj = {
tabnr = tab.tabnr,
options = tab.options,
highlights = tab.highlights,
}
self.__index = self
newObj = setmetatable(newObj, self)
return newObj
end
function Tab:label()
local buflist = vim.fn.tabpagebuflist(self.tabnr)
local winnr = vim.fn.tabpagewinnr(self.tabnr)
local bufnr = buflist[winnr]
local file = vim.fn.bufname(bufnr)
local buftype = vim.fn.getbufvar(bufnr, '&buftype')
if buftype == 'help' then
return 'help:' .. vim.fn.fnamemodify(file, ':t:r')
elseif buftype == 'terminal' then
local match = string.match(vim.split(file, ' ')[1], 'term:.*:(%a+)')
return match ~= nil and match or vim.fn.fnamemodify(vim.env.SHELL, ':t')
elseif vim.fn.isdirectory(file) == 1 then
return vim.fn.fnamemodify(file, ':p:.')
elseif file == '' then
return '[No Name]'
end
return vim.fn.fnamemodify(file, ':t')
end
function Tab:render()
local name
if self.ellipse then
name = '...'
else
if self.options.mode == 0 then
name = string.format('%s%s ', (self.last or not self.first) and ' ' or '', tostring(self.tabnr))
elseif self.options.mode == 1 then
name = string.format('%s%s ', (self.last or not self.first) and ' ' or '', self:label())
else
name = string.format('%s%s %s ', (self.last or not self.first) and ' ' or '', tostring(self.tabnr), self:label())
end
end
self.len = #name
local line = string.format('%%%s@LualineSwitchTab@%s%%T', self.tabnr, name)
line = highlight.component_format_highlight(self.highlights[(self.current and 'active' or 'inactive')]) .. line
if self.options.self.section < 'lualine_x' and not self.first then
local sep_before = self:separator_before()
line = sep_before .. line
self.len = self.len + vim.fn.strchars(sep_before)
elseif self.options.self.section >= 'lualine_x' and not self.last then
local sep_after = self:separator_after()
line = line .. sep_after
self.len = self.len + vim.fn.strchars(sep_after)
end
return line
end
function Tab:separator_before()
if self.current or self.aftercurrent then
return '%S{' .. self.options.section_separators.left .. '}'
else
return self.options.component_separators.left
end
end
function Tab:separator_after()
if self.current or self.beforecurrent then
return '%s{' .. self.options.section_separators.right .. '}'
else
return self.options.component_separators.right
end
end
function Tabs:new(options, child)
local newObj = self._parent:new(options, child or Tabs)
default_options.tabs_color = {
active = get_hl(options.self.section, true),
inactive = get_hl(options.self.section, false),
}
newObj.options = vim.tbl_deep_extend('keep', newObj.options or {}, default_options)
-- stylua: ignore
newObj.highlights = {
active = highlight.create_component_highlight_group(
newObj.options.tabs_color.active,
'tabs_active',
newObj.options
),
inactive = highlight.create_component_highlight_group(
newObj.options.tabs_color.inactive,
'tabs_active',
newObj.options
),
}
return newObj
end
function Tabs:update_status()
local data = {}
local tabs = {}
for t = 1, vim.fn.tabpagenr '$' do
tabs[#tabs + 1] = Tab:new { tabnr = t, options = self.options, highlights = self.highlights }
end
local current = vim.api.nvim_get_current_tabpage()
tabs[1].first = true
tabs[#tabs].last = true
if tabs[current] then
tabs[current].current = true
end
if tabs[current - 1] then
tabs[current - 1].beforecurrent = true
end
if tabs[current + 1] then
tabs[current + 1].aftercurrent = true
end
local max_length = self.options.max_length
if max_length == 0 then
max_length = math.floor(vim.o.columns / 3)
end
local total_length
for i, tab in pairs(tabs) do
if tab.current then
current = i
end
end
local current_tab = tabs[current]
if current_tab == nil then
local t = Tab:new { tabnr = vim.fn.tabpagenr() }
t.current = true
t.last = true
data[#data + 1] = t:render()
else
data[#data + 1] = current_tab:render()
total_length = current_tab.len
local i = 0
local before, after
while true do
i = i + 1
before = tabs[current - i]
after = tabs[current + i]
local rendered_before, rendered_after
if before == nil and after == nil then
break
end
if before then
rendered_before = before:render()
total_length = total_length + before.len
if total_length > max_length then
break
end
table.insert(data, 1, rendered_before)
end
if after then
rendered_after = after:render()
total_length = total_length + after.len
if total_length > max_length then
break
end
data[#data + 1] = rendered_after
end
end
if total_length > max_length then
if before ~= nil then
before.ellipse = true
before.first = true
table.insert(data, 1, before:render())
end
if after ~= nil then
after.ellipse = true
after.last = true
data[#data + 1] = after:render()
end
end
end
return table.concat(data)
end
vim.cmd [[
function! LualineSwitchTab(tabnr, mouseclicks, mousebutton, modifiers)
execute a:tabnr . "tabnext"
endfunction
]]
return Tabs