michaelbrusegard / tabline.wez

A versatile and easy to use retro tab bar plugin for the WezTerm terminal emulator created with the lualine.nvim configuration format
MIT License
42 stars 5 forks source link
lua lualine statusline tabline tabs wez wezterm wezterm-plugin

tabline.wez

A versatile and easy to use tab-bar written in Lua.

tabline.wez requires the WezTerm terminal emulator.

Tabline was greatly inspired by lualine.nvim, a statusline plugin for Neovim, and tries to use the same configuration format.

Contributing

Feel free to create an issue/PR if you want to see anything else implemented, or if you have some question or need help with configuration.

Screenshots

Here is a preview of what the tab-bar can look like.

tabline 1 tabline 2 tabline 3 tabline 4 tabline 5

Some more examples, but very zoomed in.

tabline 1 big tabline 2 big tabline 3 big tabline 4 big

tabline.wez supports all the same themes as WezTerm. You can find the list of themes here.

Installation

WezTerm Plugin API

local tabline = wezterm.plugin.require("https://github.com/michaelbrusegard/tabline.wez")

You'll also need to have a patched font if you want icons.

Usage and customization

Tabline has sections as shown below just like lualine with the addition of tabs in the middle.

+-------------------------------------------------+
| A | B | C |  TABS                   | X | Y | Z |
+-------------------------------------------------+

Each sections holds its components e.g. Current active keytable (mode).

Configuring tabline in wezterm.lua

Default configuration

tabline.setup({
  options = {
    icons_enabled = true,
    theme = 'Catppuccin Mocha',
    color_overrides = {},
    section_separators = {
      left = wezterm.nerdfonts.pl_left_hard_divider,
      right = wezterm.nerdfonts.pl_right_hard_divider,
    },
    component_separators = {
      left = wezterm.nerdfonts.pl_left_soft_divider,
      right = wezterm.nerdfonts.pl_right_soft_divider,
    },
    tab_separators = {
      left = wezterm.nerdfonts.pl_left_hard_divider,
      right = wezterm.nerdfonts.pl_right_hard_divider,
    },
  },
  sections = {
    tabline_a = { 'mode' },
    tabline_b = { 'workspace' },
    tabline_c = { ' ' },
    tab_active = {
      'index',
      { 'parent', padding = 0 },
      '/',
      { 'cwd', padding = { left = 0, right = 1 } },
      { 'zoomed', padding = 0 },
    },
    tab_inactive = { 'index', { 'process', padding = { left = 0, right = 1 } } },
    tabline_x = { 'ram', 'cpu' },
    tabline_y = { 'datetime', 'battery' },
    tabline_z = { 'hostname' },
  },
  extensions = {},
})

If you want to get your current tabline config, you can do so with:

tabline.get_config()

WezTerm configuration

Tabline requires that some options are applied to the WezTerm Config struct. For example the retro tab-bar must be enabled. Tabline provides a function to apply some recommended options to the config. If you already set these options in your wezterm.lua you do not need this function. This needs to be called after wezterm.setup().

tabline.apply_to_config(config)

[!CAUTION] This function has nothing to do with the tabline config passed into setup and retrieved with tabline.get_config(). It only applies some recommended options to the WezTerm config. More info here


Starting tabline

tabline.setup()

Setting a theme

options = { theme = 'GruvboxDark' }

All available themes are found here.

Customizing themes

To modify a theme, you can use the color_overrides option.

-- Change the background of tabline_c section for normal mode
tabline.setup({
  options = {
    color_overrides = {
      normal_mode = {
        c = { bg = '#112233' },
      },
    }
  }
})

This is also where you would specify the colors for a new Key Table (mode). Tabline expects each key table to end with _mode.

