diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 22fc222..6771717 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -50,6 +50,8 @@ jobs: run: | git clone --depth 1 https://github.com/nvim-lua/plenary.nvim ~/.local/share/nvim/site/pack/vendor/start/plenary.nvim git clone --depth 1 https://github.com/kyazdani42/nvim-web-devicons ~/.local/share/nvim/site/pack/vendor/start/nvim-web-devicons + git clone --depth 1 https://github.com/feline-nvim/feline.nvim ~/.local/share/nvim/site/pack/vendor/start/feline.nvim + git clone --depth 1 https://github.com/dokwork/lua-schema.nvim ~/.local/share/nvim/site/pack/vendor/start/lua-schema.nvim ln -s $(pwd) ~/.local/share/nvim/site/pack/vendor/start - name: Run tests diff --git a/Guide.md b/Guide.md index e69de29..e153b4d 100644 --- a/Guide.md +++ b/Guide.md @@ -0,0 +1,336 @@ +# Guide to feline-theme + +The best way to understand how something works is using it to create some example. +Here we're going to create a simple statusline theme with dynamic colors, which depends on +the background: + +```viml +:echo &background +> light +``` +![light_example](light_example.png) + +```viml +:echo &background +> dark +``` +![dark_example](dark_example.png) + +_Note, that changing background will not affect the statusline. Only changing the colorscheme will +have an effect._ + +## Configuration of the statusline + +### Describe statusline + +`Feline-theme` provides a way to describe components for _active_ and _inactive_ statusline. +In both cases the statusline has three zones: _left_, _middle_ and _right_. Every zone can have +unlimited sections. Sections can have arbitrary names. But, it would be better to follow next +convention: left and middle zones should have sections with chars from a to z as their names, +and right zone should have sections with names from z to a. Names can intersect in different zones, +it's ok. + +In this example we'll turn our minds only to the _active_ line. The _inactive_ line has completely +the same rules, as _active_. + +```lua +local statusline = { + active = { + left = { + -- in the first section 'a' we will have a name of the current vi mode: + a = { 'vi_mode' }, + -- then a shorten full path to the working directory will be in the section 'b': + b = { 'short_working_directory' }, + -- and the name of the current file will be in the last section 'c': + c = { 'file_name' }, + }, + middle = { + -- we'll put a current time to the middle: + a = { 'time' }, + }, + right = { + -- far right component will show the current cursor position: + z = { 'position' }, + -- info abount encoding and format of the file will go before the section + -- with cursor position: + y = { 'file_encoding', 'file_format' }, + }, + }, +} +``` + +### Describe components + +Now, let's prepare components for our statusline. Every component mentioned in the statusline +will be looked in the table with components. If a key with the name of the component is found, the +value will be used as a component description, which should follow the feline's rules of components +describing: [USAGE.md#component +values](https://github.com/feline-nvim/feline.nvim/blob/master/USAGE.md#component-values). +Otherwise, a new component with eponymous provider will be created. Regardless of how component was +created, the `name` property will be added to it. + +```lua +statusline.components = { + vi_mode = { + provider = 'vi_mode', + -- turn icon off and use full name of the mode + icon = '' + } + + short_working_directory = { + provider = function() + return vim.fn.pathshorten(vim.fn.fnamemodify(vim.fn.getcwd(), ':p')) + end, + } + + file_name = { + provider = function() + return vim.fn.expand('%:t') + end, + } + + time = { + provider = function() + return vim.fn.strftime('%H:%M') + end, + } + + file_format = { + provider = { + name = 'file_type', + opts = { + filetype_icon = true, + case = 'lowercase', + }, + }, + } +} +``` + + +### Describe colors + +Our statusline is ready, but looks ugly. To fix it we need to describe which colors should be used +in the every section and maybe add separators between sections and zones. But, before we begin +describing a theme, we need prepare palette of colors. A palette is a table with colors, where the +keys are names of the colors, and values are `#RRGGBB` colors. `feline-theme` can have a few +palettes, which will be chosen depending on the `background` option and current `statusline`. So, you +can specify colors for `dark` and/or `light` background, and/or create palette for particular +colorscheme with appropriate key. But the simplest way is create `default` palette, which will be +used when nor palette with the name of the current colorscheme, nor `light`/`dark` palettes will be +found. + +Ok, let's create a default palette with `fg` and `bg` colors, plus colors for sections `a`, `b`, +`c`, `d`, and `z`, `y`, `x`, `w`. Colors in our palette will be generated automatically, changing +brightness of the `bg` color for every next section: + +```lua +local u = require('feline-theme.utils') + +-- We begin from the default background for Statusline +-- and will make it lighter/darker (depends on vim.go.background) +-- for every next section +local gradient_colors = function() + local function change(color) + if vim.go.background == 'light' then + return u.darkening_color(color, 0.4, 0.3, 0.1) + else + return u.ligthening_color(color, 0.2, 0.2, 0.3) + end + end + local colors = {} + colors.fg = vim.go.background == 'light' and 'White' or 'Black' + colors.bg = u.get_hl_bg('Statusline') or (vim.go.background == 'light' and '#c8c8cd' or '#505050') + colors.d = change(colors.bg) + colors.c = change(colors.d) + colors.b = change(colors.c) + colors.a = change(colors.b) + + colors.z = colors.a + colors.y = colors.b + colors.x = colors.c + colors.w = colors.d + + return colors +end + +statusline.colors = { default = gradient_colors } +``` + +Colors must be recalculated, when user will change the colorscheme. This is why palette can be +represented as a function, which returns table with colors. + + +### Describe theme + +When we have prepared palettes, we're ready to create a theme for our statusline. The theme has +pretty similar structure as a statusline, but instead of list of components, sections should have +description of their highlights. The highlights follow the same rules as feline: [USAGE.md#component +highlight](https://github.com/feline-nvim/feline.nvim/blob/master/USAGE.md#component-highlight). + +Additionally to highlights, we can specify separators for zones and sections in the theme. All +separators will be added to the extreme components by the follow rules: +1. zones' separators will override sections' separators; +1. zones' separators will be created with property `always_visible = true`; +1. separators, which were described directly in the components will override any separators from the + theme. + +Rules for describe a separator the same as in the feline: [USAGE.md#component +separators](https://github.com/feline-nvim/feline.nvim/blob/master/USAGE.md#component-separators). + +We will use two utility functions, to use colors, specific for the current vi mode: +```lua +local vi_mode_bg = function() + return { + bg = require('feline.providers.vi_mode').get_mode_color(), + } +end + +-- NOTE: this function return another function! +-- This is done to be able to specify a different bacground color: +local vi_mode_fg = function(bg) + return function() + return { + fg = require('feline.providers.vi_mode').get_mode_color(), + bg = bg, + } + end +end +``` + +Our left and right zones should have  and  as separators respectively. It'll separate them from +the middle zone. The sections inside zones will have symbols  and  as separators: + +```lua +statusline.theme = { + active = { + left = { + separators = { right = ' ' }, + a = { + hl = vi_mode_bg, + separators = { right = { str = '', hl = vi_mode_fg('b') } }, + }, + b = { + hl = { bg = 'b' }, + separators = { right = { str = '', hl = { fg = 'b', bg = 'c' } } }, + }, + c = { + hl = { bg = 'c' }, + separators = { right = { str = '', hl = { fg = 'c', bg = 'd' } } }, + }, + d = { + hl = { bg = 'd' }, + separators = { right = { str = '', hl = { fg = 'd', bg = 'bg' } } }, + }, + }, + right = { + separators = { left = ' ' }, + w = { + hl = { bg = 'w' }, + separators = { left = { str = '', hl = { bg = 'bg' } } }, + }, + x = { + hl = { bg = 'x' }, + separators = { left = { str = '', hl = { bg = 'w' } } }, + }, + y = { + hl = { bg = 'y' }, + separators = { left = { str = '', hl = { bg = 'x' } } }, + }, + z = { + hl = vi_mode_bg, + separators = { left = { str = '', hl = vi_mode_fg('y') } }, + }, + }, + }, + + vi_mode = { + NORMAL = 'green', + OP = 'magenta', + INSERT = 'blue', + VISUAL = 'magenta', + LINES = 'magenta', + BLOCK = 'magenta', + REPLACE = 'violet', + ['V-REPLACE'] = 'pink', + ENTER = 'cyan', + MORE = 'cyan', + SELECT = 'yellow', + COMMAND = 'orange', + SHELL = 'yellow', + TERM = 'orange', + NONE = 'yellow', + }, +} +``` + +_Note, that the vi mode colors are part of the theme, not the colors!_ + +### Setup statusline + +Finally, the last step is setup of our statusline: + +```lua +require('feline-theme').setup_statusline(statusline) +``` + +## Statusline's commands + +In successful case, the `FelineTheme` global lua variable become available. It provides ability +to work with the current statusline as with `FelineTheme.statusline` variable. + +### Showing the components + +The final schema is not simple, and your can easily make a mistake on describing your statusline. +For debug purpose, the statusline in `feline-theme` has few methods, which can help you to find +mistakes. The first one is `build_components`, which compose together the description of the +statusline, components, colors and theme, and returns a table with feline's components. You can +print them to looking for mistakes: + +```lua +:lua vim.pretty_print(FelineTheme.statusline:build_components()) +``` + +Or you can use the second short way: + +```lua +:lua FelineTheme.statusline:show_components() +``` + +### Showing and refresh colors + +`feline-theme` creates an `autocmd` to change colors on changing the colorscheme. To do it manually, +you can run: + +```lua +:lua FelineTheme.statusline:refresh_colors() +``` + +To see the colors, which should be used according to the current vim settings, you can print the +result of the `actual_colors`: + +```lua +:lua vim.pretty_print(FelineTheme.statusline:actual_colors()) +``` + +Or shortly: + +```lua +:lua FelineTheme.statusline:show_actual_colors() +``` + +### Show the full configuration + +Obviously, you can print the full current configuration just using the `FelineTheme.statusline`: + +```lua +:lua vim.pretty_print(FelineTheme.statusline) +``` + +But, you can do it easily: + +```lua +:lua FelineTheme.statusline:show_all() +``` + +That's all for today! See you! diff --git a/README.md b/README.md index 9bdb532..44937fa 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ -# Components line +# Feline-theme ![light_example](light_example.png) ![dark_example](dark_example.png) This plugin is an extension for the [feline.nvim](https://github.com/feline-nvim/feline.nvim), which combines advantages of the templating similar to the -[lualine.nvim](https://github.com/nvim-lualine/lualine.nvim) with powerful syntax for description -components of the statusline from the `feline.nvim`. +[lualine.nvim](https://github.com/nvim-lualine/lualine.nvim) with powerful syntax for components +description of the statusline from the `feline.nvim`. ## Configuration example @@ -15,11 +15,11 @@ Let's see how to create a simple statusline: ```lua -- Prepare needed components -- - local components = { - mode = { - provider = require('feline.providers.vi_mode').get_vim_mode, - } + vi_mode = { + provider = 'vi_mode', + icon = '' + } file_name = { provider = function() @@ -29,88 +29,53 @@ local components = { } -- Describe how the statusline should look like -- - -local vi_mode_fg = function() - return { - fg = require('feline.providers.vi_mode').get_mode_color(), - bg = 'bg', - } -end - -local vi_mode_bg = function() - return { - fg = 'fg', - bg = require('feline.providers.vi_mode').get_mode_color(), - } -end - local theme = { active = { left = { - zone_separators = { right = { '', hl = vi_mode_fg } }, - sections_separators = { right = ' ' }, - sections = { - a = { hl = vi_mode_bg }, - b = { hl = vi_mode_fg } - }, + separators = { right = '', hl = { fg = 'blue' } }, + a = { hl = { bg = 'blue' } }, }, right = { - zone_separators = { left = { '', hl = vi_mode_fg } }, - sections_separators = { left = ' ' }, - sections = { - c = { hl = vi_mode_fg }, - d = { hl = vi_mode_bg }, - }, + separators = { left = { '', hl = { fg = 'green' } } }, + z = { hl = { bg = 'green' } }, }, }, - - dark = { - bg = '#282c34' - } } -- Create your oun statusline -- - -local statusline = require('compline.statusline'):new('example', { +require('feline-theme').setup_statusline({ active = { left = { - a = { 'mode' }, - c = { 'file_name' }, + a = { 'file_name' }, }, right = { - e = { 'file_type' }, - g = { 'position' }, + z = { 'vi_mode' }, }, }, - inactive = { - left = { - a = { 'file_name' }, - }, - }, - themes = { - default = theme, - }, + theme = theme, components = components }) ``` -More details about configuration you can find here: [Guide.md](Guide.md). +More details about configuration you can find in the [Guide.md](Guide.md). ## How to install +_This project **is under development**. API can be changed in non compatible way, so, it may be good +idea to use a tagged version in your own configuration._ + With [packer.nvim](https://github.com/wbthomason/packer.nvim/): ```lua use({ - 'dokwork/compline.nvim', + 'dokwork/feline-theme', requires = { 'kyazdani42/nvim-web-devicons', 'famiu/feline.nvim', - 'tpope/vim-fugitive', -- used for git components }, - -- optionally, you can setup preconfigured statusline: config = function() - require('compline.cosmosline'):setup() + -- setup your statusline on start: + require('feline-theme').setup_statusline(require('feline-theme.example')) end, }) ``` @@ -118,9 +83,6 @@ use({ ## Motivation I'm glad to use the [feline.nvim](https://github.com/feline-nvim/feline.nvim) plugin. This is a very -powerful and useful plugin for configuring the neovim statusline. But for my taste, the final +powerful and useful plugin for configuring the neovim statusline. But, to my taste, the final configuration usually looks a little bit cumbersome and messy. I prefer to separate an implementation of the components and their composition. - -This project is more the proof of concept, instead of the final solution, and currently **is under -develop**. diff --git a/dark_example.png b/dark_example.png index 283e14e..a0f8ae3 100644 Binary files a/dark_example.png and b/dark_example.png differ diff --git a/light_example.png b/light_example.png index e9108ed..d14b559 100644 Binary files a/light_example.png and b/light_example.png differ diff --git a/lua/compline/components.lua b/lua/compline/components.lua deleted file mode 100644 index f173038..0000000 --- a/lua/compline/components.lua +++ /dev/null @@ -1,82 +0,0 @@ -local p = require('compline.providers') -local i = require('compline.icons') -local c = require('compline.conditions') -local h = require('compline.highlights') - -local M = {} - ----The name of the current file relative to the current working directory. ----If file is not in the one of subdirectories of the working directory, then its ----path will be returned with: ---- * prefix "/.../" in case when the file is not in the one of home subdirectories; ---- * prefix "~/" in case when the file is in one of home subdirectories. ---- ----@type Component -M.relative_file_name = { - provider = p.relative_file_name, - short_provider = p.file_name, - enabled = c.is_buffer_not_empty, -} - ----The name of the current git branch. This component uses 'tpope/vim-fugitive' ----plugin to take info about git workspace. ---- ----@type Component -M.git_branch = { - provider = p.fugitive_branch, - icon = i.git_icon, - hl = h.git_status, -} - ----The last N directories of the current working directory path. Count of directories ----can be customized. ---- ----Custom properties: ---- * `hls` should have custom highlights for vi modes. ----The keys of this table are names of the vi mode according to ----`'feline.providers.vi_mode'.get_mode_highlight_name`. ---- ---- * `opts` can have a property 'length' with count of directories in the path. ---- ----@type Component -M.working_directory = { - provider = p.working_path, -} - ----Returns a list of languages used for spellchecking. If spellchecking is off and component ----doesn't have an icon, then only string '暈' will be returned. But, if component has an icon, ----then it behave as usually: shows a list of langs with icon when spellchecking is on, and it's ----hide when spellchecking is off. ----Example: '暈en' for english spellcheck, or just '暈' when spellchecking is off. ---- ----@type Component -M.spellcheck = { - provider = p.spellcheck_langs, - icon = i.spellcheck_icon, -} - ----@type Component -M.diagnostic_errors = { - provider = 'diagnostic_errors', - hl = { fg = 'red' }, -} - ----@type Component -M.diagnostic_warnings = { - provider = 'diagnostic_warnings', - hl = { fg = 'orange' }, -} - ----@type Component -M.diagnostic_info = { - provider = 'diagnostic_info', - hl = { fg = 'blue' }, -} - ----@type Component -M.diagnostic_hint = { - provider = 'diagnostic_hint', - hl = { fg = 'purple' }, -} - -return M diff --git a/lua/compline/conditions.lua b/lua/compline/conditions.lua deleted file mode 100644 index cd5087f..0000000 --- a/lua/compline/conditions.lua +++ /dev/null @@ -1,40 +0,0 @@ -local u = require('compline.utils') - -vim.cmd('augroup compline_git_status') -vim.cmd('autocmd!') -vim.cmd( - 'autocmd BufEnter,FocusGained,BufWritePost * let b:git_status = systemlist("git status --porcelain")' -) -vim.cmd('augroup end') - -local M = {} - ----@type fun(): boolean ----@return boolean is_buffer_empty `true` when buffer has a single line with an empty string. -M.is_buffer_empty = function() - return vim.fn.line('$') == 1 and u.is_empty(vim.fn.getline(1)) -end - ----@type fun(): boolean ----@return boolean is_buffer_not_empty `true` when buffer has at least one symbol. -M.is_buffer_not_empty = function() - return not M.is_buffer_empty() -end - ----@type fun(): boolean --- Uses 'tpope/vim-fugitive' to resolve is the file of the current buffer is in a git workspace. ----@return boolean is_git_workspace `true` when the file of the current buffer is in git workspace. ----@see vim.fn.FugitiveGitDir -M.is_git_workspace = function() - local ok, dir = pcall(vim.fn.FugitiveGitDir) - return ok and (not u.is_empty(dir)) -end - ----@type fun(): boolean --- Checks the status of files whithin the current git workspace using `git status --poreclain`. ----@return boolean is_git_changed `true` if one of the files in the current git workspace was changed. -M.is_git_changed = function() - return vim.b.git_status and next(vim.b.git_status) -end - -return M diff --git a/lua/compline/cosmosline/init.lua b/lua/compline/cosmosline/init.lua deleted file mode 100644 index 2080b27..0000000 --- a/lua/compline/cosmosline/init.lua +++ /dev/null @@ -1,27 +0,0 @@ -local Cosmosline = require('compline.statusline'):new('cosmosline', { - active = { - left = { - a = { '▊', 'file_status_icon', 'working_directory' }, - b = { 'relative_file_name' }, - }, - right = { - a = { 'diagnostic_warnings', 'diagnostic_errors' }, - b = { 'git_icon', 'git_branch' }, - c = { 'lsp_client_icon', 'treesitter_parser_icon' }, - d = { 'spellcheck_icon' }, - e = { 'position' }, - f = { 'scroll_bar' }, - }, - }, - inactive = { - left = { - a = { 'relative_file_name' }, - }, - }, - themes = { - default = require('compline.cosmosline.theme'), - }, - components = vim.tbl_extend('error', require('compline.components'), require('compline.icons')), -}) - -return Cosmosline diff --git a/lua/compline/cosmosline/theme.lua b/lua/compline/cosmosline/theme.lua deleted file mode 100644 index 6c7dbed..0000000 --- a/lua/compline/cosmosline/theme.lua +++ /dev/null @@ -1,90 +0,0 @@ -local h = require('compline.highlights') - -return { - -- TODO right zone should be rendered from the right to the left - active = { - left = { - separators = { right = { ' ', hl = { fg = 'bg', bg = 'NONE' } } }, - sections = { - a = { hl = h.vi_mode }, - b = { hl = { fg = 'fg', bg = 'bg' } }, - }, - }, - middle = { - sections = { - a = { fg = 'bg', bg = 'NONE' }, - }, - }, - right = { - separators = { left = { ' ', hl = { fg = 'bg', bg = 'NONE' } } }, - sections = { - a = { hl = { fg = 'fg', bg = 'bg' }, rs = { ' | ', hl = { fg = 'blue' } } }, - b = { hl = { fg = 'fg', bg = 'bg' }, rs = { ' | ', hl = { fg = 'blue' } } }, - c = { hl = { fg = 'fg', bg = 'bg' }, rs = { ' | ', hl = { fg = 'blue' } } }, - d = { hl = { fg = 'fg', bg = 'bg' }, rs = { ' | ', hl = { fg = 'blue' } } }, - e = { hl = { fg = 'fg', bg = 'bg' }, rs = { ' | ', hl = { fg = 'blue' } } }, - f = { hl = h.vi_mode }, - }, - }, - }, - - inactive = { - left = { a = { fg = 'fg', bg = 'bg' } }, - }, - - vi_mode = { - NORMAL = 'blue', - OP = 'magenta', - INSERT = 'green', - VISUAL = 'magenta', - LINES = 'magenta', - BLOCK = 'magenta', - REPLACE = 'violet', - ['V-REPLACE'] = 'pink', - ENTER = 'cyan', - MORE = 'cyan', - SELECT = 'yellow', - COMMAND = 'orange', - SHELL = 'yellow', - TERM = 'orange', - NONE = 'yellow', - }, - - dark = { - fg = '#abb2bf', - bg = '#282c34', - yellow = '#e8a561', - cyan = '#56b6c2', - grey = '#5c6370', - green = '#79a15c', - orange = '#e5c07b', - purple = '#5931a3', - magenta = '#c678dd', - blue = '#61afef', - red = '#e06c75', - black = '#000000', - white = '#abb2bf', - oceanblue = '#45707a', - violet = '#d3869b', - skyblue = '#7daea3', - }, - - light = { - fg = '#565c69', - bg = '#dbdbdb', - yellow = '#e8a561', - cyan = '#56b6c2', - grey = '#abb2bf', - green = '#79a15c', - orange = '#ab7f2e', - purple = '#5931a3', - magenta = '#c678dd', - blue = '#3974a8', - red = '#e06c75', - black = '#000000', - white = '#abb2bf', - oceanblue = '#45707a', - violet = '#d3869b', - skyblue = '#7daea3', - }, -} diff --git a/lua/compline/example/components.lua b/lua/compline/example/components.lua deleted file mode 100644 index 74db25a..0000000 --- a/lua/compline/example/components.lua +++ /dev/null @@ -1,13 +0,0 @@ -local M = {} - -M.mode = { - provider = require('feline.providers.vi_mode').get_vim_mode, -} - -M.file_name = { - provider = function() - return vim.fn.expand('%:t') - end, -} - -return M diff --git a/lua/compline/example/highlights.lua b/lua/compline/example/highlights.lua deleted file mode 100644 index 5b668f6..0000000 --- a/lua/compline/example/highlights.lua +++ /dev/null @@ -1,12 +0,0 @@ -local M = {} - -M.vi_mode = function() - return { - name = require('feline.providers.vi_mode').get_mode_highlight_name(), - fg = require('feline.providers.vi_mode').get_mode_color(), - bg = 'bg', - style = 'bold', - } -end - -return M diff --git a/lua/compline/example/init.lua b/lua/compline/example/init.lua deleted file mode 100644 index cb37447..0000000 --- a/lua/compline/example/init.lua +++ /dev/null @@ -1,23 +0,0 @@ -local example = require('compline.statusline'):new('example', { - active = { - left = { - a = { 'mode' }, - b = { 'file_name' }, - }, - right = { - c = { 'file_type' }, - d = { 'position' }, - }, - }, - inactive = { - left = { - a = { 'file_name' }, - }, - }, - themes = { - default = require('compline.example.theme'), - }, - components = require('compline.example.components'), -}) - -return example diff --git a/lua/compline/example/theme.lua b/lua/compline/example/theme.lua deleted file mode 100644 index ed54c0c..0000000 --- a/lua/compline/example/theme.lua +++ /dev/null @@ -1,40 +0,0 @@ -local vi_mode_fg = function() - return { - fg = require('feline.providers.vi_mode').get_mode_color(), - bg = 'bg', - } -end - -local vi_mode_bg = function() - return { - fg = 'fg', - bg = require('feline.providers.vi_mode').get_mode_color(), - } -end - -local theme = { - active = { - left = { - zone_separators = { right = { '', hl = vi_mode_fg } }, - sections_separators = { right = ' ' }, - sections = { - a = { hl = vi_mode_bg }, - b = { hl = vi_mode_fg }, - }, - }, - right = { - zone_separators = { left = { '', hl = vi_mode_fg } }, - sections_separators = { left = ' ' }, - sections = { - c = { hl = vi_mode_fg }, - d = { hl = vi_mode_bg }, - }, - }, - }, - - colors = { - bg = '#282c34', - }, -} - -return theme diff --git a/lua/compline/highlights.lua b/lua/compline/highlights.lua deleted file mode 100644 index 5079e0c..0000000 --- a/lua/compline/highlights.lua +++ /dev/null @@ -1,124 +0,0 @@ -local c = require('compline.conditions') -local u = require('compline.utils') -local vi_mode = u.lazy_load('feline.providers.vi_mode') - -local M = {} - ----@type fun(hls: table): Highlight ----Returns highlight according to the current vi mode. -M.vi_mode = function(cmp) - local hls = cmp and cmp.hls or {} - return hls[vi_mode.get_vim_mode()] or { - fg = vi_mode.get_mode_color(), - } -end - ----@type fun(hls: table): Highlight ----Returns a highlight according to the current git status. ---- ----@param hls table # custom highlights with possible properties: ---- * `inactive: Highlight` a highlight which sould be used when git is not active; ---- * `changed: Highlight` a highlight which sould be used when at least one file is changed; ---- * `commited: Highlight` a highlight which sould be used when no one change exist. ---- ----@return Highlight # actual highlight according to the current git status. -M.git_status = function(cmp) - local hls = u.merge(cmp and cmp.hls, { - inactive = { fg = 'grey' }, - changed = { fg = 'orange' }, - commited = { fg = 'green' }, - }) - -- TODO move git utils to the separate script - if not c.is_git_workspace() then - return hls.inactive - end - if c.is_git_changed() then - return hls.changed - else - return hls.commited - end -end - ----@type fun(hls: table): Highlight ----Returns highlight according to the current state of the spellchecking. ---- ----@param hls table # custom highlights with possible properties: ---- * `active: Highlight` a highlight which sould be used when spellcheck is turned on; ---- * `inactive: Highlight` a highlight which sould be used when spellcheck is turned off; ---- ----@return function # actual highlight for spellchecking depending on its state. -M.spellcheck = function(cmp) - local hls = u.merge(cmp and cmp.hls, { - active = { fg = 'fg' }, - inactive = { fg = 'grey' }, - }) - if vim.wo.spell then - return hls.active - else - return hls.inactive - end -end - ----@type fun(hls: table): Highlight ----Returns a highlight according to the first attached lsp client. ----The color will be taken from the 'nvim-web-devicons' or 'hls.fg' will be used. If no one ----client is attached, then 'hls.client_off' will be used as foreground color. ---- ----@param hls table # custom highlights with possible properties: ---- * `unknown` a highlight which will be used if a color for the attached lsp ---- client is not found; ---- * `client_off` a highlight which will be used if no one lsp client is attached; ---- ----@return Highlight # highlight for the first attached lsp client. -M.lsp_client = function(cmp) - local hls = u.merge(cmp and cmp.hls, { - unknown = 'fg', - client_off = 'grey', - }) - local client = u.lsp_client() - local icon = u.lsp_client_icon({}, client) - return { - fg = (u.is_lsp_client_ready(client) and (icon and icon.color) or (icon and hls.unknown)) - or hls.client_off, - } -end - -M.treesitter_parser = function(cmp) - local hls = u.merge(cmp and cmp.hls, { - active = { fg = 'green' }, - inactive = { fg = 'grey' }, - }) - -- TODO move to conditions - local ok, _ = pcall(vim.treesitter.get_parser, 0) - if ok then - return hls.active - else - return hls.inactive - end -end - ----@type fun(hls: table): Highlight ---- ----@param hls table # custom highlights with possible properties: ---- * `default: Highlight` a highlight which sould be used when the file is not changed; ---- * `changed: Highlight` a highlight which sould be used when the file is changed; ---- * `read_only: Highlight` a highlight which sould be used when the file is in read only mode. ---- ----@return Highlight # actual highlight according to the current file state. -M.file_status = function(hls) - local hls = u.merge(hls, { - default = { name = 'FCFileDefault', fg = 'fg' }, - changed = { name = 'FCFileChanged', fg = 'orange' }, - read_only = { name = 'FCFileReadOnly', fg = 'red' }, - }) - if vim.bo.readonly then - return hls.read_only - end - if vim.bo.modified then - return hls.changed - else - return hls.default - end -end - -return M diff --git a/lua/compline/icons.lua b/lua/compline/icons.lua deleted file mode 100644 index b8f1f65..0000000 --- a/lua/compline/icons.lua +++ /dev/null @@ -1,85 +0,0 @@ -local h = require('compline.highlights') -local u = require('compline.utils') - -local M = {} - -M.file_status_icon = { - provider = function(c) - return (vim.bo.readonly and c.readonly_icon) or (vim.bo.modified and c.modified_icon) or '' - end, - hl = h.file_status, - readonly_icon = '  ', - modified_icon = '  ', -} - --- Returns an icon for the first lsp client attached to the current buffer. --- Icon will be taken from the `icons` field of this component or from the module --- 'nvim-web-devicons' if it's installed. If no one client will be found, --- the `icons.client_off` or 'ﮤ' will be used. --- --- `icons` an optional table with icons for possible lsp clients. Keys are names of --- the lsp clients in lowercase; Values are icons; Also, it can have two special keys: --- * `unknown` an optional string with icon for unknown lsp client. Default is '?'; --- * `client_off` an optional string with icon which means that no one client is --- attached to the current buffer. Default is 'ﮤ'; -M.lsp_client_icon = { - provider = function(cmp) - local client = u.lsp_client() - if not client then - return cmp.icons.client_off - end - local dev_icon = u.lsp_client_icon(cmp.icons, client) - return dev_icon and dev_icon.icon or cmp.icons.unknown - end, - hl = h.lsp_client, - icons = { - unknown = '?', - client_off = 'ﮤ', - }, - hls = { - unknown = 'fg', - client_off = 'grey', - }, -} - ----Returns an icon symbolizing state of the spellchecking. ----When spellchecking is on, the icon will have `hls.active` color. ----When spellchecking is off, the icon will have `hls.inactive` color. -M.spellcheck_icon = { - provider = function(cmp) - return cmp and cmp.icon_symbol - end, - hl = h.spellcheck, - icon_symbol = '暈', - hls = { - active = 'fg', - inactive = 'grey', - }, -} - -M.git_icon = { - provider = function(cmp) - return cmp and cmp.icon_symbol - end, - hl = h.git_status, - icon_symbol = ' ', - hls = { - inactive = { fg = 'grey' }, - changed = { fg = 'orange' }, - commited = { fg = 'green' }, - }, -} - -M.treesitter_parser_icon = { - provider = function(cmp) - return cmp and cmp.icon_symbol - end, - hl = h.treesitter_parser, - icon_symbol = '  ', - hls = { - active = { fg = 'green' }, - inactive = { fg = 'grey' }, - }, -} - -return M diff --git a/lua/compline/metadata.lua b/lua/compline/metadata.lua deleted file mode 100644 index c2a59d5..0000000 --- a/lua/compline/metadata.lua +++ /dev/null @@ -1,67 +0,0 @@ ------------------------------------------------------------------- --- This script contains EmmyLua Annotations for all used types. -- ------------------------------------------------------------------- - ----@alias LspClient table #an object which returns from the `vim.lsp.client()`. - ----@alias DevIcon table #an object which returns from the 'nvim-web-devicons' module. - ----@alias RGB string # RGB hex color description - ----@alias Color string # a name of the color or RGB hex color description - ----@alias Highlight string|table|function # similar to the highlight from the Feline, but a function ----can take a table as an argument. - ----@alias Provider string|table|function - ----@class Icon ----@field str string ----@field hl Highlight ----@field always_visible boolean - ----@class FelineComponent # see complete description here: |feline-components| ----@field name string ----@field provider Provider ----@field hl string|table|function # a description of the highlight according to the |feline-Component-highlight| ----@field icon Icon ----@field enabled boolean - ----@class FelineSetup # see |feline-setup-function| ----@field components table ----@field conditional_components table ----@field custom_providers table ----@field theme string|table ----@field separators table ----@field force_inactive table - ----@class Component : FelineComponent ----@field component string # a name of the existing component which will be used as a prototype. ----@field hls table # custom highlights for the component. - ----@alias Section Component[] - ----@class Line ----@field left table ----@field middle table ----@field right table - ----@class Theme ----@field colors table # key - name of a color; value - RGB hex color description ----@field vi_mode table # key - name of the vi mode; value - RGB hex color description - ----@class Library # library of reusable components. ----@field components table ----@field providers table ----@field highlights table ----@field icons table ----@field themes table - ----@class Statusline ----@field name string ----@field active Section[] list of sections with components for an active window. ----@field inactive Section[] list of sections with components for an inactive window. ----@field theme? Theme optional name of particular theme which should be used with this ---- statusline. If absent, a theme with the same name as the current ---- colorscheme will be taken (if exists) ----@field lib Library diff --git a/lua/compline/providers.lua b/lua/compline/providers.lua deleted file mode 100644 index 07e97f4..0000000 --- a/lua/compline/providers.lua +++ /dev/null @@ -1,85 +0,0 @@ -local c = require('compline.conditions') -local u = require('compline.utils') - -local M = {} - ----@type fun(): string ---- ----@return string # the name with extension of the file from the current buffer. -M.file_name = function() - return vim.fn.expand('%:t') -end - ----@type fun(): string ----Resolves the name of the current file relative to the current working directory. ----If file is not in the one of subdirectories of the working directory, then its ----path will be returned with: ---- * prefix "/.../" in case when the file is not in the one of home subdirectories; ---- * prefix "~/" in case when the file is in one of home subdirectories. ---- ----@return string # the name of the file relative to the current working directory. -M.relative_file_name = function() - local full_name = vim.fn.expand('%:p') - local name = vim.fn.expand('%:.') - if name == full_name then - name = vim.fn.expand('%:~') - end - if name == full_name then - name = '/.../' .. vim.fn.expand('%:t') - end - return name -end - ----@type fun(component: FelineComponent): string ----Cuts the current working path and gets the `opts.depth` directories from the end ----with prefix ".../". For example: inside the path `/3/2/1` this provider will return ----the string ".../2/1" for depth 2. If `opts.depth` is more then directories in the path, ----then path will be returned as is. ---- ----@param component FelineComponent the current component with properties: ----* `opts.depth: number` it will be used as a count of the last directories in the working path. Default is 2. ---- ----@return string # last `opts.depth` ac count of directories of the current working path. -M.working_path = function(component) - local full_path = vim.fn.getcwd() - local count = component.opts and component.opts.depth or 1 - local sep = '/' -- FIXME: use system separator - local dirs = vim.split(full_path, sep, { plain = true, trimempty = true }) - local result = '...' .. sep - if count > #dirs then - return full_path - end - if count <= 0 then - return result - end - local tail = vim.list_slice(dirs, #dirs - count + 1, #dirs) - for _, dir in ipairs(tail) do - result = result .. dir .. sep - end - return result -end - ----@type fun(): string ----@return string # a list of languages used to spell check or an empty string. -M.spellcheck_langs = function() - if vim.wo.spell then - return vim.bo.spelllang - else - return '' - end -end - ----@type fun(): string ----Returns the curent git branch. ----It uses `vim.fn.FugitiveHead` to take a current git branch. ---- ----@return string branch a name of the current branch or empty string; -M.fugitive_branch = function() - if c.is_git_workspace() then - return vim.fn.FugitiveHead() - else - return '' - end -end - -return M diff --git a/lua/compline/schema/feline.lua b/lua/compline/schema/feline.lua deleted file mode 100644 index 2e248f7..0000000 --- a/lua/compline/schema/feline.lua +++ /dev/null @@ -1,25 +0,0 @@ -local M = {} - -M.color = 'string' - -M.highlight = { - oneof = { - 'string', - 'function', - { - table = { - { key = 'fg', value = M.color }, - { key = 'bg', value = M.color }, - }, - }, - }, -} - -M.component = { - table = { - { key = 'provider', value = { oneof = { 'function', 'string' } } }, - { key = 'hl', value = M.highlight }, - }, -} - -return M diff --git a/lua/compline/schema/init.lua b/lua/compline/schema/init.lua deleted file mode 100644 index 4f382ba..0000000 --- a/lua/compline/schema/init.lua +++ /dev/null @@ -1,431 +0,0 @@ -local M = {} - --- const = | { const = } --- type = 'string' | 'number' | 'boolean' | 'function' | 'any' | 'nil' | const --- | { list = type } --- | { oneof = [ type ] } --- | { table = { keys = type, values = type } | [ { key = const, value = type} ] } - -M.const = { oneof = { 'string', { table = { key = 'const', value = 'string' } } } } - -M.list = function() - return { - table = { - { key = 'list', value = M.type, required = true }, - { key = 'non_empty', value = 'boolean' }, - }, - } -end - -M.oneof = function() - return { - table = { key = 'oneof', value = { list = M.type } }, - } -end - -M.table = function() - return { - table = { - key = 'table', - value = { - oneof = { - { - table = { - { key = 'key', value = M.type, required = true }, - { key = 'value', value = M.type, required = true }, - }, - }, - { - list = { - table = { - { key = 'key', value = M.type, required = true }, - { key = 'value', value = M.type, required = true }, - }, - }, - non_empty = true, - }, - }, - }, - }, - } -end - -M.primirives = { - 'boolean', - 'string', - 'number', - 'function', - 'nil', -} - -M.type = function() - local oneof = vim.tbl_extend('keep', {}, M.primirives) - table.insert(oneof, M.const) - table.insert(oneof, M.list()) - table.insert(oneof, M.table()) - table.insert(oneof, M.oneof()) - - return { - oneof = oneof, - } -end - -M.is_primitive = function(object) - return vim.tbl_contains(M.primirives, object) -end - -M.is_const = function(object) - if M.is_primitive(object) then - return false - end - if type(object) == 'table' then - return object[1] == 'const' - end - return true -end - -M.name_of_type = function(typ) - if type(typ) == 'string' then - return typ - end - if type(typ) == 'table' then - local typ = next(typ) - return typ - end - if M.is_const(typ) then - return string.format('const `%s`', typ) - end - error('Unsupported type ' .. vim.inspect(typ)) -end - -local PathToError = {} - -function PathToError:new(object, schema) - local p = {} - -- pointer to the current validated position in the object - p.current_object = self.current_object - p.current_object_key = self.current_object_key - -- pointer to the current validated position in the schema - p.current_schema = self.current_schema - p.current_schema_key = self.current_schema_key - setmetatable(p, { - __index = self, - __tostring = function(t) - return string.format( - '%s\n\nValidated value: %s\n\nValidated schema: %s', - t.error_message or '', - vim.inspect(t.object), - vim.inspect(t.schema) - ) - end, - }) - if not (object or schema) then - return p - end - - -- adding a new elements to the path -- - - if not p.current_schema then - p.object = object - p.schema = schema - p.current_object = object - p.current_schema = schema - elseif p.current_schema.list then - p.current_object[p.current_object_key] = object - p.current_object = object - p.current_object_key = nil - elseif p.current_schema.table then - local kv = p.current_schema.table[p.current_schema_key] - if not kv or kv.value then - -- key and value are already set, we should prepare a new key-value schema now - kv = {} - p.current_schema.table[p.current_schema_key] = kv - end - if not kv.key then - -- we should set a key - kv.key = schema - p.current_object[object] = '?' - p.current_schema = schema - p.current_object = object - p.current_schema_key = nil - p.current_object_key = nil - else - -- a key is already set, we should set a value now - kv.value = schema - p.current_object[p.current_object_key] = object - p.current_schema = schema - p.current_object = object - p.current_schema_key = nil - p.current_object_key = nil - end - else - p.current_object = object - p.current_schema = schema - p.current_object_key = nil - p.current_schema_key = nil - end - - return p -end - -function PathToError:wrong_type(expected_type, obj) - self.error_message = string.format( - 'Wrong type. Expected <%s>, but actual was <%s>.', - M.name_of_type(expected_type), - type(obj) - ) - return self -end - -function PathToError:wrong_type_in_schema(type_name, schema) - self.error_message = string.format( - 'Unknown type `%s` in the schema: %s', - type_name, - vim.inspect(schema) - ) - return self -end - -function PathToError:wrong_value(expected, actual) - self.error_message = string.format( - 'Wrong value "%s". Expected "%s".', - tostring(actual), - tostring(expected) - ) - return self -end - -function PathToError:wrong_oneof(value, options) - self.error_message = string.format( - 'Wrong oneof value: %s. Expected values %s.', - vim.inspect(value), - vim.inspect(options) - ) - return self -end - -function PathToError:empty_list_of(el_type) - self.error_message = string.format( - 'No one element in the none empty list of %s', - M.name_of_type(el_type) - ) - return self -end - -function PathToError:wrong_kv_types_schema(kv_types) - self.error_message = string.format( - "Wrong schema. It should have description for 'key' and 'value', but it doesn't: `%s`", - vim.inspect(kv_types) - ) - return self -end - -function PathToError:wrong_schema_of(typ, type_schema) - self.error_message = string.format( - 'Wrong schema of the %s. Expected table, but was %s.', - typ, - type(type_schema) - ) - return self -end - -function PathToError:required_key_not_found(kv_types, orig_table) - self.error_message = string.format( - 'Required key `%s` was not found.\nKeys in the original table were:\n%s', - vim.inspect(kv_types.key), - vim.inspect(vim.tbl_keys(orig_table)) - ) - return self -end - -function PathToError:required_pair_not_found(kv_types, orig_table) - self.error_message = string.format( - 'Required pair with key = `%s` and value = `%s` was not found.\nOriginal table was:\n%s', - vim.inspect(kv_types.key), - vim.inspect(kv_types.value), - vim.inspect(orig_table) - ) - return self -end - -local function validate_const(value, schema, path) - local path = path:new(value, schema) - if value ~= schema then - return false, path:wrong_value(schema, value) - end - return true -end - -local function validate_list(list, el_type, non_empty, path) - local path = path:new({}, { list = el_type, non_empty = non_empty }) - if type(list) ~= 'table' then - return false, path:wrong_type('table', list) - end - - if non_empty and #list == 0 then - return false, path:empty_list_of(el_type) - end - - for i, el in ipairs(list) do - path.current_object_key = i - local _, err = M.validate(el, el_type, path) - if err then - return false, err - end - end - return true -end - -local function validate_oneof(value, options, path) - local path = path:new(value, { oneof = options }) - if type(options) ~= 'table' then - return nil, path:wrong_schema_of('oneof', options) - end - for _, opt in ipairs(options) do - -- we do not pass any path here to avoid adding not applicable opt - if M.validate(value, opt) then - return true - end - end - return false, path:wrong_oneof(value, options) -end - -local function validate_table(orig_tbl, kvs_schema, path) - local path = path:new({}, { table = {} }) - if type(kvs_schema) ~= 'table' then - return false, path:wrong_schema_of('table', kvs_schema) - end - - if type(orig_tbl) ~= 'table' then - return false, path:wrong_type('table', orig_tbl) - end - - local function split_list(list) - local required = {} - local optional = {} - for _, v in ipairs(list) do - if type(v) == 'table' and v.required then - table.insert(required, v) - else - table.insert(optional, v) - end - end - return required, optional - end - - local function validate_key_value(unvalidated_tbl, kv_types, is_strict) - if not (kv_types.key and kv_types.value) then - return false, path:wrong_kv_types_schema(kv_types) - end - - local at_least_one_key_passed = false - local at_least_one_pair_passed = false - - for k, v in pairs(unvalidated_tbl) do - path.current_object_key = k - local _, err = M.validate(k, kv_types.key, path) - if not err then - at_least_one_key_passed = true - - _, err = M.validate(v, kv_types.value, path) - -- if key is valid, but value is not, - -- then validation must be failed regadles of is_strict - if err then - return false, err - end - - at_least_one_pair_passed = true - - -- remove validated key - unvalidated_tbl[k] = nil - - -- constant can be checked only once - if M.is_const(kv_types.key) then - return true - end - end - end - - if is_strict and not at_least_one_key_passed then - return false, path:required_key_not_found(kv_types, unvalidated_tbl) - end - - if is_strict and not at_least_one_pair_passed then - return false, path:required_pair_not_found(kv_types, unvalidated_tbl) - end - - return true - end - - local function validate_keys(unvalidated_tbl, kv_schemas, is_strict) - for i, kv_schema in ipairs(kv_schemas) do - path.current_schema_key = i - local _, err = validate_key_value(unvalidated_tbl, kv_schema, is_strict) - if err then - return false, err - end - end - return true - end - - -- this instance will be changed on validation - local unvalidated_tbl = vim.tbl_extend('error', {}, orig_tbl) - if kvs_schema.key and kvs_schema.value then - path.current_schema_key = 1 - return validate_key_value(unvalidated_tbl, kvs_schema, true) - else - local required, optional = split_list(kvs_schema) - local _, err = validate_keys(unvalidated_tbl, required, true) - if err then - return false, err - end - return validate_keys(unvalidated_tbl, optional, false) - end -end - ----@type fun(object: any, schema: table) ---- Checks that {object} sutisfied to the {schema} or raises error. ---- You can use safe version `call_validate` to avoid error and use returned status ---- instead. -M.validate = function(object, schema, path) - local path = path or PathToError:new() - - local type_name, type_schema, type_value - if type(schema) == 'function' then - return M.validate(object, schema(), path) - elseif type(schema) == 'table' then - type_name, type_schema = next(schema) - type_value = type_name == 'const' and type_schema or nil - elseif M.is_const(schema) then - type_name = 'const' - type_value = schema - else - type_name = schema - end - - if type_name == 'table' then - return validate_table(object, type_schema, path) - end - if type_name == 'oneof' then - return validate_oneof(object, type_schema, path) - end - if type_name == 'list' or type_name == 'non_empty' then - return validate_list(object, schema.list, schema.non_empty, path) - end - if type_name == 'const' then - return validate_const(object, type_value, path) - end - - if not M.is_primitive(type_name) then - return false, path:wrong_type_in_schema(type_name, schema) - end - -- flat constants or primitives - path = path:new(object, schema) - local ok = type(object) == type_name or object == type_value - if not ok then - return false, path:wrong_type(type_name, object) - end - return true -end - -return M diff --git a/lua/compline/schema/statusline.lua b/lua/compline/schema/statusline.lua deleted file mode 100644 index 45e8f20..0000000 --- a/lua/compline/schema/statusline.lua +++ /dev/null @@ -1,46 +0,0 @@ -local f = require('compline.schema.feline') -local t = require('compline.schema.theme') - -local M = {} - -M.section = { list = 'string' } - -M.zone = { - table = { - key = 'string', - value = M.section, - }, -} - -M.line = { - table = { - key = { oneof = { 'left', 'middle', 'right' } }, - value = M.zone, - }, -} - -M.statusline = { - table = { - { - key = { oneof = { 'active', 'inactive' } }, - value = M.line, - }, - { - key = 'themes', - value = { - table = { - { key = 'default', value = t.theme, required = true }, - { key = 'string', value = t.theme }, - }, - }, - required = true, - }, - { - key = 'components', - value = { table = { key = 'string', value = f.component } }, - required = true, - }, - }, -} - -return M diff --git a/lua/compline/schema/theme.lua b/lua/compline/schema/theme.lua deleted file mode 100644 index 7de3698..0000000 --- a/lua/compline/schema/theme.lua +++ /dev/null @@ -1,97 +0,0 @@ -local f = require('compline.schema.feline') - -local M = {} - -M.separator = { - oneof = { - 'string', - { - table = { - { key = 1, value = 'string', required = true }, - { key = 'hl', value = f.highlight }, - }, - }, - }, -} - -M.separators = { - table = { - { key = 'left', value = M.separator }, - { key = 'right', value = M.separator }, - }, -} - -M.sections = { - table = { - key = 'string', - value = { - table = { - { key = 'hl', value = f.highlight, required = true }, - { key = { oneof = { 'sr', 'sl' } }, value = M.separator }, - }, - }, - }, -} - -M.zone = { - table = { - { - key = 'zone_separators', - value = M.separators, - }, - { - key = 'sections_separators', - value = M.separators, - }, - { - key = 'sections', - value = M.sections, - required = true, - }, - }, -} - -M.line = { - table = { - { key = { oneof = { 'left, middle', 'right' } }, value = M.zone }, - }, -} - -M.colors = { - table = { key = 'string', value = 'string' }, -} - -M.vi_mode = { - table = { - key = { - oneof = { - 'NORMAL', - 'OP', - 'INSERT', - 'VISUAL', - 'LINES', - 'BLOCK', - 'REPLACE', - 'V-REPLACE', - 'ENTER', - 'MORE', - 'SELECT', - 'COMMAND', - 'SHELL', - 'TERM', - 'NONE', - }, - }, - value = 'string', - }, -} - -M.theme = { - table = { - { key = { oneof = { 'active', 'inactive' } }, value = M.line }, - { key = { oneof = { 'colors', 'dark', 'light' } }, value = M.colors, required = true }, - { key = 'vi_mode', value = M.vi_mode, reqired = true }, - }, -} - -return M diff --git a/lua/compline/statusline.lua b/lua/compline/statusline.lua deleted file mode 100644 index 6bd8496..0000000 --- a/lua/compline/statusline.lua +++ /dev/null @@ -1,183 +0,0 @@ -local u = require('compline.utils') -local feline = u.lazy_load('feline') - -local resolve_component = function(components, component_name) - return components[component_name] or { provider = component_name } -end - -local add_highlight = function(component, theme_hl) - component.hl = component.hl or theme_hl -end - -local add_separator = function(component, sections_seps, separator, side) - local sep = separator or sections_seps[side] - if type(sep) == 'table' then - component[side .. '_sep'] = { - str = sep[1], - hl = sep.hl, - } - elseif type(sep) == 'string' then - component[side .. '_sep'] = { - str = sep, - } - end -end - -local separator_as_component = function(sep) - if type(sep) == 'string' then - return { provider = sep } - elseif type(sep) == 'table' then - return { provider = sep[1], hl = sep.hl } - else - error('Unexpected separator: ' .. vim.inspect(sep)) - end -end - ----@param line table ----@param line_name string ----@param zone_name string ----@param theme table ----@param components table -local build_zone = function(line, line_name, zone_name, theme, components) - local result = {} - local theme_sections = vim.tbl_get(theme, line_name, zone_name, 'sections') or {} - local sections_separators = vim.tbl_get(theme, line_name, zone_name, 'sections_separators') - or {} - local zone_separators = vim.tbl_get(theme, line_name, zone_name, 'zone_separators') or {} - local sections = line[zone_name] - sections = sections ~= 'nil' and sections or {} - - local j = 0 - -- add a left separator to the zone - if zone_separators.left then - j = j + 1 - result[j] = separator_as_component(zone_separators.left) - end - -- now, resolve components in the every section a, b, c, etc... - for section_name, section in u.sorted_by_keys(sections) do - -- 'nil' is an option to remove existed section when extends existed Statusline - if section ~= 'nil' then - local theme_section = theme_sections[section_name] or {} - -- resolve components - for n, component_name in ipairs(section) do - j = j + 1 - local component = resolve_component(components, component_name) - add_highlight(component, theme_section.hl) - if n == 1 then - -- add left section's separator - add_separator(component, sections_separators, theme_section.ls, 'left') - end - if n == #section then - -- add right section's separator - add_separator(component, sections_separators, theme_section.rs, 'right') - end - result[j] = component - end - end - end - -- add a right separator to the zone - if zone_separators.right then - j = j + 1 - result[j] = separator_as_component(zone_separators.right) - end - return result -end - -local build_line = function(statusline, line_name) - local result = {} - local theme = statusline.theme or {} - local components = statusline.components or {} - local line = statusline[line_name] - local i = 0 - for _, zone_name in pairs({ 'left', 'middle', 'right' }) do - i = i + 1 - result[i] = build_zone(line, line_name, zone_name, theme, components) - end - return result -end - -local Statusline = { - -- user should be able to not specify components in some case at all - active = nil, - inactive = nil, - components = {}, - themes = {}, -} - ----@fun(name: string, customization: Statusline): Statusline -function Statusline:new(name, customization) - assert(type(name) == 'string', 'Statusline must have a name.') - - local x = vim.tbl_deep_extend('force', self, customization or {}) - x.name = name - return x -end - -function Statusline:validate() - local statusline_schema = require('compline.schema.statusline').statusline - local ok, schema = pcall(require, 'compline.schema') - if not ok then - error('To validate statusline schema, "compline.schema" module should be installed.') - end - local ok, err = schema.validate(self, statusline_schema) - - assert(ok, tostring(err)) -end - -function Statusline:build_components() - self.theme = self.themes and (self.themes[vim.g.colors_name] or self.themes.default) - - local result = {} - for _, line_name in ipairs({ 'active', 'inactive' }) do - local line = self[line_name] - if line and line ~= 'nil' then - result[line_name] = build_line(self, line_name) - end - end - return result -end - -function Statusline:select_theme() - local feline_themes = require('feline.themes') - local background = vim.o.background or 'colors' - local theme = string.format('%s_%s_%s', self.name, vim.g.colors_name, background) - local default = string.format('%s_%s_%s', self.name, 'default', background) - - theme = feline_themes[theme] or feline_themes[default] - if theme then - feline.use_theme(theme) - return - end -end - ----@return FelineSetup # table which were used to setup feline. -function Statusline:setup() - local config = {} - config.components = self:build_components() - config.vi_mode_colors = self.theme.vi_mode - - feline.setup(config) - - local feline_themes = require('feline.themes') - for theme_name, theme in pairs(self.themes) do - feline_themes[self.name .. '_' .. theme_name .. '_colors'] = theme.colors - feline_themes[self.name .. '_' .. theme_name .. '_dark'] = theme.dark - feline_themes[self.name .. '_' .. theme_name .. '_light'] = theme.light - end - - self:select_theme() - - -- change the theme on every changes colorscheme or background - local group = vim.api.nvim_create_augroup('compline_select_theme', { clear = true }) - vim.api.nvim_create_autocmd('ColorScheme', { - pattern = '*', - group = group, - callback = function() - self:select_theme() - end, - }) - - return config -end - -return Statusline diff --git a/lua/compline/test.lua b/lua/compline/test.lua deleted file mode 100644 index f5ee7d0..0000000 --- a/lua/compline/test.lua +++ /dev/null @@ -1,35 +0,0 @@ -local M = {} - ----@type fun(table: table, prop: string | number, mock: any, test: function): any --- Replaces a value in the {table} with a key {prop} by the {mock], and run the {test}. --- Then restores an original value of the {table}, when the {test} is completed --- (despite of errors), and returns thr result of the {test}. -M.use_mocked_table = function(table, prop, mock, test) - local orig = table[prop] - table[prop] = mock - local ok, result = pcall(test) - table[prop] = orig - if ok then - return result - else - error(result) - end -end - ----@type fun(module: string, prop: string | number, mock: any, test: function): any --- Replace a value in the {module} with a key {prop} by the {mock}, and run the {test}. --- Then unload {module}, when test is completed (despite errors in the {test}). -M.use_mocked_module = function(module, prop, mock, test) - package.loaded[table] = nil - local m = require(module) - m[prop] = mock - local ok, result = pcall(test) - package.loaded[table] = nil - if ok then - return result - else - error(result) - end -end - -return M diff --git a/lua/compline/utils.lua b/lua/compline/utils.lua deleted file mode 100644 index d521442..0000000 --- a/lua/compline/utils.lua +++ /dev/null @@ -1,137 +0,0 @@ -local M = {} - ----@type fun(x: any): boolean ----Checks is an argument {x} is empty. ---- ----@return boolean #true when the argument is empty. ----The argument is empty when: ----* it is the nil; ----* it has a type 'table' and doesn't have any pair; ----* it has a type 'string' and doesn't have any char; ----otherwise result is false. -M.is_empty = function(x) - if x == nil then - return true - end - if type(x) == 'table' and next(x) == nil then - return true - end - if type(x) == 'string' and string.len(x) < 1 then - return true - end - return false -end - ----@type fun(t1: table, t2: table): table ----The same as `vim.extend('keep', t1 or {}, t2 or {})` -M.merge = function(t1, t2) - return vim.tbl_extend('keep', t1 or {}, t2 or {}) -end - -M.sorted_by_keys = function(t, f) - local index = {} - for k in pairs(t) do - table.insert(index, k) - end - table.sort(index, f) - local i = 0 - return function() - i = i + 1 - local k = index[i] - return k, t[k] - end -end - ----@type fun():LspClient ---- ----@return LspClient the first attached to the current buffer lsp client. -M.lsp_client = function() - local clients = vim.lsp.buf_get_clients(0) - if M.is_empty(clients) then - return nil - end - local _, client = next(clients) - return client -end - ----@type fun(icons: table, client?: LspClient): DevIcon ----Takes a type of the file from the {client} and tries to take a corresponding icon ----from the {icons} or 'nvim-web-devicons'. {client} can be omitted. If so, result of ----the `lsp_client()` is used. ---- ----DevIcon example: ----```lua ----{ ---- icon = "", ---- color = "#51a0cf", ---- cterm_color = "74", ---- name = "Lua", ----} ----``` ---- ----@see require('nvim-web-devicons').get_icons ---- ----@param icons table # a table with icons for the lsp clients. ----If no one lsp client is attached, then nil will be returned. ----If an icon for the client is not found, then it's taken from the 'nvim-web-devicons' ----module (if such module exists) or nil will be returned. ---- ----@param client? LspClient the client to the LSP server. If absent, the first attached client to ----the current buffer is used. ---- ----@return DevIcon # icon of the LspClient or `nil` when the `client` is absent or icon not found. -M.lsp_client_icon = function(icons, client) - local c = client or M.lsp_client() - if c == nil then - return nil - end - - -- try to get icons from the 'nvim-web-devicons' module - local ok, dev_icons = pcall(require, 'nvim-web-devicons') - dev_icons = ok and dev_icons.get_icons() - - -- merge both sources with icons - local all_icons = M.merge(icons, dev_icons) - - -- get an appropriated icon - local icon - for _, ft in ipairs(c.config.filetypes) do - if all_icons[ft] then - icon = all_icons[ft] - break - end - end - return icon -end - -M.is_lsp_client_ready = function(client) - -- TODO: add support of the metals - return true -end - ----@type fun(t: table): table ----Replace all string values with 'nil' by the `nil` to remove the pair from the table. -M.remove_nil = function(t) - for k, v in pairs(t) do - if v == 'nil' then - t[k] = nil - elseif type(v) == 'table' then - t[k] = M.remove_nil(v) - end - end - return (not M.is_empty(t)) and t or nil -end - ----@type fun(module_name: string): table ----Lazy import of a module. It doesn't load a module til a first using. ----@return table # a proxy which delegates any invocation of the `__index` to the module with {module_name}. -M.lazy_load = function(module_name) - local module = {} - setmetatable(module, module) - module.__index = function(_, k) - return require(module_name)[k] - end - return module -end - -return M diff --git a/lua/feline-theme/example/colors.lua b/lua/feline-theme/example/colors.lua new file mode 100644 index 0000000..4728b03 --- /dev/null +++ b/lua/feline-theme/example/colors.lua @@ -0,0 +1,34 @@ +local u = require('feline-theme.utils') + +-- Prepare colors for sections: +-- We begin from the default background for Statusline +-- and will make it lighter/darker (depends on vim.go.background) +-- for every next section +local gradient_colors = function() + local function change(color) + if vim.go.background == 'light' then + return u.darkening_color(color, 0.4, 0.3, 0.1) + else + return u.ligthening_color(color, 0.2, 0.2, 0.3) + end + end + local colors = {} + colors.fg = vim.go.background == 'light' and 'White' or 'Black' + colors.bg = u.get_hl_bg('Statusline') + or (vim.go.background == 'light' and '#c8c8cd' or '#505050') + colors.d = change(colors.bg) + colors.c = change(colors.d) + colors.b = change(colors.c) + colors.a = change(colors.b) + + colors.z = colors.a + colors.y = colors.b + colors.x = colors.c + colors.w = colors.d + + return colors +end + +return { + default = gradient_colors, +} diff --git a/lua/feline-theme/example/components.lua b/lua/feline-theme/example/components.lua new file mode 100644 index 0000000..cdbe956 --- /dev/null +++ b/lua/feline-theme/example/components.lua @@ -0,0 +1,37 @@ +local M = {} + +M.vi_mode = { + provider = 'vi_mode', + -- turn icon off and use full name of the mode + icon = '', +} + +M.short_working_directory = { + provider = function() + return vim.fn.pathshorten(vim.fn.fnamemodify(vim.fn.getcwd(), ':p')) + end, +} + +M.file_name = { + provider = function() + return vim.fn.expand('%:t') + end, +} + +M.time = { + provider = function() + return vim.fn.strftime('%H:%M') + end, +} + +M.file_format = { + provider = { + name = 'file_type', + opts = { + filetype_icon = true, + case = 'lowercase', + }, + }, +} + +return M diff --git a/lua/feline-theme/example/init.lua b/lua/feline-theme/example/init.lua new file mode 100644 index 0000000..e5b9bfc --- /dev/null +++ b/lua/feline-theme/example/init.lua @@ -0,0 +1,19 @@ +return { + active = { + left = { + a = { 'vi_mode' }, + b = { 'short_working_directory' }, + c = { 'file_name' }, + }, + middle = { + a = { 'time' }, + }, + right = { + y = { 'file_encoding', 'file_format' }, + z = { 'position' }, + }, + }, + theme = require('feline-theme.example.theme'), + components = require('feline-theme.example.components'), + colors = require('feline-theme.example.colors'), +} diff --git a/lua/feline-theme/example/theme.lua b/lua/feline-theme/example/theme.lua new file mode 100644 index 0000000..7b41907 --- /dev/null +++ b/lua/feline-theme/example/theme.lua @@ -0,0 +1,75 @@ +local vi_mode_bg = function() + return { + bg = require('feline.providers.vi_mode').get_mode_color(), + } +end + +local vi_mode_fg = function(bg) + return function() + return { + fg = require('feline.providers.vi_mode').get_mode_color(), + bg = bg, + } + end +end + +return { + active = { + left = { + separators = { right = ' ' }, + a = { + hl = vi_mode_bg, + separators = { right = { str = '', hl = vi_mode_fg('b') } }, + }, + b = { + hl = { bg = 'b' }, + separators = { right = { str = '', hl = { fg = 'b', bg = 'c' } } }, + }, + c = { + hl = { bg = 'c' }, + separators = { right = { str = '', hl = { fg = 'c', bg = 'd' } } }, + }, + d = { + hl = { bg = 'd' }, + separators = { right = { str = '', hl = { fg = 'd', bg = 'bg' } } }, + }, + }, + right = { + separators = { left = ' ' }, + w = { + hl = { bg = 'w' }, + separators = { left = { str = '', hl = { bg = 'bg' } } }, + }, + x = { + hl = { bg = 'x' }, + separators = { left = { str = '', hl = { bg = 'w' } } }, + }, + y = { + hl = { bg = 'y' }, + separators = { left = { str = '', hl = { bg = 'x' } } }, + }, + z = { + hl = vi_mode_bg, + separators = { left = { str = '', hl = vi_mode_fg('y') } }, + }, + }, + }, + + vi_mode = { + NORMAL = 'green', + OP = 'magenta', + INSERT = 'blue', + VISUAL = 'magenta', + LINES = 'magenta', + BLOCK = 'magenta', + REPLACE = 'violet', + ['V-REPLACE'] = 'pink', + ENTER = 'cyan', + MORE = 'cyan', + SELECT = 'yellow', + COMMAND = 'orange', + SHELL = 'yellow', + TERM = 'orange', + NONE = 'yellow', + }, +} diff --git a/lua/feline-theme/init.lua b/lua/feline-theme/init.lua new file mode 100644 index 0000000..de6bcad --- /dev/null +++ b/lua/feline-theme/init.lua @@ -0,0 +1,198 @@ +local feline = require('feline') +local u = require('feline-theme.utils') + +-- global variable: +FelineTheme = {} +-- private global state: +local __state = {} +setmetatable(FelineTheme, { + __index = __state, + __newindex = function() + error('Attempt to update a read-only table') + end, +}) + +---@param statusline table a full description of the statusline +---@param line_name string active or inactive. +---@param zone_name string left or middle or right. +---@param section_name string the name of the section: a or b or c and etc. +---@param result table container for built components. +local build_section = function(statusline, line_name, zone_name, section_name, result) + local zone = vim.tbl_get(statusline, line_name, zone_name) + local section = zone and zone[section_name] + -- empty section. Skip build + if vim.tbl_isempty(section) then + return + end + + local zone_theme = vim.tbl_get(statusline, 'theme', line_name, zone_name) or {} + local section_theme = zone_theme[section_name] or {} + local section_separators = section_theme.separators or {} + + local first_component = #result + 1 + -- here we're building components stubs, which will be partially overrided later + for _, component_name in ipairs(section) do + local component = { name = component_name, hl = section_theme.hl } + table.insert(result, component) + end + local last_component = #result + -- render section separators + if #result >= first_component then + result[first_component]['left_sep'] = section_separators.left + result[last_component]['right_sep'] = section_separators.right + end +end + +---@param statusline table a full description of the statusline +---@param line_name string active or inactive. +---@param zone_name string left or middle or right. +---@return table[] # resolved components inside zone. +local build_zone = function(statusline, line_name, zone_name) + local result = {} + local zone = vim.tbl_get(statusline, line_name, zone_name) + local zone_theme = vim.tbl_get(statusline, 'theme', line_name, zone_name) or {} + local zone_separators = zone_theme.separators or {} + + -- adds `always_visible` property to the separator + local function always_visible(sep) + if type(sep) == 'table' then + sep.always_visible = true + return sep + else + return { str = sep, always_visible = true } + end + end + + -- build component stubs for every section + for section_name in u.sorted_by_keys(zone) do + build_section(statusline, line_name, zone_name, section_name, result) + end + + -- add left zone separator + if #result > 0 and zone_separators.left then + result[1].left_sep = always_visible(zone_separators.left) + end + + -- add right zone separator + if #result > 0 and zone_separators.right then + result[#result].right_sep = always_visible(zone_separators.right) + end + + -- finally resolve components + local components = statusline.components or {} + for i, stub in ipairs(result) do + local component = components[stub.name] + if component then + result[i] = vim.tbl_extend('force', stub, component) + else + stub.provider = stub.name + end + end + + return result +end + +---@param statusline table a full description of the statusline. +---@param line_name string active or inactive. +---@return table[] # description of the statusline in term of feline. +local build_line = function(statusline, line_name) + local result = {} + for i, zone_name in ipairs({ 'left', 'middle', 'right' }) do + if statusline[line_name][zone_name] then + result[i] = build_zone(statusline, line_name, zone_name) + else + result[i] = {} + end + end + return result +end + +local Statusline = { + -- user should be able to not specify components in some case at all + active = nil, + inactive = nil, + components = {}, + theme = {}, + colors = {}, +} + +function Statusline:validate() + local statusline_schema = require('feline-theme.schema.statusline').statusline + local ok, schema = pcall(require, 'lua-schema') + if ok then + local ok, err = schema.validate(self, statusline_schema) + assert(ok, tostring(err)) + else + error('To validate statusline schema module "dokwork/lua-schema.nvim" should be installed.') + end +end + +function Statusline:build_components() + local result = {} + for _, line_name in ipairs({ 'active', 'inactive' }) do + if self[line_name] then + result[line_name] = build_line(self, line_name) + end + end + return result +end + +function Statusline:show_components() + vim.pretty_print(self:build_components()) +end + +function Statusline:actual_colors() + local colors = self.colors or {} + colors = colors[vim.g.colors_name] or colors[vim.go.background] or colors['default'] + if type(colors) == 'function' then + return colors() + else + return colors + end +end + +function Statusline:show_actual_colors() + vim.pretty_print(self:actual_colors()) +end + +function Statusline:refresh_colors() + local colors = self:actual_colors() + if colors then + feline.use_theme(colors) + feline.reset_highlights() + end + return colors +end + +function Statusline:show_all() + vim.pretty_print(self) +end + +return { + setup_statusline = function(statusline) + setmetatable(statusline, { + __index = Statusline, + }) + + local config = {} + config.components = statusline:build_components() + config.theme = statusline:actual_colors() + config.vi_mode_colors = statusline.theme and statusline.theme.vi_mode + + feline.setup(config) + + -- change the theme on every changes colorscheme or background + local group = vim.api.nvim_create_augroup('feline_select_theme', { clear = true }) + vim.api.nvim_create_autocmd('ColorScheme', { + pattern = '*', + group = group, + callback = function() + statusline:refresh_colors() + end, + }) + + __state.statusline = statusline + + return statusline + end, +} diff --git a/lua/feline-theme/schema/colors.lua b/lua/feline-theme/schema/colors.lua new file mode 100644 index 0000000..17c017b --- /dev/null +++ b/lua/feline-theme/schema/colors.lua @@ -0,0 +1,23 @@ +local feline = require('feline-theme.schema.feline') + +local M = {} + +M.palette = { oneof = { 'function', { table = { key = 'string', value = feline.color } } } } + +M.colors = { + table = { + -- the palette can have a key with a name of the colorscheme for which this palette + -- should be used + { key = 'string', value = M.palette }, + -- will be used in case of `dark` background, if palette with the same name as the + -- current colorscheme was not found + { key = 'light', value = M.palette }, + -- will be used in case of `light` background, if palette with the same name as the + -- current colorscheme was not found + { key = 'dark', value = M.palette }, + -- palette with colors which will be used when no other option will be appropriate + { key = 'default', value = M.palette, required = true }, + }, +} + +return M diff --git a/lua/feline-theme/schema/feline.lua b/lua/feline-theme/schema/feline.lua new file mode 100644 index 0000000..d2c6bb8 --- /dev/null +++ b/lua/feline-theme/schema/feline.lua @@ -0,0 +1,54 @@ +local M = {} + +-- #RRBBGG +M.color = 'string' + +M.highlight = { + oneof = { + 'string', + 'function', + { + table = { + { key = 'fg', value = M.color }, + { key = 'bg', value = M.color }, + }, + }, + }, +} + +M.provider = { + oneof = { + 'string', + 'function', + { + table = { + { key = 'name', value = 'string' }, + { key = 'opts', value = { table = { key = 'string', value = 'any' } } }, + }, + }, + }, +} + +M.separator = { + oneof = { + 'string', + 'function', + { + table = { + { key = 'str', value = 'string' }, + { key = 'hl', value = M.highlight }, + { key = 'always_visible', value = 'boolean' }, + }, + }, + }, +} + +M.component = { + table = { + { key = 'provider', value = M.provider }, + { key = 'hl', value = M.highlight }, + { key = { oneof = { 'left_sep', 'right_sep' } }, value = M.separator }, + }, +} + +return M diff --git a/lua/feline-theme/schema/statusline.lua b/lua/feline-theme/schema/statusline.lua new file mode 100644 index 0000000..f28632c --- /dev/null +++ b/lua/feline-theme/schema/statusline.lua @@ -0,0 +1,38 @@ +local feline = require('feline-theme.schema.feline') + +local M = {} + +M.component = 'string' + +M.section = { list = M.component } + +M.zone = { + table = { + -- usually chars are used as name of the section + key = 'string', + value = M.section, + }, +} + +M.line = { + table = { + { key = 'left', value = M.zone }, + { key = 'middle', value = M.zone }, + { key = 'right', value = M.zone }, + }, +} + +M.statusline = { + table = { + { key = 'active', value = M.line }, + { key = 'inactive', value = M.line }, + { key = 'theme', value = require('feline-theme.schema.theme').theme }, + { key = 'colors', value = require('feline-theme.schema.colors').colors }, + { + key = 'components', + value = { table = { key = 'string', value = feline.component } }, + }, + }, +} + +return M diff --git a/lua/feline-theme/schema/theme.lua b/lua/feline-theme/schema/theme.lua new file mode 100644 index 0000000..85cd4fe --- /dev/null +++ b/lua/feline-theme/schema/theme.lua @@ -0,0 +1,68 @@ +local feline = require('feline-theme.schema.feline') + +local M = {} + +M.separator = feline.separator + +M.separators = { + table = { + { key = 'left', value = M.separator }, + { key = 'right', value = M.separator }, + }, +} + +M.section = { + table = { + { key = 'hl', value = feline.highlight }, + { key = 'separators', value = M.separators }, + }, +} + +M.zone = { + table = { + { key = 'separators', value = M.separators }, + { key = 'string', value = M.section }, + }, +} + +M.line = { + table = { + { key = 'left', value = M.zone }, + { key = 'middle', value = M.zone }, + { key = 'right', value = M.zone }, + }, +} + +M.vi_mode = { + table = { + key = { + oneof = { + 'NORMAL', + 'OP', + 'INSERT', + 'VISUAL', + 'LINES', + 'BLOCK', + 'REPLACE', + 'V-REPLACE', + 'ENTER', + 'MORE', + 'SELECT', + 'COMMAND', + 'SHELL', + 'TERM', + 'NONE', + }, + }, + value = feline.color, + }, +} + +M.theme = { + table = { + { key = { oneof = { 'active', 'inactive' } }, value = M.line }, + { key = 'vi_mode', value = M.vi_mode }, + }, +} + +return M diff --git a/lua/feline-theme/utils.lua b/lua/feline-theme/utils.lua new file mode 100644 index 0000000..41fd0cd --- /dev/null +++ b/lua/feline-theme/utils.lua @@ -0,0 +1,126 @@ +local M = {} + +---@type fun(x: any): boolean +---Checks is an argument {x} is empty. +--- +---@return boolean #true when the argument is empty. +---The argument is empty when: +---* it is the nil; +---* it has a type 'table' and doesn't have any pair; +---* it has a type 'string' and doesn't have any char; +---otherwise result is false. +M.is_empty = function(x) + if x == nil then + return true + end + if type(x) == 'table' and next(x) == nil then + return true + end + if type(x) == 'string' and string.len(x) < 1 then + return true + end + return false +end + +---@type fun(t1: table, t2: table): table +---The same as `vim.extend('keep', t1 or {}, t2 or {})` +M.merge = function(t1, t2) + return vim.tbl_extend('keep', t1 or {}, t2 or {}) +end + +M.sorted_by_keys = function(t, f) + local index = {} + for k in pairs(t) do + table.insert(index, k) + end + table.sort(index, f) + local i = 0 + return function() + i = i + 1 + local k = index[i] + return k, t[k] + end +end + +---Lazy import of a module. It doesn't load a module til a first using. +---@return table # a proxy which delegates any invocation of the `__index` to the module with {module_name}. +M.lazy_load = function(module_name) + local module = {} + setmetatable(module, module) + module.__index = function(_, k) + return require(module_name)[k] + end + return module +end + +M.get_hl_attr = function(hl, what) + local result = vim.fn.synIDattr(vim.fn.synIDtrans(vim.fn.hlID(hl)), what) + if result == '' then + return nil + else + return result + end +end + +M.get_hl_fg = function(hl) + return M.get_hl_attr(hl, 'fg#') +end + +M.get_hl_bg = function(hl) + return M.get_hl_attr(hl, 'bg#') +end + +---@type fun(color: string): number, number, number +--- Parses a color in the format '#RRGGBB', where +--- RR is a number for red part in the hex format, +--- GG is a number for green part in the hex format, +--- BB is a number for blue part in the hex format. +--- For example: '#000000' for 'black' color, or '#AAAAAA' for white color. +--- In case of wrong format of the color the error will be thrown. +M.parse_rgb_color = function(color) + if type(color) ~= 'string' then + error(string.format('Illegal color type %s. It must be string.', type(color))) + end + local _, _, r, g, b = string.find(color, '#(%x%x)(%x%x)(%x%x)') + r = tonumber(r, 16) + g = tonumber(g, 16) + b = tonumber(b, 16) + if r and g and b then + return r, g, b + else + error( + string.format( + 'Illegal color: %s. A color must follow format: #(%x%x)(%x%x)(%x%x)', + color + ) + ) + end +end + +M.create_color = function(r, g, b) + return string.format('#%02x%02x%02x', r, g, b) +end + +M.darkening_color = function(color, rf, gf, bf) + local rf = rf or 0.1 + local gf = gf or 0.1 + local bf = bf or 0.1 + local r, g, b = M.parse_rgb_color(color) + r = r * (1 - rf) + g = g * (1 - gf) + b = b * (1 - bf) + return M.create_color(r, g, b) +end + +M.ligthening_color = function(color, rf, gf, bf) + local rf = rf or 0.1 + local gf = gf or 0.1 + local bf = bf or 0.1 + local r, g, b = M.parse_rgb_color(color) + r = r + (255 - r) * rf + g = g + (255 - g) * gf + b = b + (255 - b) * bf + return M.create_color(r, g, b) +end + +return M diff --git a/test/cosmosline_spec.lua b/test/cosmosline_spec.lua deleted file mode 100644 index 3b1e543..0000000 --- a/test/cosmosline_spec.lua +++ /dev/null @@ -1,9 +0,0 @@ -describe('validating', function() - it('should be passed', function() - -- given: - local cosmosline = require('compline.cosmosline') - - -- when: - cosmosline:validate() - end) -end) diff --git a/test/init.lua b/test/init.lua index 32fb963..56d5c97 100644 --- a/test/init.lua +++ b/test/init.lua @@ -24,11 +24,11 @@ vim.cmd([[packadd packer.nvim]]) require('packer').startup(function(use) use('wbthomason/packer.nvim') use({ - 'compline', + 'feline-theme', requires = { 'kyazdani42/nvim-web-devicons', 'famiu/feline.nvim', - 'tpope/vim-fugitive', + 'dokwork/lua-schema.nvim', }, }) end) @@ -39,16 +39,5 @@ if packer_bootstrap then else -- Configuration for test: - if vim.env.COSMOSLINE then - ---@diagnostic disable-next-line: unused-local - local config = require('compline.cosmosline') - -- :new('my_line', { - -- active = { left = { a = { [1] = 'test' } } }, - -- }) - -- vim.pretty_print(config:build_components()) - config:setup() - -- vim.pretty_print(config) - else - require('compline.example'):setup() - end + require('feline-theme').setup_statusline(require('feline-theme.example')) end diff --git a/test/schema_spec.lua b/test/schema_spec.lua index 5acc9f1..7742d2c 100644 --- a/test/schema_spec.lua +++ b/test/schema_spec.lua @@ -1,317 +1,120 @@ -local s = require('compline.schema') - -describe('validation the schema', function() - it('should be passed for every type', function() - assert(s.validate(s.const, s.type()), 'Wrong schema for the const') - assert(s.validate(s.list(), s.type()), 'Wrong schema for the list') - assert(s.validate(s.oneof(), s.type()), 'Wrong schema for the oneof') - assert(s.validate(s.table(), s.type()), 'Wrong schema for the table') - assert(s.validate(s.type(), s.type()), 'Wrong schema for the type') +local s = require('lua-schema') +local c = require('feline-theme.schema.colors') +local t = require('feline-theme.schema.theme') +local st = require('feline-theme.schema.statusline') +local feline = require('feline-theme.schema.feline') + +describe('feline schema validation', function() + it('should be passed for every object', function() + -- when: + for name, schema in pairs(feline) do + local ok, err = s.validate(schema, s.type) + + -- then: + assert( + ok, + string.format( + 'Error on validation schema of the %s.\nReason:\n%s\nSchema:\n%s', + name, + err, + vim.inspect(schema) + ) + ) + end end) - it('should be failed for invalide schema', function() - assert(not s.validate({ oNNeof = { 1, 2, 3 } }, s.type())) - assert(not s.validate({ lst = 'string' }, s.type())) - assert(not s.validate({ tbl = { key = true, value = true } }, s.type())) - assert(not s.validate({ table = { ky = true, value = true } }, s.type())) - assert(not s.validate({ table = { key = true, val = true } }, s.type())) - assert(not s.validate({ table = { key = true } }, s.type())) - assert(not s.validate({ cnst = 123 }, s.type())) + it('should be passed for highlight', function() + assert(s.validate('red', feline.highlight)) + assert(s.validate({ fg = 'red', bg = 'green' }, feline.highlight)) + -- example with a function + assert(s.validate(it, feline.highlight)) end) +end) - describe('of the constants', function() - it('should be passed for both syntax of constants', function() - -- when: - assert(s.validate('123', s.const)) - assert(s.validate({ const = '123' }, s.const)) - end) - - it('should be passed for particular value by the short schema', function() - -- given: - local schema = '123' - - -- then: - assert(s.validate('123', schema)) - assert(not s.validate('12', schema)) - assert(not s.validate(123, schema)) - end) - - it('should be passed for particular value by the full schema', function() - -- given: - local schema = { const = '123' } - - -- then: - assert(s.validate('123', schema)) - assert(not s.validate('12', schema)) - assert(not s.validate(123, schema)) - end) +describe('colors schema validation', function() + it('should be passed for every object', function() + -- when: + for name, schema in pairs(c) do + local ok, err = s.validate(schema, s.type) + + -- then: + assert( + ok, + string.format( + 'Error on validation schema of the %s.\nReason:\n%s\nSchema:\n%s', + name, + err, + vim.inspect(schema) + ) + ) + end end) +end) - describe('of the oneof', function() - it('should be passed for every option', function() - -- given: - local schema = { - oneof = { '123', 456, 'function', true, { table = { key = 'a', value = 1 } } }, - } - - -- then: - assert(s.validate('123', schema)) - assert(s.validate(456, schema)) - assert(s.validate(true, schema)) - assert(s.validate({ a = 1 }, schema)) - assert(s.validate(it, schema)) - end) - - it('should be failed when value is not in the list, or has wwrong type', function() - -- given: - local schema = { oneof = { '123', 456, true, { table = { key = 'a', value = 1 } } } } - - -- then: - assert(not s.validate('!123', schema)) - assert(not s.validate('true', schema)) - end) +describe('theme schema validation', function() + it('should be passed for every object', function() + -- when: + for name, schema in pairs(t) do + local ok, err = s.validate(schema, s.type) + + -- then: + assert( + ok, + string.format( + 'Error on validation schema of the %s.\nReason:\n%s\nSchema:\n%s', + name, + err, + vim.inspect(schema) + ) + ) + end end) +end) - describe('of the list', function() - it('should be passed for list with elements with valid type', function() - -- given: - local schema = { list = 'number' } - - -- when: - local ok, err = s.validate({ 1, 2, 3 }, schema) - - -- then: - assert(ok, tostring(err)) - end) - - it('should be passed for empty list', function() - -- given: - local schema = { list = 'number' } - - -- then: - assert(s.validate({}, schema)) - end) - - it('should not be passed for empty list', function() - -- given: - local schema = { list = 'number', non_empty = true } - - -- when: - local ok, err = s.validate({}, schema) - - -- then: - assert(not ok) - assert.are.same({}, err.object) - assert.are.same(schema, err.schema) - end) - - it('should be failed for list with element with wrong type', function() - -- given: - local schema = { list = 'number' } - - -- when: - local ok, err = s.validate({ 1, 2, '3' }, schema) - - -- then: - assert(not ok) - assert.are.same({ 1, 2, '3' }, err.object) - assert.are.same(schema, err.schema) - end) +describe('statusline schema validation', function() + it('should be passed for every object', function() + -- when: + for name, schema in pairs(st) do + local ok, err = s.validate(schema, s.type) + + -- then: + assert( + ok, + string.format( + 'Error on validation schema of the %s.\nReason:\n%s\nSchema:\n%s', + name, + err, + vim.inspect(schema) + ) + ) + end end) - describe('of the table', function() - it('should be passed for a valid table', function() - -- given: - local schema = { table = { key = 'string', value = 'string' } } - - -- when: - local ok, err = s.validate({ a = 'b' }, schema) - - -- then: - assert(ok, tostring(err)) - end) - - it('should validate type of keys', function() - -- given: - local schema = { table = { key = 'string', value = 'number' } } - - -- then: - assert(not s.validate({ 1, 2 }, schema)) - end) + it('should be passed for the zone', function() + -- given: + local zone = { + a = { 'str' }, + } - it('should validate type of values', function() - -- given: - local schema = { table = { key = 'string', value = 'number' } } + -- when + local ok, err = s.validate(zone, st.zone) - -- when: - local ok, err = s.validate({ a = 'str' }, schema) - - -- then: - assert(not ok) - assert.are.same({ a = 'str' }, err.object) - assert.are.same({ table = { { key = 'string', value = 'number' } } }, err.schema) - end) - - it('should support oneof as a type of keys', function() - -- given: - local schema = { table = { key = { oneof = { 'a', 'b' } }, value = 'string' } } - - -- then: - assert(s.validate({ a = 'a' }, schema)) - assert(s.validate({ b = 'b' }, schema)) - assert(not s.validate({ c = 'c' }, schema)) - end) - - it('should check other key options if oneof failed', function() - -- given: - local schema = { - table = { - { key = { oneof = { 'a', 'b' } }, value = 'string' }, - { key = 'string', value = 'boolean' }, - }, - } - - -- when: - local ok, err = s.validate({ c = true }, schema) - - -- then: - assert(ok, tostring(err)) - end) - - it('should not be passed when required oneof was not satisfied', function() - -- given: - local schema = { - table = { - { key = { oneof = { 'a', 'b' } }, value = 'string', required = true }, - { key = 'string', value = 'boolean' }, - }, - } - - -- when: - local ok, err = s.validate({ c = true }, schema) - - -- then: - assert(not ok) - - assert.are.same({ c = '?' }, err.object) - assert.are.same({ - table = { { key = { oneof = { 'a', 'b' } } } }, - }, err.schema) - end) - - it('should support oneof as a type of values', function() - -- given: - local schema = { table = { key = 'string', value = { oneof = { 'a', 'b' } } } } - - -- then: - assert(s.validate({ a = 'a' }, schema)) - assert(s.validate({ a = 'b' }, schema)) - assert(not s.validate({ a = 'c' }, schema)) - end) - - it('should support const as a type of keys', function() - -- given: - local schema = { - table = { - { key = 'a', value = 'number' }, - { key = 'b', value = 'boolean' }, - }, - } - - -- when: - local ok, err = s.validate({ a = 1, b = true }, schema) - - -- then: - assert(ok, tostring(err)) - assert(not s.validate({ a = 'str', b = true }, schema)) - assert(not s.validate({ a = 1, b = 1 }, schema)) - end) - - it('should be passed for missed optional keys', function() - -- given: - local schema = { - table = { - { key = 'a', value = 'number' }, - { key = 'b', value = 'boolean', required = true }, - }, - } - - -- then: - assert(s.validate({ b = true }, schema)) - end) - - it('should be failed for missed required keys', function() - -- given: - local schema = { - table = { - { key = 'a', value = 'number' }, - { key = 'b', value = 'boolean', required = true }, - }, - } - - -- then: - assert(not s.validate({ a = 1 }, schema)) - end) - - it('should support mix of const and other types', function() - -- given: - local schema = { - table = { - { key = 'a', value = 'number' }, - { key = 'string', value = 'boolean' }, - }, - } - - -- when: - local ok, err = s.validate({ a = 1, str = true }, schema) - - -- then: - assert(ok, tostring(err)) - end) - - it('should support table as a value', function() - -- given: - local schema = { - table = { - { - key = 'a', - value = { - table = { key = 'b', value = 'number' }, - }, - }, - }, - } - - -- when: - local ok, err = s.validate({ a = { b = 1 } }, schema) - - -- then: - assert(ok, tostring(err)) - end) + -- then: + assert(ok, tostring(err)) + end) - it('should correctly compose error for nested tables', function() - -- given: - local schema = { - table = { - { - key = 'a', - value = { - table = { key = 'b', value = 'number' }, - }, - }, - }, - } + it('should be passed for the line', function() + -- given: + local line = { + left = { + a = { 'str' }, + }, + } - -- when: - local ok, err = s.validate({ a = { c = 1 } }, schema) + -- when + local ok, err = s.validate(line, st.line) - -- then: - assert(not ok) - assert.are.same({ a = { c = '?' } }, err.object) - assert.are.same( - { table = { { key = 'a', value = { table = { { key = 'b' } } } } } }, - err.schema - ) - end) + -- then: + assert(ok, tostring(err)) end) end) diff --git a/test/statusline_schema_spec.lua b/test/statusline_schema_spec.lua deleted file mode 100644 index 4f7ec0b..0000000 --- a/test/statusline_schema_spec.lua +++ /dev/null @@ -1,109 +0,0 @@ -local s = require('compline.schema') -local ss = require('compline.schema.statusline') -local st = require('compline.schema.theme') -local sf = require('compline.schema.feline') - -describe('feline schema validation', function() - it('should be passed for highlight', function() - assert(s.validate('red', sf.highlight)) - assert(s.validate({ fg = 'red', bg = 'green' }, sf.highlight)) - -- example with a function - assert(s.validate(it, sf.highlight)) - end) -end) - -describe('theme schema validation', function() - it('should be passed for colors', function() - -- when: - local ok, err = s.validate(st.colors, s.type) - - -- then: - assert(ok, tostring(err)) - end) - - it('should be passed for vi_mode', function() - -- when: - local ok, err = s.validate(st.vi_mode, s.type) - - -- then: - assert(ok, tostring(err)) - end) - - it('should be passed for separator', function() - -- when: - local ok, err = s.validate(st.separator, s.type) - - -- then: - assert(ok, tostring(err)) - end) - - it('should be passed for sections', function() - -- when: - local ok, err = s.validate(st.sections, s.type) - - -- then: - assert(ok, tostring(err)) - end) - - it('should be passed for zone', function() - -- when: - local ok, err = s.validate(st.zone, s.type) - - -- then: - assert(ok, tostring(err)) - end) - - it('should be passed for line', function() - -- when: - local ok, err = s.validate(st.line, s.type) - - -- then: - assert(ok, tostring(err)) - end) - - it('should be passed for whole theme', function() - -- when: - local ok, err = s.validate(st.theme, s.type) - - -- then: - assert(ok, tostring(err)) - end) -end) - -describe('statusline schema validation', function() - it('should be passed for the zone', function() - -- given: - local zone = { - a = { 'str' }, - } - - -- when - local ok, err = s.validate(zone, ss.zone) - - -- then: - assert(ok, tostring(err)) - end) - - it('should be passed for the line', function() - -- given: - local line = { - left = { - a = { 'str' }, - }, - } - - -- when - local ok, err = s.validate(line, ss.line) - - -- then: - assert(ok, tostring(err)) - end) - - it('should be passed for whole statusline', function() - -- when: - local ok, err = s.validate(ss.statusline, s.type) - - -- then: - assert(ok, tostring(err)) - end) -end) diff --git a/test/statusline_spec.lua b/test/statusline_spec.lua index 5c6e2e5..2bfaf33 100644 --- a/test/statusline_spec.lua +++ b/test/statusline_spec.lua @@ -1,14 +1,53 @@ -local Statusline = require('compline.statusline') +local FelineTheme = require('feline-theme') describe('Building componentns', function() + it('should resolve components by their names', function() + -- given: + local components = { + some_component = { provider = 'example', hl = 'ComponentHighlight' }, + } + local statusline = FelineTheme.setup_statusline({ + active = { + left = { + a = { 'some_component' }, + }, + }, + components = components, + }) + + -- when: + local result = statusline:build_components() + + -- then: + local expected = { + active = { + { + { name = 'some_component', provider = 'example', hl = 'ComponentHighlight' }, + }, + {}, + {}, + }, + } + local msg = string.format( + '\nExpected:\n%s\nActual:\n%s', + vim.inspect(expected), + vim.inspect(result) + ) + assert.are.same(expected, result, msg) + end) + it('should build components and sections in correct order', function() -- given: - local statusline = Statusline:new('test', { + local statusline = FelineTheme.setup_statusline({ active = { left = { b = { 'b', 'c' }, a = { 'a' }, }, + right = { + z = { 'z' }, + w = { 'w' }, + }, }, }) @@ -19,12 +58,15 @@ describe('Building componentns', function() local expected = { active = { { - { provider = 'a' }, - { provider = 'b' }, - { provider = 'c' }, + { name = 'a', provider = 'a' }, + { name = 'b', provider = 'b' }, + { name = 'c', provider = 'c' }, }, {}, - {}, + { + { name = 'w', provider = 'w' }, + { name = 'z', provider = 'z' }, + }, }, } local msg = string.format( @@ -34,19 +76,18 @@ describe('Building componentns', function() ) assert.are.same(expected, result, msg) end) +end) - it('should resolve components by their names', function() +describe('Resolving highlights', function() + it('should use default hl if nothing specified in the theme', function() -- given: - local components = { - some_component = { provider = 'example' }, - } - local statusline = Statusline:new('test', { + local statusline = FelineTheme.setup_statusline({ active = { left = { a = { 'some_component' }, }, }, - components = components, + theme = {}, }) -- when: @@ -56,7 +97,10 @@ describe('Building componentns', function() local expected = { active = { { - { provider = 'example' }, + { + name = 'some_component', + provider = 'some_component', + }, }, {}, {}, @@ -72,28 +116,24 @@ describe('Building componentns', function() it('should add hl to components from the theme', function() -- given: - local components = { - some_component = { provider = 'example' }, - } + local components = {} local theme = { active = { left = { - sections = { - a = { hl = { fg = 'black', bg = 'whignoree' } }, - }, + a = { hl = 'CustomHighlight' }, + b = { hl = { bg = 'white' } }, }, }, } - local statusline = Statusline:new('test', { + local statusline = FelineTheme.setup_statusline({ active = { left = { - a = { 'some_component' }, + a = { 'some_component_1' }, + b = { 'some_component_2' }, }, }, components = components, - themes = { - default = theme, - }, + theme = theme, }) -- when: @@ -103,7 +143,16 @@ describe('Building componentns', function() local expected = { active = { { - { provider = 'example', hl = { fg = 'black', bg = 'whignoree' } }, + { + name = 'some_component_1', + provider = 'some_component_1', + hl = 'CustomHighlight', + }, + { + name = 'some_component_2', + provider = 'some_component_2', + hl = { bg = 'white' }, + }, }, {}, {}, @@ -116,31 +165,26 @@ describe('Building componentns', function() ) assert.are.same(expected, result, msg) end) +end) - it("should use hl from a component when ignore's specified", function() +describe('Resolving separators', function() + it("should add always visible zone's separators for the first and last components", function() -- given: - local components = { - some_component = { provider = 'example', hl = { fg = 'red' } }, - } local theme = { active = { left = { - sections = { - a = { hl = { fg = 'black', bg = 'whignoree' } }, - }, + separators = { left = '<', right = { str = '>', hl = { fg = 'red' } } }, }, }, } - local statusline = Statusline:new('test', { + local statusline = FelineTheme.setup_statusline({ active = { left = { - a = { 'some_component' }, + a = { 'first' }, + b = { 'other', 'last' }, }, }, - components = components, - themes = { - default = theme, - }, + theme = theme, }) -- when: @@ -150,7 +194,22 @@ describe('Building componentns', function() local expected = { active = { { - { provider = 'example', hl = { fg = 'red' } }, + { + name = 'first', + provider = 'first', + + left_sep = { str = '<', always_visible = true }, + }, + { + name = 'other', + provider = 'other', + }, + { + name = 'last', + provider = 'last', + + right_sep = { str = '>', hl = { fg = 'red' }, always_visible = true }, + }, }, {}, {}, @@ -164,20 +223,24 @@ describe('Building componentns', function() assert.are.same(expected, result, msg) end) - it("should create components for zone's separators", function() + it("should add section's separators to the first and last components", function() -- given: local theme = { active = { left = { - zone_separators = { left = '<', right = { '>', hl = 'red' } }, + a = { + separators = { left = '<', right = '>' }, + }, }, }, } - local statusline = Statusline:new('test', { - active = { left = { a = { 'test' } } }, - themes = { - default = theme, + local statusline = FelineTheme.setup_statusline({ + active = { + left = { + a = { 'first', 'test', 'last' }, + }, }, + theme = theme, }) -- when: @@ -187,9 +250,20 @@ describe('Building componentns', function() local expected = { active = { { - { provider = '<' }, - { provider = 'test' }, - { provider = '>', hl = 'red' }, + { + name = 'first', + provider = 'first', + left_sep = '<', + }, + { + name = 'test', + provider = 'test', + }, + { + name = 'last', + provider = 'last', + right_sep = '>', + }, }, {}, {}, @@ -203,26 +277,25 @@ describe('Building componentns', function() assert.are.same(expected, result, msg) end) - it("should add section's separators to the outside components", function() + it("zone's separators must override sections separators", function() -- given: local theme = { active = { left = { - sections = { - a = { ls = '<', rs = { '>', hl = 'green' } }, - }, + separators = { right = '>' }, + a = { separators = { left = '[' } }, + b = { separators = { right = ']' } }, }, }, } - local statusline = Statusline:new('test', { - themes = { - default = theme, - }, + local statusline = FelineTheme.setup_statusline({ active = { left = { - a = { 'test' }, + a = { 'test 1' }, + b = { 'test 2' }, }, }, + theme = theme, }) -- when: @@ -233,9 +306,14 @@ describe('Building componentns', function() active = { { { - provider = 'test', - left_sep = { str = '<' }, - right_sep = { str = '>', hl = 'green' }, + name = 'test 1', + provider = 'test 1', + left_sep = '[', + }, + { + name = 'test 2', + provider = 'test 2', + right_sep = { str = '>', always_visible = true }, }, }, {}, @@ -249,38 +327,38 @@ describe('Building componentns', function() ) assert.are.same(expected, result, msg) end) -end) - -describe('Extending an existing statusline', function() - local existed_statusline = Statusline:new('existed', { - active = { - left = { - a = { 'component 1', 'component 2' }, - b = { 'component 3' }, - }, - }, - }) - it('should override the section', function() + it('should use separators from the components', function() -- given: - local new_statusline = existed_statusline:new('new', { + local components = { + test = { + provider = 'test', + left_sep = '<', + right_sep = { str = '>' }, + }, + } + local statusline = FelineTheme.setup_statusline({ active = { left = { - a = { 'component 1', 'new' }, + a = { 'test' }, }, }, + components = components, }) -- when: - local result = new_statusline:build_components() + local result = statusline:build_components() -- then: local expected = { active = { { - { provider = 'component 1' }, - { provider = 'new' }, - { provider = 'component 3' }, + { + name = 'test', + provider = 'test', + left_sep = '<', + right_sep = { str = '>' }, + }, }, {}, {}, @@ -294,24 +372,46 @@ describe('Extending an existing statusline', function() assert.are.same(expected, result, msg) end) - it('should remove the section', function() + it("components's separators must override section's separators", function() -- given: - local new_statusline = existed_statusline:new('new', { + local theme = { + active = { + left = { + a = { separators = { left = '[', right = ']' } }, + }, + }, + } + local components = { + test = { + provider = 'test', + left_sep = '<', + right_sep = '>', + }, + } + local statusline = FelineTheme.setup_statusline({ active = { left = { - a = 'nil', - b = 'nil', + a = { 'test' }, }, }, + theme = theme, + components = components, }) -- when: - local result = new_statusline:build_components() + local result = statusline:build_components() -- then: local expected = { active = { - {}, + { + { + name = 'test', + provider = 'test', + left_sep = '<', + right_sep = '>', + }, + }, {}, {}, }, @@ -324,28 +424,46 @@ describe('Extending an existing statusline', function() assert.are.same(expected, result, msg) end) - it('should remove active components and add inactive', function() + it("components's separators must override zone's separators", function() -- given: - local new_statusline = existed_statusline:new('new', { - active = 'nil', - inactive = { - right = { - a = { 'new' }, + local theme = { + active = { + separators = { left = '[', right = ']' }, + }, + } + local components = { + test = { + provider = 'test', + left_sep = '<', + right_sep = '>', + }, + } + local statusline = FelineTheme.setup_statusline({ + active = { + left = { + a = { 'test' }, }, }, + theme = theme, + components = components, }) -- when: - local result = new_statusline:build_components() + local result = statusline:build_components() -- then: local expected = { - inactive = { - {}, - {}, + active = { { - { provider = 'new' }, + { + name = 'test', + provider = 'test', + left_sep = '<', + right_sep = '>', + }, }, + {}, + {}, }, } local msg = string.format( diff --git a/test/try.sh b/test/try.sh index 4a06f39..ca6d2da 100755 --- a/test/try.sh +++ b/test/try.sh @@ -6,8 +6,8 @@ # got to the directory with this script (./test/): cd $(dirname ${BASH_SOURCE[0]}) -export XDG_CONFIG_HOME='/tmp/compline.nvim/conf' -export XDG_DATA_HOME='/tmp/compline.nvim/data' +export XDG_CONFIG_HOME='/tmp/feline-theme/conf' +export XDG_DATA_HOME='/tmp/feline-theme/data' ARG=$1 @@ -19,12 +19,6 @@ if [ "$ARG" == "--reset" ]; then fi -if [ "$ARG" == "--cosmosline" ]; then - - ARG='' - export COSMOSLINE="1" -fi - mkdir -p $XDG_CONFIG_HOME mkdir -p $XDG_DATA_HOME nvim -u init.lua --cmd 'set rtp='$XDG_DATA_HOME',$VIMRUNTIME,'$XDG_CONFIG_HOME $ARG diff --git a/test/utils_spec.lua b/test/utils_spec.lua index 1c83655..bb5f4a3 100644 --- a/test/utils_spec.lua +++ b/test/utils_spec.lua @@ -1,5 +1,4 @@ -local test = require('compline.test') -local u = require('compline.utils') +local u = require('feline-theme.utils') describe('is_empty', function() it('should return true for nil', function() @@ -29,65 +28,53 @@ describe('iterate with sorted keys', function() end) end) -describe('lsp_client', function() - it('should return the first attached to the current buffer client', function() - -- given: - local clients = { { name = 'first' }, { name = 'second' } } - local mock = function() - return clients - end - test.use_mocked_table(vim.lsp, 'buf_get_clients', mock, function() - -- when: - local result = u.lsp_client() +describe('colors utilities', function() + describe('create a color', function() + it('should create string with color', function() + -- given: + local r, g, b = 0, 0, 0 -- then: - assert.are.same({ name = 'first' }, result) + assert.are.equal('#000000', u.create_color(r, g, b)) end) end) -end) -describe('lsp_client_icon', function() - it('should find the icon for the client by the filetype', function() - -- given: - local client = { config = { filetypes = { 'other_type', 'test' } } } - local icon = { name = 'test', icon = '!' } + describe('parse RGB color', function() + it('should return numbers for every part of the color', function() + -- given: + local color = '#11AA4B' - -- when: - local result = u.lsp_client_icon({ test = icon }, client) + -- when: + local r, g, b = u.parse_rgb_color(color) - -- then: - assert.are.same(icon, result) + -- then: + assert.are.equal(17, r) + assert.are.equal(170, g) + assert.are.equal(75, b) + end) end) - it('should find the icon for the first attached client', function() - -- given: - local client = { config = { filetypes = { 'other_type', 'test' } } } - local icon = { name = 'test', icon = '!' } - local mock = function() - return { client } - end - test.use_mocked_table(vim.lsp, 'buf_get_clients', mock, function() + describe('changing a brightness of a color', function() + it('should make a color lighter', function() + -- given: + local color = '#9e6f11' + -- when: - local result = u.lsp_client_icon({ test = icon }) + local light_color = u.ligthening_color(color) -- then: - assert.are.same(icon, result) + assert.are.equal('#a77d28', light_color) end) - end) - it('should take an icon from the "nvim-web-devicons"', function() - -- given: - local client = { config = { filetypes = { 'other_type', 'test' } } } - local icon = { name = 'test', icon = '!' } - local mock = function() - return { test = icon } - end - test.use_mocked_module('nvim-web-devicons', 'get_icons', mock, function() + it('should make a color darker', function() + -- given: + local color = '#9e6f11' + -- when: - local result = u.lsp_client_icon({}, client) + local light_color = u.darkening_color(color) -- then: - assert.are.same(icon, result) + assert.are.equal('#8e630f', light_color) end) end) end)