diff --git a/README.md b/README.md index 76baf63..62825db 100644 --- a/README.md +++ b/README.md @@ -48,10 +48,10 @@ For those who want to break the norms. You can create custom looks in lualine. **Example** : - [evil_lualine](examples/evil_lualine.lua) - + - [bubbles](examples/bubbles.lua) - + @@ -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) @@ -223,7 +225,9 @@ sections = {lualine_a = {'mode'}} 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` @@ -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) @@ -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 = {
Component specific local options +#### 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 + }, + } + } +} +``` +
--- @@ -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'} Custom extensions 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}} diff --git a/doc/lualine.txt b/doc/lualine.txt index e6d34b2..b40d136 100644 --- a/doc/lualine.txt +++ b/doc/lualine.txt @@ -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 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 @@ -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: > diff --git a/kkk.lua b/kkk.lua new file mode 100644 index 0000000..394f45e --- /dev/null +++ b/kkk.lua @@ -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 diff --git a/lua/lualine/components/buffers.lua b/lua/lualine/components/buffers.lua new file mode 100644 index 0000000..995e2d1 --- /dev/null +++ b/lua/lualine/components/buffers.lua @@ -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 diff --git a/lua/lualine/components/tabs.lua b/lua/lualine/components/tabs.lua new file mode 100644 index 0000000..b563f01 --- /dev/null +++ b/lua/lualine/components/tabs.lua @@ -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