tabline.setup({
  options = {
    color_overrides = {
    -- Default colors from Catppuccin Mocha
      normal_mode = {
        a = { fg = '#181825', bg = '#89b4fa' },
        b = { fg = '#89b4fa', bg = '#313244' },
        c = { fg = '#cdd6f4', bg = '#181825' },
      },
      copy_mode = {
        a = { fg = '#181825', bg = '#f9e2af' },
        b = { fg = '#f9e2af', bg = '#313244' },
        c = { fg = '#cdd6f4', bg = '#181825' },
      },
      search_mode = {
        a = { fg = '#181825', bg = '#a6e3a1' },
        b = { fg = '#a6e3a1', bg = '#313244' },
        c = { fg = '#cdd6f4', bg = '#181825' },
      },
      -- Defining colors for a new key table
      window_mode = {
        a = { fg = '#181825', bg = '#cba6f7' },
        b = { fg = '#cba6f7', bg = '#313244' },
        c = { fg = '#cdd6f4', bg = '#181825' },
      },
      -- Default tab colors
      tab = {
        active = { fg = '#89b4fa', bg = '#313244' },
        inactive = { fg = '#cdd6f4', bg = '#181825' },
        inactive_hover = { fg = '#f5c2e7', bg = '#313244' } },
      }
    }
  }
})

Getting colors

If you want to get the colors used in the current theme, you can do so with:

tabline.get_colors()

You will get an object like the color_overrides object above, but with the addition of a scheme property (the scheme property is the scheme object from WezTerm with every color found there).

[!TIP] This can be useful when creating your own components or extensions and you want to use the same colors as the current theme


Separators

tabline defines three kinds of separators:

options = {
  section_separators = {
    left = wezterm.nerdfonts.pl_left_hard_divider,
    right = wezterm.nerdfonts.pl_right_hard_divider,
  },
  component_separators = {
    left = wezterm.nerdfonts.pl_left_soft_divider,
    right = wezterm.nerdfonts.pl_right_soft_divider,
  },
  tab_separators = {
    left = wezterm.nerdfonts.pl_left_hard_divider,
    right = wezterm.nerdfonts.pl_right_hard_divider,
  },
}

Here, left refers to the left-most sections (a, b, c), and right refers to the right-most sections (x, y, z). For the tabs it refers to each side of the tab.

Disabling separators

options = {
  section_separators = '',
  component_separators = '',
  tab_separators = '',
}

Changing components in tabline sections

sections = { tabline_a = { 'mode' } }

Available components

Tabline separates components into ones available for the tabline components (tabline_a, tabline_b, etc...), which are grouped under Window since they have access to the Window object.

And the tab_active and tab_inactive components which are grouped under Tab and have access to TabInformation.

Custom components

Lua functions as tabline component
local function hello()
  return 'Hello World'
end
sections = { tabline_a = { hello } }

[!NOTE] Functions receive the Window object or TabInformation object as the first argument depending on the component group

Text string as tabline component
sections = { tabline_a = { 'Hello World' } }
WezTerm Formatitem as tabline component

You can find all the available format items here. The ResetAttributes format item has been overwritten to reset all attributes back to the default for that component instead of the WezTerm default.

sections = {
  tabline_c = {
    { Attribute = { Underline = 'Single' } },
    { Foreground = { AnsiColor = 'Fuchsia' } },
    { Background = { Color = 'blue' } },
    'Hello World', -- { Text = 'Hello World' }
  }
}

[!TIP] Strings are automatically wrapped in a Text FormatItem when used as a component.

Lua expressions as tabline component

You can use any valid lua expression as a component including:

sections = { tabline_c = { os.date('%a'), data, require('util').status() } }

data is a global variable in this example.


Component options

Component options can change the way a component behave. 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 options used locally overwrites the global, for example:

tabline.setup {
  options = { fmt = string.lower },
  sections = {
    tabline_a = {
      { 'mode', fmt = function(str) return str:sub(1,1) end }
    },
    tabline_b = { 'window' }
  }
}

mode will be formatted with the passed function so only first char will be shown. On the other hand window will be formatted with the global formatter string.lower so it will be showed in lower case.

Available options

Global options

These are options that are used in the options table. They set behavior of tabline.

Values set here are treated as default for other options that work in the component level.

