From 47dc59e63d444d5e46e7c7aa7595ef6bba44e0b2 Mon Sep 17 00:00:00 2001 From: Famiu Haque Date: Mon, 25 Oct 2021 01:48:45 +0600 Subject: [PATCH] feat!: add color theme support Allow adding color themes to Feline and switching between them on the fly. BREAKING CHANGE: The `colors` configuration option in the setup function now needs to be renamed to `theme`. --- USAGE.md | 72 +++++++++++++++++++++++++++++------ lua/feline/defaults.lua | 21 ++-------- lua/feline/generator.lua | 57 ++++++++++++++------------- lua/feline/init.lua | 71 ++++++++++++++++++++++++++++++++-- lua/feline/themes/default.lua | 15 ++++++++ lua/feline/themes/init.lua | 32 ++++++++++++++++ 6 files changed, 205 insertions(+), 63 deletions(-) create mode 100644 lua/feline/themes/default.lua create mode 100644 lua/feline/themes/init.lua diff --git a/USAGE.md b/USAGE.md index 7280046..01ee13b 100644 --- a/USAGE.md +++ b/USAGE.md @@ -285,7 +285,7 @@ If a string, it'll use the given string as the name of the component highlight g If it's a table, it'll automatically generate a highlight group for you based on the given values. The hl table can have four values: `fg`, `bg`, `style` and `name`. -The `fg` and `bg` values are strings that represent the RGB hex or [name](#value-presets) of the foreground and background color of the highlight, respectively. (eg: `'#FFFFFF'`, `'white'`). If `fg` or `bg` is not provided, it uses the default foreground or background color provided in the `setup()` function, respectively. +The `fg` and `bg` values are strings that represent the RGB hex or [name](#themes) of the foreground and background color of the highlight, respectively. (eg: `'#FFFFFF'`, `'white'`). If `fg` or `bg` is not provided, it uses the default foreground or background color provided in the `setup()` function, respectively. The `style` value is a string that determines the formatting style of the component's text (do `:help attr-list` in Neovim for more info). By default it is set to `'NONE'` @@ -327,7 +327,7 @@ end There are two types of separator values that you can put in a component, which are `left_sep` and `right_sep`, which represent the separator on the left and the right side of the component, respectively. -The value of `left_sep` and `right_sep` can just be set to a string that's displayed. You can use a function that returns a string just like the other component values. The value can also be equal to the name of one of the [separator presets](#value-presets). +The value of `left_sep` and `right_sep` can just be set to a string that's displayed. You can use a function that returns a string just like the other component values. The value can also be equal to the name of one of the [separator presets](#separator-presets). The value of `left_sep` and `right_sep` can also be a table or a function returning a table. Inside the table there can be three values, `str`, `hl` and `always_visible`. `str` represents the separator string and `hl` represents the separator highlight. The separator's highlight works just like the component's `hl` value. The only difference is that the separator's `hl` by default uses the parent's background color as its foreground color. @@ -474,8 +474,8 @@ custom_providers = { } ``` -- `colors` - A table containing custom [color value presets](#value-presets). The value of `colors.fg` and `colors.bg` also represent the default foreground and background colors, respectively. -- `separators` - A table containing custom [separator value presets](#value-presets). +- `theme` - Either a string containing the color theme name or a table containing the colors. The theme's `fg` and `bg` values also represent the default foreground and background colors, respectively. To know more about Feline themes, take a look at the [Themes](#themes) section +- `separators` - A table containing custom [separator presets](#separator-presets). - `force_inactive` - A table that determines which buffers should always have the inactive statusline, even when they are active. It can have 3 values inside of it, `filetypes`, `buftypes` and `bufnames`, all three of them are tables which contain Lua patterns to match against file type, buffer type and buffer name respectively.

Default: @@ -664,22 +664,60 @@ The diagnostics and LSP providers all require the Neovim built-in LSP to be conf The diagnostics provider also provides a utility function `require('feline.providers.lsp').diagnostics_exist(type)` for checking if any diagnostics of the provided type exists. The values of `type` must be one of `'Error'`, `'Warning'`, `'Hint'` or `'Information'`. -## Value presets +## Themes -Value presets are names for colors and separators that you can use instead of the hex code or separator string, respectively. +Feline supports different themes to customize your statusline colors on the fly. A theme is a Lua table associating color names with their RGB hex codes. Feline only has one theme built-in, which is the default theme. However, it's possible to add new themes to Feline. See the [Adding new themes](#adding-new-themes) section for more info. -For your ease of use, Feline has some default color and separator values set. You can manually access them through `require('feline.defaults').colors` and `require('feline.defaults').separators` respectively. But there's a much easier way to use them, which is to just directly assign the name of the color or separator to the value, eg: +There are mainly two ways of using a theme. The first is to set the value of `theme` in the [setup function](#setup-function) to the theme name or value. The second way is through the `require('feline').use_theme` function. `use_theme` can take the theme name as an argument. For example, this is how to use the default theme: ```lua -hl = {bg = 'oceanblue'}, -right_sep = 'slant_right' +require('feline').use_theme('default') +``` + +`use_theme` can also take the theme table directly as argument, like this: + +```lua +-- Theme table +local my_theme = { + red = '#FF0000', + green = '#00FF00', + blue = '#0000FF' +} + +require('feline').use_theme(my_theme) +``` + +### Adding new themes + +If you're developing a plugin or colorscheme and wish to support Feline for that plugin / colorscheme or if you're just a user who wants to be able to quickly switch your statusline colors, you'd be glad to know that it's possible to add custom color themes for Feline. You just have to call `require('feline').add_theme` with the theme name and the colors table. Like this: + +``` +-- Theme table +local my_theme = { + red = '#FF0000', + green = '#00FF00', + blue = '#0000FF' +} + +require('feline').add_theme('my_theme_name', my_theme) ``` -Not only that, you can add your own custom colors and separators through the [setup function](#setup-function) which allows you to just use the name of the color or separator to refer to it. +The user can then later use that theme through one of the two ways mentioned above. -Below is a list of all the default value names and their values: +### Using theme colors -### Default colors +To use colors from a theme, just use the color name instead of the RGB hex code in your component's `hl` value, like this: + +```lua +hl = { + bg = 'oceanblue', + fg = 'white' +} +``` + +### Default color theme + +Feline comes with a default color theme by default, and it falls back to this theme if a color name is not found in the current theme. Here are the colors available in the default theme and their values: | Name | Value | | ----------- | ----------- | @@ -697,6 +735,16 @@ Below is a list of all the default value names and their values: | `white` | `'#FFFFFF'` | | `yellow` | `'#E1E120'` | +## Separator presets + +Instead of having to remember unicode values for separator glyphs or having to constantly copy-paste them, you can use Feline's separator presets instead. They allow you to either use Feline's [default separators](#default-separators) or your own manually defined separators (added through the [setup function](#setup-function)) by just using their name. For example: + +```lua +right_sep = 'slant_right' +``` + +Below is a list of all the default separator names and their values: + ### Default Separators | Name | Value | diff --git a/lua/feline/defaults.lua b/lua/feline/defaults.lua index a0b5fa9..011d61c 100644 --- a/lua/feline/defaults.lua +++ b/lua/feline/defaults.lua @@ -6,24 +6,9 @@ -- overriding it return { - colors = { - type = 'table', - update_default = true, - default_value = { - bg = '#1F1F23', - black = '#1B1B1B', - skyblue = '#50B0F0', - cyan = '#009090', - fg = '#D0D0D0', - green = '#60A040', - oceanblue = '#0066cc', - magenta = '#C26BDB', - orange = '#FF9000', - red = '#D10000', - violet = '#9E93E8', - white = '#FFFFFF', - yellow = '#E1E120' - } + theme = { + type = {'table', 'string'}, + default_value = 'default' }, separators = { type = 'table', diff --git a/lua/feline/generator.lua b/lua/feline/generator.lua index 9176eed..8585ab6 100644 --- a/lua/feline/generator.lua +++ b/lua/feline/generator.lua @@ -2,9 +2,6 @@ local bo = vim.bo local api = vim.api local feline = require('feline') -local providers = feline.providers -local colors = feline.colors -local separators = feline.separators local M = { -- Cached highlights @@ -13,31 +10,31 @@ local M = { -- Return true if any pattern in tbl matches provided value local function find_pattern_match(tbl, val) - return next(vim.tbl_filter(function(pattern) return val:match(pattern) end, tbl)) + return tbl and next(vim.tbl_filter(function(pattern) return val:match(pattern) end, tbl)) end -- Check if current buffer is forced to have inactive statusline local function is_forced_inactive() - local force_inactive = feline.force_inactive local buftype = bo.buftype local filetype = bo.filetype local bufname = api.nvim_buf_get_name(0) - return (force_inactive.filetypes and find_pattern_match(force_inactive.filetypes, filetype)) or - (force_inactive.buftypes and find_pattern_match(force_inactive.buftypes, buftype)) or - (force_inactive.bufnames and find_pattern_match(force_inactive.bufnames, bufname)) + return + find_pattern_match(feline.force_inactive.filetypes, filetype) or + find_pattern_match(feline.force_inactive.buftypes, buftype) or + find_pattern_match(feline.force_inactive.bufnames, bufname) end -- Check if buffer is configured to have statusline disabled local function is_disabled() - local disable = feline.disable local buftype = bo.buftype local filetype = bo.filetype local bufname = api.nvim_buf_get_name(0) - return (disable.filetypes and find_pattern_match(disable.filetypes, filetype)) or - (disable.buftypes and find_pattern_match(disable.buftypes, buftype)) or - (disable.bufnames and find_pattern_match(disable.bufnames, bufname)) + return + find_pattern_match(feline.disable.filetypes, filetype) or + find_pattern_match(feline.disable.buftypes, buftype) or + find_pattern_match(feline.disable.bufnames, bufname) end -- Evaluate a component key if it is a function, else return the value @@ -67,14 +64,18 @@ end local function parse_hl(hl, parent_hl) parent_hl = parent_hl or {} - hl.fg = hl.fg or parent_hl.fg or colors.fg - hl.bg = hl.bg or parent_hl.bg or colors.bg - hl.style = hl.style or parent_hl.style or 'NONE' + local fg = hl.fg or parent_hl.fg or feline.colors.fg + local bg = hl.bg or parent_hl.bg or feline.colors.bg + local style = hl.style or parent_hl.style or 'NONE' - if colors[hl.fg] then hl.fg = colors[hl.fg] end - if colors[hl.bg] then hl.bg = colors[hl.bg] end + if feline.colors[fg] then fg = feline.colors[fg] end + if feline.colors[bg] then bg = feline.colors[bg] end - return hl + return { + fg = fg, + bg = bg, + style = style + } end -- If highlight is a string, use it as highlight name and @@ -147,15 +148,15 @@ local function parse_sep(sep, parent_bg, is_component_empty) if is_component_empty then return '' end str = sep - hl = {fg = parent_bg, bg = colors.bg} + hl = {fg = parent_bg, bg = feline.colors.bg} else if is_component_empty and not sep.always_visible then return '' end str = evaluate_if_function(sep.str) or '' - hl = evaluate_if_function(sep.hl) or {fg = parent_bg, bg = colors.bg} + hl = evaluate_if_function(sep.hl) or {fg = parent_bg, bg = feline.colors.bg} end - if separators[str] then str = separators[str] end + if feline.separators[str] then str = feline.separators[str] end return string.format('%%#%s#%s', get_hlname(hl), str) end @@ -217,8 +218,8 @@ local function parse_provider(provider, component) -- If provider is a string and its name matches the name of a registered provider, use it if type(provider) == 'string' then - if providers[provider] then - str, icon = providers[provider](component, {}) + if feline.providers[provider] then + str, icon = feline.providers[provider](component, {}) else str = provider end @@ -234,13 +235,13 @@ local function parse_provider(provider, component) "Expected 'string' for provider name, got '%s' instead", type(provider.name) )) - elseif not providers[provider.name] then + elseif not feline.providers[provider.name] then api.nvim_err_writeln(string.format( "Provider with name '%s' doesn't exist", provider.name )) else - str, icon = providers[provider.name](component, provider.opts or {}) + str, icon = feline.providers[provider.name](component, provider.opts or {}) end end @@ -364,9 +365,7 @@ end -- Generate statusline by parsing all components and return a string function M.generate_statusline(is_active) - local components_table = feline.components - - if not components_table or is_disabled() then + if not feline.components or is_disabled() then return '' end @@ -378,7 +377,7 @@ function M.generate_statusline(is_active) statusline_type='inactive' end - local sections = components_table[statusline_type] + local sections = feline.components[statusline_type] if not sections then return '' diff --git a/lua/feline/init.lua b/lua/feline/init.lua index f046716..7326d42 100644 --- a/lua/feline/init.lua +++ b/lua/feline/init.lua @@ -4,9 +4,10 @@ local fn = vim.fn local api = vim.api local cmd = api.nvim_command +local presets = require('feline.presets') +local themes = require('feline.themes') local utils = require('feline.utils') local gen = utils.lazy_require('feline.generator') -local presets = utils.lazy_require('feline.presets') local M = {} @@ -17,13 +18,32 @@ local function parse_config(config_dict, defaults) local parsed_config = {} + local function config_has_correct_type(config_name, possible_types) + local config_type = type(config_dict[config_name]) + + -- If there's only one possible type (which means that `possible_types is a string`) + -- just check for type equality and return the result + if type(possible_types) == 'string' then + return config_type == possible_types + end + + -- Otherwise, iterate through every possible type until a match is found + for _, v in ipairs(possible_types) do + if config_type == v then + return true + end + end + + return false + end + -- Iterate through every possible configuration options, also checking their type to ensure the -- validity of the configuration and also using the defaults if a custom configuration is not -- provided for config_name, config_info in pairs(defaults) do if config_dict[config_name] == nil then parsed_config[config_name] = config_info.default_value - elseif type(config_dict[config_name]) ~= config_info.type then + elseif not config_has_correct_type(config_name, config_info.type) then api.nvim_err_writeln(string.format( "Feline: expected type '%s' for config option '%s', got '%s'", config_info.type, @@ -78,6 +98,47 @@ function M.use_preset(name) end end +-- Add a Feline color theme +function M.add_theme(name, value) + themes[name] = value +end + +-- Use a theme (can be either a string containing theme name or a table containing theme colors) +function M.use_theme(name_or_tbl) + local theme_colors + + if type(name_or_tbl) == 'string' then + if not themes[name_or_tbl] then + api.nvim_err_writeln(string.format( + "Theme '%s' not found!", + name_or_tbl + )) + + return + end + + theme_colors = themes[name_or_tbl] + else + theme_colors = name_or_tbl + end + + local colors = {} + + -- To make sure Feline falls back to default theme for missing colors, first iterate through the + -- default colors and put their values in the colors table, and then iterate through the + -- theme colors to update the default values + for k, v in pairs(themes.default) do + colors[k] = v + end + + for k, v in pairs(theme_colors) do + colors[k] = v + end + + M.colors = colors + M.reset_highlights() +end + -- Setup Feline using the provided configuration options function M.setup(config) -- Check if Neovim version is 0.5 or greater @@ -97,7 +158,6 @@ function M.setup(config) config = parse_config(config, require('feline.defaults')) - M.colors = config.colors M.separators = config.separators M.vi_mode_colors = config.vi_mode_colors M.force_inactive = config.force_inactive @@ -110,6 +170,9 @@ function M.setup(config) M.providers[k] = v end + -- Use configured theme + M.use_theme(config.theme) + -- If components table is provided, use it, else use a preset if config.components then M.components = config.components @@ -126,7 +189,7 @@ function M.setup(config) end end - M.components = presets[preset] + M.use_preset(preset) end -- Ensures custom quickfix statusline isn't loaded diff --git a/lua/feline/themes/default.lua b/lua/feline/themes/default.lua new file mode 100644 index 0000000..2774ca8 --- /dev/null +++ b/lua/feline/themes/default.lua @@ -0,0 +1,15 @@ +return { + bg = '#1F1F23', + black = '#1B1B1B', + skyblue = '#50B0F0', + cyan = '#009090', + fg = '#D0D0D0', + green = '#60A040', + oceanblue = '#0066cc', + magenta = '#C26BDB', + orange = '#FF9000', + red = '#D10000', + violet = '#9E93E8', + white = '#FFFFFF', + yellow = '#E1E120' +} diff --git a/lua/feline/themes/init.lua b/lua/feline/themes/init.lua new file mode 100644 index 0000000..a347624 --- /dev/null +++ b/lua/feline/themes/init.lua @@ -0,0 +1,32 @@ +-- Use lazy_require to only load the theme that's are being used +local api = vim.api +local lazy_require = require('feline.utils').lazy_require + +local default_themes = { + default = require('feline.themes.default'), +} + +local custom_themes = {} + +local themes_mt = { + __index = function(_, key) + if default_themes[key] then + return default_themes[key] + elseif custom_themes[key] then + return custom_themes[key] + end + end, + __newindex = function(_, key, val) + if default_themes[key] or custom_themes[key] then + api.nvim_err_writeln(string.format( + "Theme '%s' already exists!", + key + )) + else + custom_themes[key] = val + end + end, + __metatable = false +} + +return setmetatable({}, themes_mt)