For example even though icons_enabled is a general component option. You can set icons_enabled to false and icons will be disabled on all component. You can still overwrite defaults set in the options table by specifying the option value in the component.

options = {
  theme = 'nord', -- tabline theme
  section_separators = {
    left = wezterm.nerdfonts.ple_right_half_circle_thick,
    right = wezterm.nerdfonts.ple_left_half_circle_thick,
  },
  component_separators = {
    left = wezterm.nerdfonts.ple_right_half_circle_thin,
    right = wezterm.nerdfonts.ple_left_half_circle_thin,
  },
  tab_separators = {
    left = wezterm.nerdfonts.ple_right_half_circle_thick,
    right = wezterm.nerdfonts.ple_left_half_circle_thick,
  },
}

General component options

These are options that control behavior at the component level and are available for all components.

sections = {
  tabline_a = {
    {
      'mode',
      icons_enabled = true, -- Enables the display of icons alongside the component.
      -- Defines the icon to be displayed in front of the component.
      -- Can be string|table
      -- As table it must contain the icon as first entry and can use
      -- color option to custom color the icon. Example:
      -- { 'workspace', icon = wezterm.nerdfonts.cod_terminal_tmux } / { 'workspace', icon = { wezterm.nerdfonts.cod_terminal_tmux, color = { fg= '#00ff00' } } }

      icons_only = false, -- Only show icon (if the component has one)

      -- icon position can also be set to the right side from table. Example:
      -- {'branch', icon = { wezterm.nerdfonts.cod_terminal_tmux, align = 'right', color = { fg = '#00ff00' } } }
      icon = nil,

      cond = nil, -- Condition function, the component is loaded when the function returns `true`.

      padding = 1, -- Adds padding to the left and right of components.
                   -- Padding can be specified to left or right independently, e.g.:
                   --   padding = { left = left_padding, right = right_padding }

      fmt = nil, -- Format function, formats the component's output.
      -- This function receives two arguments:
      -- - string that is going to be displayed and
      --   that can be changed, enhanced and etc.
      -- - Window/TabInformation object with information you might
      --   need. E.g. active_pane if used with Window.
    },
  },
}

Component specific options

These are options that are available on specific components. For example, you have option on index component to specify if it should be zero indexed.

datetime component options

sections = {
  tabline_a = {
    {
      'datetime',
      -- options: your own format string ('%Y/%m/%d %H:%M:%S', etc.)
      style = '%H:%M',
      hour_to_icon = {
        ['00'] = wezterm.nerdfonts.md_clock_time_twelve_outline,
        ['01'] = wezterm.nerdfonts.md_clock_time_one_outline,
        ['02'] = wezterm.nerdfonts.md_clock_time_two_outline,
        -- for every hour...
        ['23'] = wezterm.nerdfonts.md_clock_time_eleven,
      },
    -- hour_to_icon is a table that maps hours to icons it overwrites the default icon property.
    -- To use the default icon property set hour_to_icon to nil.
    -- The color and align properties can still be used on the icon property.
    },
  },
}

cwd and parent component options

sections = {
  tab_active = {
    {
      'cwd',
      max_length = 10, -- Max length before it is truncated
    },
  },
}

index component options

sections = {
  tab_active = {
    {
      'index',
      zero_indexed = false, -- Does the tab index start at 0 or 1
    },
  },
}

process component options

sections = {
  tab_active = {
    {
      'process',
      process_to_icon = {
        ['apt'] = wezterm.nerdfonts.dev_debian,
        ['bash'] = wezterm.nerdfonts.cod_terminal_bash,
        ['bat'] = wezterm.nerdfonts.md_bat,
        ['cmd.exe'] = wezterm.nerdfonts.md_console_line,
        ['curl'] = wezterm.nerdfonts.md_flattr,
        ['debug'] = wezterm.nerdfonts.cod_debug,
        ['default'] = wezterm.nerdfonts.md_application,
        ['docker'] = wezterm.nerdfonts.linux_docker,
        ['docker-compose'] = wezterm.nerdfonts.linux_docker,
        ['git'] = wezterm.nerdfonts.dev_git,
        ['go'] = wezterm.nerdfonts.md_language_go,
        ['lazydocker'] = wezterm.nerdfonts.linux_docker,
        ['lazygit'] = wezterm.nerdfonts.cod_github,
        ['lua'] = wezterm.nerdfonts.seti_lua,
        ['make'] = wezterm.nerdfonts.seti_makefile,
        ['nix'] = wezterm.nerdfonts.linux_nixos,
        ['node'] = wezterm.nerdfonts.md_nodejs,
        ['npm'] = wezterm.nerdfonts.md_npm,
        ['nvim'] = wezterm.nerdfonts.custom_neovim,
        ['psql'] = wezterm.nerdfonts.dev_postgresql,
        ['zsh'] = wezterm.nerdfonts.dev_terminal,
        -- and more...
      },
    -- process_to_icon is a table that maps process to icons
    },
  },
}

cpu and ram component options

sections = {
  tab_active = {
    {
      'cpu',
      throttle = 3, -- How often in seconds the component updates, set to 0 to disable throttling
    },
  },
}

battery component options

sections = {
  battery_to_icon = {
    empty = { wezterm.nerdfonts.fa_battery_empty, color = { fg = scheme.ansi[2] } },
    quarter = wezterm.nerdfonts.fa_battery_quarter,
    half = wezterm.nerdfonts.fa_battery_half,
    three_quarters = wezterm.nerdfonts.fa_battery_three_quarters,
    full = wezterm.nerdfonts.fa_battery_full,
  }
-- battery_to_icon is a table that maps battery percentage to icons
-- It overwrites the default icon property. To use the default icon property set battery_to_icon to nil
-- The color and align properties can still be used on the icon property
}

Extensions

tabline extensions change statusline appearance for other plugins.

By default no extensions are loaded to improve performance. You can load extensions with:

extensions = { 'resurrect' }

Available extensions

Custom extensions

You can define your own extensions. If you believe an extension may be useful to others, then please submit a PR.

Custom extensions requires a show event to be defined. When the show event is triggered the defined sections will be shown. If a section in sections is not defined it will use the default from the config. The hide event is optional. When the hide event is triggered the extension will hide the defined sections. If the hide event is not defined the extension will hide the sections after the delay which is set to 5 seconds by default. You can also have a delay with the hide event, which will delay the hide for the specified time in seconds after the hide event is triggered. You can also have a optional callback function that will be called when the show event is triggered with the properties from the event. The colors overwrite the default colors for the extension to use with its sections.

local my_extension = {
  'my_extension_name',
  events = {
    show = 'my_plugin.show',
    hide = 'my_plugin.hide',
    delay = 3
    callback = function(window)
      wezterm.log_info('Extension was shown')
    end
  },
  sections = {
    tabline_a = { 'mode' }
  },
  colors = {
    a = { fg = '#181825', bg = '#f38ba8' },
    b = { fg = '#f38ba8', bg = '#313244' },
    c = { fg = '#cdd6f4', bg = '#181825' },
  }
}

tabline.setup({ extensions = { my_extension } })

You can also pass multiple events to the show and hide properties.

events = {
  show = { 'my_plugin.show', 'my_plugin.show2' },
  hide = { 'my_plugin.hide', 'my_plugin.hide2' }
}

Refreshing tabline

By default tabline refreshes itself based on the status_update_interval. However you can also force tabline to refresh at any time by calling tabline.refresh function. The refresh function needs the Window object to refresh the tabline, and the TabInformation object to refresh the tabs. If passing one of them as nil it won't refresh the respective section.

tabline.refresh(window, tab)

Avoid calling tabline.refresh inside components. Since components are evaluated during refresh, calling refresh while refreshing can have undesirable effects.

Disabling tabline

You can also disable tabline completely. By setting the enable_tab_bar option to false in the WezTerm config.

Inspiration

Thanks to MLFlexer for some tips in developing a plugin for WezTerm.

Thanks to lualine.nvim for the inspiration and a nice statusline for my Neovim.