├── .gitignore ├── .gtea ├── LICENSE ├── README.md ├── doc ├── chaivim-modules.txt ├── chaivim.md ├── chaivim.txt └── modules.md ├── dockerfile ├── gtea.toml ├── images ├── asset.dash.png ├── asset.nix__config.png └── asset.telescope.png ├── lua ├── .luarc.json └── ch │ ├── config.lua │ ├── config │ ├── base.lua │ ├── cmp.lua │ ├── commands.lua │ ├── dash.lua │ ├── fidget.lua │ ├── gitsigns.lua │ ├── highlights.lua │ ├── hl.lua │ ├── incline.lua │ ├── indent.lua │ ├── keymaps.lua │ ├── lazy.lua │ ├── lsp.lua │ ├── lualine.lua │ ├── luasnip.lua │ ├── mini.lua │ ├── null.lua │ ├── options.lua │ ├── telescope.lua │ ├── todo_comments.lua │ ├── treesitter.lua │ ├── trouble.lua │ ├── ui.lua │ └── whichkey.lua │ ├── init.lua │ ├── lazy │ └── plugins.lua │ ├── lib │ ├── autocmd.lua │ ├── color.lua │ ├── event.lua │ ├── fmt.lua │ ├── hl.lua │ ├── init.lua │ ├── keymaps.lua │ ├── math.lua │ ├── options.lua │ └── preload.lua │ ├── load │ ├── async.lua │ ├── autocmds.lua │ ├── constants.lua │ ├── globals.lua │ ├── handle.lua │ ├── init.lua │ └── spec.lua │ ├── log.lua │ ├── modules │ ├── default.lua │ └── init.lua │ ├── parts.lua │ ├── plugin │ ├── cmp-emoji.lua │ ├── command.lua │ ├── dash.lua │ ├── highlight.lua │ ├── hl.lua │ ├── icons.lua │ ├── keymaps.lua │ ├── lsp │ │ ├── diagnostic_lines.lua │ │ ├── init.lua │ │ ├── signature.lua │ │ └── utils.lua │ ├── lspkind.lua │ ├── telescope.lua │ └── transparency.lua │ ├── plugins.lua │ └── ui │ ├── bufferline │ ├── hl.lua │ ├── init.lua │ ├── load.lua │ └── modules.lua │ ├── cheatsheet.lua │ ├── cursor.lua │ ├── handles.lua │ ├── input.lua │ ├── internal │ └── model.lua │ ├── status.lua │ ├── statusline │ ├── hl.lua │ ├── init.lua │ ├── modules │ │ ├── cursor_position.lua │ │ ├── cwd.lua │ │ ├── fileinfo.lua │ │ ├── git_branch.lua │ │ ├── git_status.lua │ │ ├── lsp_diagnostics.lua │ │ ├── lsp_status.lua │ │ ├── macro.lua │ │ ├── mode.lua │ │ └── textinfo.lua │ └── utils.lua │ ├── term │ ├── init.lua │ ├── terminal.lua │ └── utils.lua │ ├── theme.lua │ └── util.lua └── utils ├── bin └── cvim.template └── installer └── install.sh /.gitignore: -------------------------------------------------------------------------------- 1 | doc/tags 2 | -------------------------------------------------------------------------------- /.gtea: -------------------------------------------------------------------------------- 1 | { 2 | "master": "mega" 3 | } 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # chaivim 2 | 3 | :tea: easily configurable neovim system with solid defaults and a cozy editor experience. 4 | 5 | ![dash](./images/asset.dash.png) 6 | 7 | ![telescope__config](./images/asset.telescope.png) 8 | 9 | ## :sparkles: Features 10 | 11 | ### Lazy plugin management 12 | 13 | chaivim uses [lazy.nvim](https://github.com/folke/lazy.nvim) to manage plugins. 14 | 15 | ### integrated plugins 16 | 17 | - :telescope: [telescope](https://github.com/nvim-telescope/telescope.nvim) - a highly extendable fuzzy finder. 18 | - :evergreen_tree: [treesitter](https://github.com/nvim-treesitter/nvim-treesitter) - treesitter configurations and abstraction layer for neovim. 19 | - :pencil: [none-ls](https://github.com/nvimtools/none-ls.nvim) - abstraction layer for lsp diagnostics, code actions, and formatters. 20 | - :computer: [keymaps](https://github.com/comfysage/keymaps.nvim) - a keymap manager for neovim. 21 | - :airplane: [lualine](https://github.com/nvim-lualine/lualine.nvim) - a blazing fast and easy to configure neovim statusline plugin. 22 | - :pencil: [cmp](https://github.com/hrsh7th/nvim-cmp) - a completion plugin for neovim. 23 | - :scissors: [luasnip](https://github.com/L3MON4D3/LuaSnip) with [friendly-snippets](https://github.com/rafamadriz/friendly-snippets) - a snippet engine for neovim. 24 | - :hammer: [mini.nvim](https://github.com/echasnovski/mini.nvim) - the "swiss army knife" among neovim plugins. 25 | - :vertical_traffic_light: [gitsigns](https://github.com/lewis6991/gitsigns.nvim) - git integration for neovim. 26 | - :scroll: [which-key](https://github.com/folke/which-key.nvim) - a keymap ui for neovim. 27 | - :pushpin: [todo-comments](https://github.com/folke/todo-comments.nvim) - highlight, list and search todo comments in your projects. 28 | - :construction: [trouble](https://github.com/folke/trouble.nvim) - a diagnostics manager. 29 | - :balloon: [incline](https://github.com/b0o/incline.nvim) - floating statuslines for neovim. 30 | - :straight_ruler: [indent-blankline](https://github.com/lukas-reineke/indent-blankline.nvim) - indent guides for neovim. 31 | 32 | ## :lock: requirements 33 | 34 | - Neovim `>= 0.10.0` (needs to be built with LuaJIT) 35 | - git `>= 2.19.0` (for partial clones support) 36 | - a Nerd Font (optional) 37 | - [luarocks](https://luarocks.org/) to install rockspecs. 38 | 39 | ## :package: installation 40 | 41 | chaivim can be installed [manually](#manual-install) or through the installer: 42 | ```shell 43 | curl -fsSL https://github.com/comfysage/chaivim/raw/mega/utils/installer/install.sh | sh 44 | ``` 45 | 46 | ### manual install 47 | 48 | ```lua 49 | -- init.lua 50 | local rootpath = vim.fn.stdpath("data") .. "/ch" 51 | local chaipath = rootpath .. "/chai" 52 | 53 | if not vim.uv.fs_stat(chaipath) then 54 | vim.system({ 55 | "git", 56 | "clone", 57 | "--filter=blob:none", 58 | "https://github.com/comfysage/chaivim.git", 59 | chaipath, 60 | }):wait() 61 | end 62 | 63 | vim.opt.rtp:prepend(chaipath) 64 | ``` 65 | 66 | ## :rocket: usage 67 | 68 | ```lua 69 | -- init.lua 70 | require 'ch'.setup('custom.config', 'custom.modules') 71 | 72 | -- lua/custom/config.lua 73 | return { 74 | ui = { 75 | colorscheme = 'evergarden', 76 | transparent_background = false, 77 | }, 78 | } 79 | 80 | -- lua/custom/modules.lua 81 | return { 82 | ch = { 83 | { 84 | 'options', 85 | opts = { 86 | cursorline = false, 87 | tab_width = 2, 88 | scrolloff = 5, 89 | }, 90 | }, 91 | { 92 | 'dash', 93 | opts = { 94 | open_on_startup = true, 95 | }, 96 | }, 97 | }, 98 | custom = { 99 | -- your custom modules (in `lua/custom/`) 100 | }, 101 | } 102 | ``` 103 | or call `require 'ch'.setup 'custom'` to load a custom configuration from `lua/custom/init.lua`: 104 | ```lua 105 | -- init.lua 106 | require 'ch'.setup 'custom' 107 | 108 | -- lua/custom/init.lua 109 | return { 110 | ui = { 111 | colorscheme = 'evergarden', 112 | }, 113 | modules = { 114 | ch = { 115 | { 'options' }, 116 | { 'base' }, 117 | }, 118 | }, 119 | } 120 | ``` 121 | 122 | all config fields can be overwritten after `setup()`: 123 | ```lua 124 | ch.config.ui.colorscheme = 'tokyonight' 125 | ``` 126 | 127 | ## :gear: config modules 128 | 129 | view example configurations for modules [here](doc/modules.md). 130 | 131 | ## :camera: screenshots 132 | 133 | ![nix-config](./images/asset.nix__config.png) 134 | -------------------------------------------------------------------------------- /doc/chaivim.md: -------------------------------------------------------------------------------- 1 | # getting started 2 | 3 | you can install chaivim using the installer: 4 | ```bash 5 | curl -fsSL https://github.com/comfysage/chaivim/raw/mega/utils/installer/install.sh | sh 6 | cvim 7 | 8 | ``` 9 | or you can get started using chaivim with the [starter template](https://github.com/comfysage/chaivim/tree/start): 10 | ```bash 11 | git clone --depth 1 -b start https://github.com/comfysage/chaivim.git ~/.config/nvim 12 | nvim 13 | ``` 14 | 15 | # usage 16 | 17 | chaivim configuration is usually split into `custom.config` and `custom.modules`. 18 | 19 | ```lua 20 | -- lua/custom/config.lua 21 | return { 22 | ui = { 23 | colorscheme = 'evergarden', 24 | transparent_background = false, 25 | }, 26 | } 27 | 28 | -- lua/custom/modules.lua 29 | return { 30 | ch = { 31 | { 32 | 'options', 33 | opts = { 34 | cursorline = false, 35 | tab_width = 2, 36 | scrolloff = 5, 37 | }, 38 | }, 39 | { 40 | 'dash', 41 | opts = { 42 | open_on_startup = true, 43 | }, 44 | }, 45 | }, 46 | custom = { 47 | -- your custom modules (in `lua/custom/`) 48 | }, 49 | } 50 | ``` 51 | 52 | # configuration 53 | 54 | ch-config 55 | : ch configuration 56 | 57 | ch-config-ui 58 | : ui configuration 59 | 60 | ```lua 61 | { 62 | -- chaivim uses evergarden by default 63 | -- some other cozy alternatives are 64 | -- - [kanagawa](https://github.com/rebelot/kanagawa.nvim) 65 | -- - [gruvboxed](https://github.com/comfysage/gruvboxed) 66 | -- - [iceberg](https://github.com/cocopon/iceberg.vim) 67 | colorscheme = 'evergarden', 68 | transparent_background = false, 69 | -- separators: slant (, ) round (,) block (█,█) arrow (,) 70 | -- these are used for ui components like the statusline 71 | separator_style = 'round', 72 | -- (optionally) use 'nvim-tree/nvim-web-devicons' 73 | devicons = true, 74 | -- used by `comfysage/base46` 75 | theme_config = { 76 | keyword = { italic = false }, 77 | types = { italic = false }, 78 | comment = { italic = false }, 79 | search = { reverse = false }, 80 | inc_search = { reverse = true } 81 | }, 82 | -- key labels used by `keymaps.nvim` and some ui components 83 | key_labels = { 84 | -- text keys 85 | [''] = 'SPC', 86 | [''] = 'RET', 87 | [''] = 'BS', 88 | -- tab keys 89 | [''] = 'TAB', 90 | [''] = 'SHIFT TAB', 91 | -- leader key 92 | [''] = 'LD', 93 | -- directional keys 94 | [''] = '↑', 95 | [''] = '←', 96 | [''] = '↓', 97 | [''] = '→', 98 | } 99 | ``` 100 | 101 | ch-config-log_level 102 | : Minimum log level 103 | 104 | Set to `vim.log.levels.OFF` to disable logging from `chai`, or `vim.log.levels.TRACE` 105 | to enable all logging. 106 | 107 | Type: `vim.log.levels` (default: `vim.log.levels.INFO`) 108 | 109 | module-spec 110 | : specification for a module 111 | 112 | modules.highlights.fix 113 | : specification for ch highlight module 114 | 115 | Type: `function` (default: `nil`) 116 | -------------------------------------------------------------------------------- /doc/chaivim.txt: -------------------------------------------------------------------------------- 1 | *chaivim.txt* a cozy neovim system 2 | 3 | ============================================================================== 4 | Table of Contents *chaivim-table-of-contents* 5 | 6 | 1. getting started |chaivim-getting-started| 7 | 2. usage |chaivim-usage| 8 | 3. configuration |chaivim-configuration| 9 | 10 | ============================================================================== 11 | 1. getting started *chaivim-getting-started* 12 | 13 | you can install chaivim using the installer: 14 | 15 | >bash 16 | curl -fsSL https://github.com/comfysage/chaivim/raw/mega/utils/installer/install.sh | sh 17 | cvim 18 | < 19 | 20 | or you can get started using chaivim with the starter template 21 | : 22 | 23 | >bash 24 | git clone --depth 1 -b start https://github.com/comfysage/chaivim.git ~/.config/nvim 25 | nvim 26 | < 27 | 28 | 29 | ============================================================================== 30 | 2. usage *chaivim-usage* 31 | 32 | chaivim configuration is usually split into `custom.config` and 33 | `custom.modules`. 34 | 35 | >lua 36 | -- lua/custom/config.lua 37 | return { 38 | ui = { 39 | colorscheme = 'evergarden', 40 | transparent_background = false, 41 | }, 42 | } 43 | 44 | -- lua/custom/modules.lua 45 | return { 46 | ch = { 47 | { 48 | 'options', 49 | opts = { 50 | cursorline = false, 51 | tab_width = 2, 52 | scrolloff = 5, 53 | }, 54 | }, 55 | { 56 | 'dash', 57 | opts = { 58 | open_on_startup = true, 59 | }, 60 | }, 61 | }, 62 | custom = { 63 | -- your custom modules (in `lua/custom/`) 64 | }, 65 | } 66 | < 67 | 68 | 69 | ============================================================================== 70 | 3. configuration *chaivim-configuration* 71 | 72 | 73 | *chaivim-ch-config* 74 | 75 | 76 | ch-config ch configuration 77 | 78 | 79 | *chaivim-ch-config-ui* 80 | 81 | 82 | ch-config-ui ui configuration 83 | 84 | 85 | 86 | >lua 87 | { 88 | -- chaivim uses evergarden by default 89 | -- some other cozy alternatives are 90 | -- - [kanagawa](https://github.com/rebelot/kanagawa.nvim) 91 | -- - [gruvboxed](https://github.com/comfysage/gruvboxed) 92 | -- - [iceberg](https://github.com/cocopon/iceberg.vim) 93 | colorscheme = 'evergarden', 94 | transparent_background = false, 95 | -- separators: slant (, ) round (,) block (█,█) arrow (,) 96 | -- these are used for ui components like the statusline 97 | separator_style = 'round', 98 | -- (optionally) use 'nvim-tree/nvim-web-devicons' 99 | devicons = true, 100 | -- used by `comfysage/base46` 101 | theme_config = { 102 | keyword = { italic = false }, 103 | types = { italic = false }, 104 | comment = { italic = false }, 105 | search = { reverse = false }, 106 | inc_search = { reverse = true } 107 | }, 108 | -- key labels used by `keymaps.nvim` and some ui components 109 | key_labels = { 110 | -- text keys 111 | [''] = 'SPC', 112 | [''] = 'RET', 113 | [''] = 'BS', 114 | -- tab keys 115 | [''] = 'TAB', 116 | [''] = 'SHIFT TAB', 117 | -- leader key 118 | [''] = 'LD', 119 | -- directional keys 120 | [''] = '↑', 121 | [''] = '←', 122 | [''] = '↓', 123 | [''] = '→', 124 | } 125 | < 126 | 127 | 128 | *chaivim-ch-config-log_level* 129 | 130 | 131 | ch-config-log_level Minimum log level 132 | 133 | 134 | 135 | Set to `vim.log.levels.OFF` to disable logging from `chai`, or 136 | `vim.log.levels.TRACE` to enable all logging. 137 | 138 | Type: `vim.log.levels` (default: `vim.log.levels.INFO`) 139 | 140 | 141 | *chaivim-module-spec* 142 | 143 | 144 | module-spec specification for a module 145 | 146 | 147 | *chaivim-modules.highlights.fix* 148 | 149 | 150 | modules.highlights.fix specification for ch highlight module 151 | 152 | 153 | 154 | Type: `function` (default: `nil`) 155 | 156 | Generated by panvimdoc 157 | 158 | vim:tw=78:ts=8:noet:ft=help:norl: 159 | -------------------------------------------------------------------------------- /dockerfile: -------------------------------------------------------------------------------- 1 | # █▄░█ █▀▀ █▀█ █░█ █ █▀▄▀█ 2 | # █░▀█ ██▄ █▄█ ▀▄▀ █ █░▀░█ 3 | 4 | FROM alpine as builder 5 | 6 | ARG BUILD_DEPS="autoconf automake cmake curl g++ gettext gettext-dev git libtool make ninja openssl pkgconfig unzip binutils wget" 7 | ARG TARGET=nightly 8 | 9 | RUN apk add --no-cache ${BUILD_DEPS} && \ 10 | git clone https://github.com/neovim/neovim.git /tmp/neovim && \ 11 | cd /tmp/neovim && \ 12 | git fetch --all --tags -f && \ 13 | git checkout ${TARGET} && \ 14 | make CMAKE_BUILD_TYPE=RelWithDebInfo CMAKE_INSTALL_PREFIX=/usr/local/ && \ 15 | make install && \ 16 | strip /usr/local/bin/nvim 17 | 18 | FROM alpine 19 | COPY --from=builder /usr/local /usr/local/ 20 | RUN true # see: https://github.com/moby/moby/issues/37965 21 | # Required shared libraries 22 | COPY --from=builder /lib/ld-musl-x86_64.so.1 /lib/ 23 | RUN true 24 | COPY --from=builder /usr/lib/libgcc_s.so.1 /usr/lib/ 25 | RUN true 26 | COPY --from=builder /usr/lib/libintl.so.8 /usr/lib/ 27 | 28 | # █▀▄ █▀▀ █▀█ █▀▀ █▄░█ █▀▄ █▀▀ █▄░█ █▀▀ █ █▀▀ █▀ 29 | # █▄▀ ██▄ █▀▀ ██▄ █░▀█ █▄▀ ██▄ █░▀█ █▄▄ █ ██▄ ▄█ 30 | 31 | RUN apk update && apk add --no-interactive git bash curl clang 32 | 33 | # █ █▄░█ █▀ ▀█▀ ▄▀█ █░░ █░░ █▀▀ █▀█ 34 | # █ █░▀█ ▄█ ░█░ █▀█ █▄▄ █▄▄ ██▄ █▀▄ 35 | 36 | COPY ./utils/installer/install.sh install.sh 37 | 38 | RUN chmod u+x ./install.sh && ./install.sh 39 | 40 | SHELL ["bash"] 41 | CMD ["/root/.local/bin/cvim"] 42 | -------------------------------------------------------------------------------- /gtea.toml: -------------------------------------------------------------------------------- 1 | [main] 2 | branch = "mega" 3 | 4 | [nightly] 5 | branch = "nightly" 6 | enable = false 7 | 8 | [feature] 9 | prefix = "feat" 10 | -------------------------------------------------------------------------------- /images/asset.dash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/comfysage/chaivim/06efc4130adbe76e5b7747b6553beddc1ff43e92/images/asset.dash.png -------------------------------------------------------------------------------- /images/asset.nix__config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/comfysage/chaivim/06efc4130adbe76e5b7747b6553beddc1ff43e92/images/asset.nix__config.png -------------------------------------------------------------------------------- /images/asset.telescope.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/comfysage/chaivim/06efc4130adbe76e5b7747b6553beddc1ff43e92/images/asset.telescope.png -------------------------------------------------------------------------------- /lua/.luarc.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/sumneko/vscode-lua/master/setting/schema.json", 3 | "diagnostics": { 4 | "disable": [ "duplicate-doc-field", 5 | "duplicate-set-field", 6 | "duplicate-doc-alias" ] 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /lua/ch/config.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | ---@alias vim.api.keyset.win_config.border 'none'|'single'|'double'|'rounded'|'solid'|'shadow'|string[] 4 | 5 | ---@type ch.config 6 | M.default_config = { 7 | config_module = CONFIG_MODULE, 8 | log_level = vim.log.levels.INFO, 9 | ui = { 10 | colorscheme = 'evergarden', -- or 'habamax' or 'zaibatsu' or 'retrobox' 11 | transparent_background = false, 12 | -- separators: slant (, ) round (,) block (█,█) arrow (,) 13 | separator_style = 'round', 14 | float_border = 'rounded', 15 | -- use 'nvim-tree/nvim-web-devicons' 16 | devicons = true, 17 | theme_config = { 18 | keyword = { italic = false }, 19 | types = { italic = false }, 20 | comment = { italic = false }, 21 | search = { reverse = false }, 22 | inc_search = { reverse = true } 23 | }, 24 | key_labels = { 25 | -- text keys 26 | [''] = 'SPC', 27 | [''] = 'RET', 28 | [''] = 'BS', 29 | -- tab keys 30 | [''] = 'TAB', 31 | [''] = 'SHIFT TAB', 32 | -- leader key 33 | [''] = 'LD', 34 | -- directional keys 35 | [''] = '↑', 36 | [''] = '←', 37 | [''] = '↓', 38 | [''] = '→', 39 | }, 40 | }, 41 | inputs = 'ch.lazy.plugins', 42 | plugins = 'plugins', 43 | modules = {}, 44 | } 45 | 46 | ---@param opts ch.config 47 | ---@return ch.config 48 | function M.setup(opts) 49 | ch.config = vim.tbl_deep_extend('force', M.default_config, opts) 50 | return ch.config 51 | end 52 | 53 | return M 54 | -------------------------------------------------------------------------------- /lua/ch/config/base.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | M.module = { 4 | default = { 5 | opts = { 6 | -- `file_associations` can be used to set options for specific filetypes or 7 | -- setup a custom environment when opening a specific file 8 | file_associations = {}, 9 | }, 10 | }, 11 | } 12 | 13 | vim.api.nvim_create_autocmd('TermOpen', { 14 | pattern = '*', 15 | callback = function() 16 | vim.cmd('startinsert') 17 | end, 18 | desc = 'start insert mode on TermOpen', 19 | }) 20 | 21 | vim.api.nvim_create_autocmd('TermOpen', { 22 | pattern = '*', 23 | callback = function() 24 | vim.opt_local.number = false 25 | end, 26 | desc = 'remove line numbers', 27 | }) 28 | 29 | require 'ch.load.handle'.create { 30 | event = 'TermOpen', priority = 5, 31 | desc = 'create quit keymap', 32 | fn = function(ev) 33 | vim.keymap.set({ 't' }, 'q', function() 34 | return [[]] 35 | end, { buffer = ev.buf, expr = true }) 36 | end, 37 | } 38 | 39 | -- highlight when yanking (copying) text 40 | -- > try it with `yap` in normal mode 41 | -- > see `:help vim.highlight.on_yank()` 42 | vim.api.nvim_create_autocmd('TextYankPost', { 43 | desc = 'highlight when yanking (copying) text', 44 | group = vim.api.nvim_create_augroup('highlight-yank', { clear = true }), 45 | callback = function() 46 | vim.highlight.on_yank { 47 | timeout = 200, 48 | } 49 | end, 50 | }) 51 | 52 | -- mkdir path 53 | 54 | -- autocmd BufWritePre * call s:Mkdir() 55 | ch.lib.autocmd.create { 56 | event = 'BufWritePre', 57 | desc = 'create path to file', 58 | fn = function() 59 | local dir = vim.fn.expand(':p:h') 60 | 61 | -- This handles URLs using netrw. See ':help netrw-transparent' for details. 62 | if dir:find('%l+://') == 1 then 63 | return 64 | end 65 | 66 | if vim.fn.isdirectory(dir) == 0 then 67 | vim.fn.mkdir(dir, 'p') 68 | end 69 | end, 70 | } 71 | 72 | -- white space 73 | vim.cmd [[ 74 | function! StripTrailingWhitespace() 75 | if !&binary && &filetype != 'diff' 76 | normal mz 77 | normal Hmy 78 | %s/\s\+$//e 79 | normal 'yz 80 | normal `z 81 | endif 82 | endfunction 83 | ]] 84 | 85 | -- statusline 86 | require 'ch.plugin.command'.create { 87 | name = 'ToggleStatusLine', fn = function(_) 88 | if vim.o.laststatus == 0 then 89 | vim.opt.laststatus = 3 90 | vim.opt.cmdheight = ch.lib.options:get('options', 'cmdheight') 91 | else 92 | vim.opt.laststatus = 0 93 | vim.opt.cmdheight = 0 94 | end 95 | end, 96 | } 97 | 98 | require 'ch.plugin.command'.create { 99 | name = 'Close', opts = { bang = true }, fn = function(props) 100 | local buf = vim.api.nvim_get_current_buf() 101 | if props.bang then 102 | vim.api.nvim_buf_delete(buf, { force = true }) 103 | else 104 | local is_changed = vim.fn.getbufinfo(buf)[1].changed == 1 105 | if is_changed then 106 | vim.ui.input({ prompt = 'buffer has changes, are you sure? y/N' }, function(input) 107 | if input == 'yes' or input == 'y' then 108 | vim.api.nvim_buf_delete(buf, { force = true }) 109 | end 110 | end) 111 | else 112 | vim.api.nvim_buf_delete(buf, {}) 113 | end 114 | end 115 | end, 116 | } 117 | 118 | ---@class BaseConfig 119 | ---@field file_associations { [1]: string[], [2]: string, [3]: function }[] 120 | 121 | --- Setup options 122 | ---@param opts BaseConfig 123 | function M.setup(opts) 124 | -- { { patterns... }, description, callback } 125 | vim.iter(ipairs(opts.file_associations)):each(function(_, item) 126 | if not type(item[1]) == 'table' then 127 | return 128 | end 129 | if not type(item[2]) == 'string' then 130 | return 131 | end 132 | if not type(item[3]) == 'function' then 133 | return 134 | end 135 | vim.api.nvim_create_autocmd('BufEnter', { 136 | pattern = item[1], 137 | callback = item[3], 138 | group = ch.group_id, 139 | desc = item[2], 140 | }) 141 | end) 142 | end 143 | 144 | return M 145 | -------------------------------------------------------------------------------- /lua/ch/config/cmp.lua: -------------------------------------------------------------------------------- 1 | return { 2 | module = { 3 | default = { 4 | opts = { 5 | snippet_engine = 'luasnip', 6 | mappings = { 7 | docs_down = '', 8 | docs_up = '', 9 | complete = '', 10 | close = '', 11 | }, 12 | -- 'tab' or 'enter' 13 | completion_style = 'tab', 14 | -- 'evergreen' | 'nyoom' 15 | menu_style = 'evergreen', 16 | -- register `ch.plugin.cmp-emoji` 17 | use_emoji_source = true, 18 | config = { 19 | window = { 20 | completion = { 21 | winhighlight = "Normal:Pmenu,FloatBorder:Pmenu,CursorLine:PmenuSel", 22 | col_offset = 0, 23 | side_padding = 1, 24 | }, 25 | }, 26 | snippet = { 27 | -- REQUIRED - you must specify a snippet engine 28 | expand = function(_) 29 | end, 30 | }, 31 | mapping = {}, 32 | formatting = {}, 33 | -- view = { entries = 'native' }, 34 | experimental = { 35 | ghost_text = { hl_group = 'NonText' }, 36 | } 37 | }, 38 | }, 39 | }, 40 | }, 41 | add_sources = function() 42 | local sources = { 43 | 'hrsh7th/cmp-nvim-lsp', 44 | 'hrsh7th/cmp-nvim-lua', 45 | 'hrsh7th/cmp-buffer', 46 | 'hrsh7th/cmp-path', 47 | 'hrsh7th/cmp-cmdline' 48 | } 49 | require('ch.plugins').load_plugins(sources) 50 | end, 51 | setup = function(opts) 52 | vim.g.indentLine_conceallevel = 2 53 | vim.g.indentLine_concealcursor = "inc" 54 | 55 | require 'ch.plugins'.load 'nvim-cmp' 56 | 57 | local ok, cmp = SR_L 'cmp' 58 | if not ok then 59 | return 60 | end 61 | 62 | -- local status, result = pcall(require, 'config.plugin.cmp-emoji') 63 | -- if not status then 64 | -- vim.notify('error while loading module:\n\t' .. result, vim.log.levels.ERROR) 65 | -- return 66 | -- end 67 | 68 | local snippet_fn = { 69 | vsnip = function(args) vim.fn["vsnip#anonymous"](args.body) end, 70 | luasnip = function(args) require('luasnip').lsp_expand(args.body) end, 71 | snippy = function(args) require('snippy').expand_snippet(args.body) end, 72 | ultisnips = function(args) vim.fn["UltiSnips#Anon"](args.body) end, 73 | } 74 | if snippet_fn[opts.snippet_engine] then 75 | opts.config.snippet.expand = snippet_fn[opts.snippet_engine] 76 | end 77 | 78 | ---@diagnostic disable-next-line redefined-local 79 | local ok, lspkind = SR_L 'ch.plugin.lspkind' 80 | if ok then 81 | opts.config.formatting = lspkind.create_formatter(opts.menu_style) 82 | end 83 | if opts.menu_style == 'nyoom' then 84 | opts.config.window.completion.col_offset = -3 85 | opts.config.window.completion.side_padding = 0 86 | end 87 | 88 | opts.config.sources = { 89 | { name = 'nvim_lua' }, 90 | { name = 'nvim_lsp' }, 91 | { name = opts.snippet_engine, max_item_count = 5 }, 92 | { name = 'path', max_item_count = 5 }, 93 | { name = 'cmdline', max_item_count = 5 }, 94 | { name = 'buffer', max_item_count = 5 }, 95 | } 96 | 97 | keymaps.insert[opts.mappings.docs_down] = { function() cmp.scroll_docs(-4) end, '', group = 'cmp' } 98 | keymaps.insert[opts.mappings.docs_up] = { function() cmp.scroll_docs(4) end, '', group = 'cmp' } 99 | keymaps.insert[opts.mappings.complete] = { function() cmp.complete() end, '', group = 'cmp' } 100 | keymaps.insert[opts.mappings.close] = { function() cmp.abort() end, '', group = 'cmp' } 101 | vim.keymap.set('c', opts.mappings.close, function() cmp.close() end, { silent = true, noremap = true }) 102 | 103 | if opts.completion_style == 'tab' then 104 | keymaps.insert[''] = { function() cmp.confirm({ select = true }) end, '', group = 'cmp' } 105 | keymaps.insert[''] = { function() cmp.select_next_item() end, '', group = 'cmp' } 106 | keymaps.insert[''] = { function() cmp.select_prev_item() end, '', group = 'cmp' } 107 | end 108 | if opts.completion_style == 'enter' then 109 | keymaps.insert[''] = { function() cmp.confirm({ select = true }) end, '', group = 'cmp' } 110 | keymaps.insert[''] = { function() cmp.select_next_item() end, '', group = 'cmp' } 111 | keymaps.insert[''] = { function() cmp.select_prev_item() end, '', group = 'cmp' } 112 | end 113 | 114 | require 'ch.config.cmp'.add_sources() 115 | cmp.setup(opts.config) 116 | 117 | -- Set configuration for specific filetype. 118 | cmp.setup.filetype('gitcommit', { 119 | sources = cmp.config.sources({ 120 | -- { name = 'cmp_git' }, -- You can specify the `cmp_git` source if you installed it. 121 | }, { 122 | { name = 'buffer' }, 123 | }) 124 | }) 125 | 126 | cmp.setup.filetype('markdown', { 127 | sources = { 128 | { name = opts.snippet_engine, max_item_count = 5 }, 129 | { name = 'emoji' }, 130 | { name = "dictionary", keyword_length = 2, }, 131 | { name = 'path' }, 132 | { name = 'buffer', max_item_count = 5 }, 133 | } 134 | }) 135 | 136 | -- Use buffer source for `/` 137 | cmp.setup.cmdline('/', { 138 | sources = { 139 | { name = 'buffer' } 140 | } 141 | }) 142 | 143 | -- Use cmdline & path source for ':' 144 | cmp.setup.cmdline(':', { 145 | sources = { 146 | { name = 'path' }, 147 | { name = 'cmdline' } 148 | } 149 | }) 150 | 151 | if opts.use_emoji_source then 152 | require 'ch.plugin.cmp-emoji' 153 | end 154 | end 155 | } 156 | -------------------------------------------------------------------------------- /lua/ch/config/commands.lua: -------------------------------------------------------------------------------- 1 | return { 2 | module = { 3 | default = { 4 | opts = { 5 | commands = { 6 | cheatsheet = require 'ch.ui.cheatsheet'.open, 7 | ['ToggleTransparentBG'] = function() 8 | ---@diagnostic disable 9 | _G.toggle_transparent_background() 10 | end, 11 | }, 12 | }, 13 | }, 14 | }, 15 | setup = function(opts) 16 | vim.iter(pairs(opts.commands)):each(function(name, props) 17 | require 'ch.plugin.command'.create { 18 | name = name, 19 | fn = type(props) == 'table' and props.fn or props, 20 | opts = type(props) == 'table' and props.opts or nil, 21 | } 22 | end) 23 | end, 24 | } 25 | -------------------------------------------------------------------------------- /lua/ch/config/dash.lua: -------------------------------------------------------------------------------- 1 | return { 2 | module = { 3 | default = { 4 | opts = { 5 | open_on_startup = true, 6 | header = { 7 | [[]], 8 | [[ /l、 ]], 9 | [[ (゚、 。 7 ]], 10 | [[ l ~ヽ ]], 11 | [[ じしf_,)ノ ]], 12 | [[]], 13 | }, 14 | buttons = function() 15 | local buttons = { 16 | ch.modules.ch.telescope.opts.mappings.find_files, 17 | ch.modules.ch.telescope.opts.mappings.colorscheme, 18 | } 19 | 20 | local result = {} 21 | 22 | vim.iter(ipairs(buttons)):each(function(i, lhs) 23 | local map = require 'keymaps.data'.get_mapping({ lhs = lhs }) or { desc = '', lhs = '', rhs = print } 24 | result[i] = { map.desc, map.lhs, map.rhs } 25 | end) 26 | 27 | local map = require 'keymaps.data'.get_mapping({ desc = 'show cheatsheet' }) or { desc = '', lhs = '', rhs = print } 28 | result[#result + 1] = { map.desc, map.lhs, map.rhs } 29 | return result 30 | end, 31 | }, 32 | }, 33 | }, 34 | setup = function(opts) 35 | require 'ch.plugin.command'.create { 36 | name = 'Dash', fn = function(_) 37 | require 'ch.plugin.dash'.open(ch.modules.ch.dash.opts) 38 | end, 39 | } 40 | if opts.open_on_startup then 41 | vim.defer_fn(function() 42 | local buf_lines = vim.api.nvim_buf_get_lines(0, 0, 1, false) 43 | local no_buf_content = vim.api.nvim_buf_line_count(0) == 1 and buf_lines[1] == "" 44 | local bufname = vim.api.nvim_buf_get_name(0) 45 | 46 | if bufname == "" and no_buf_content then 47 | require 'ch.plugin.dash'.open(opts) 48 | end 49 | end, 0) 50 | end 51 | end 52 | } 53 | -------------------------------------------------------------------------------- /lua/ch/config/fidget.lua: -------------------------------------------------------------------------------- 1 | local opts_table = { 2 | bottom = { 3 | notification = { 4 | view = { 5 | stack_upwards = true, 6 | }, 7 | window = { 8 | align = 'bottom', 9 | }, 10 | }, 11 | }, 12 | top = { 13 | notification = { 14 | view = { 15 | stack_upwards = false, 16 | }, 17 | window = { 18 | align = 'top', 19 | }, 20 | }, 21 | }, 22 | } 23 | 24 | return { 25 | module = { 26 | default = { 27 | opts = { 28 | ui = { 29 | position = 'bottom', 30 | }, 31 | config = { 32 | progress = { 33 | display = { 34 | done_icon = '', 35 | done_style = '@constant', 36 | }, 37 | }, 38 | notification = { 39 | override_vim_notify = true, 40 | filter = vim.log.levels.DEBUG, 41 | configs = { 42 | default = { 43 | name = 'Notifications', 44 | icon = '', 45 | ttl = 4, 46 | group_style = 'Title', 47 | icon_style = 'Special', 48 | annote_style = 'Question', 49 | debug_style = 'Comment', 50 | info_style = 'Question', 51 | warn_style = 'WarningMsg', 52 | error_style = 'ErrorMsg', 53 | debug_annote = 'DEBUG', 54 | info_annote = 'INFO', 55 | warn_annote = 'WARN', 56 | error_annote = 'ERROR', 57 | }, 58 | }, 59 | view = { 60 | stack_upwards = true, 61 | group_separator_hl = 'NonText', 62 | }, 63 | window = { 64 | normal_hl = 'NonText', 65 | winblend = 0, 66 | align = 'bottom', 67 | }, 68 | }, 69 | }, 70 | }, 71 | }, 72 | }, 73 | setup = function(opts) 74 | ch.log('fidget.setup', 'loading fidget.') 75 | require('ch.plugins').load 'fidget' 76 | 77 | local ok, fidget = SR_L 'fidget' 78 | if not ok then 79 | return 80 | end 81 | local additional_opts = opts_table[opts.ui.position] 82 | opts.config = vim.tbl_deep_extend('force', opts.config, additional_opts) 83 | 84 | fidget.setup(opts.config) 85 | end, 86 | } 87 | -------------------------------------------------------------------------------- /lua/ch/config/gitsigns.lua: -------------------------------------------------------------------------------- 1 | return { 2 | module = { 3 | default = { 4 | opts = { 5 | mappings = { 6 | next_hunk = ']c', 7 | prev_hunk = '[c', 8 | stage_hunk = ',hs', 9 | reset_hunk = ',hr', 10 | stage_buffer = ',hS', 11 | undo_stage_hunk = ',hu', 12 | reset_buffer = ',hR', 13 | preview_hunk = ',hp', 14 | show_line_blame = ',hb', 15 | toggle_current_line_blame = '.gb', 16 | toggle_deleted = '.gd', 17 | diffthis = ',hd', 18 | show_diff = ',hD', 19 | select_hunk = 'ih', 20 | }, 21 | config = { 22 | signs = { 23 | add = { text = '│' }, 24 | change = { text = '│' }, 25 | delete = { text = '│' }, 26 | topdelete = { text = '‾' }, 27 | changedelete = { text = '~' }, 28 | untracked = { text = '┆' }, 29 | }, 30 | signcolumn = true, -- Toggle with `:Gitsigns toggle_signs` 31 | numhl = false, -- Toggle with `:Gitsigns toggle_numhl` 32 | linehl = false, -- Toggle with `:Gitsigns toggle_linehl` 33 | word_diff = false, -- Toggle with `:Gitsigns toggle_word_diff` 34 | watch_gitdir = { 35 | interval = 1000, 36 | follow_files = true 37 | }, 38 | attach_to_untracked = true, 39 | current_line_blame = false, -- Toggle with `:Gitsigns toggle_current_line_blame` 40 | current_line_blame_opts = { 41 | virt_text = true, 42 | virt_text_pos = 'eol', -- 'eol' | 'overlay' | 'right_align' 43 | delay = 500, 44 | ignore_whitespace = false, 45 | }, 46 | current_line_blame_formatter = ', ~ ', 47 | sign_priority = GC.priority.signs.git, 48 | update_debounce = 100, 49 | status_formatter = nil, -- Use default 50 | max_file_length = 40000, -- Disable if file is longer than this (in lines) 51 | preview_config = { 52 | -- Options passed to nvim_open_win 53 | border = 'rounded', 54 | style = 'minimal', 55 | relative = 'cursor', 56 | row = 0, 57 | col = 1 58 | }, 59 | on_attach = function(_) 60 | end, 61 | }, 62 | }, 63 | }, 64 | }, 65 | setup = function(opts) 66 | require 'ch.plugins'.load 'gitsigns' 67 | 68 | local ok, gs = SR_L 'gitsigns' 69 | if not ok then 70 | return 71 | end 72 | 73 | gs.setup(opts.config) 74 | 75 | -- clear staged signs 76 | local staged = { 77 | { 'GitSignsStagedAdd', 'GitSignsAdd', }, 78 | { 'GitSignsStagedChange', 'GitSignsChange', }, 79 | { 'GitSignsStagedDelete', 'GitSignsDelete', }, 80 | { 'GitSignsStagedChangedelete', 'GitSignsChangedelete', }, 81 | { 'GitSignsStagedTopdelete', 'GitSignsTopdelete', }, 82 | { 'GitSignsStagedAddNr', 'GitSignsAddNr', }, 83 | { 'GitSignsStagedChangeNr', 'GitSignsChangeNr', }, 84 | { 'GitSignsStagedDeleteNr', 'GitSignsDeleteNr', }, 85 | { 'GitSignsStagedChangedeleteNr', 'GitSignsChangedeleteNr', }, 86 | { 'GitSignsStagedTopdeleteNr', 'GitSignsTopdeleteNr', }, 87 | { 'GitSignsStagedAddLn', 'GitSignsAddLn', }, 88 | { 'GitSignsStagedChangeLn', 'GitSignsChangeLn', }, 89 | { 'GitSignsStagedDeleteLn', 'GitSignsDeleteLn', }, 90 | { 'GitSignsStagedChangedeleteLn', 'GitSignsChangedeleteLn', }, 91 | { 'GitSignsStagedTopdeleteLn', 'GitSignsTopdeleteLn', }, 92 | } 93 | vim.iter(staged):each(function(v) 94 | vim.api.nvim_set_hl(0, v[1], {link=v[2]}) 95 | end) 96 | 97 | -- Navigation 98 | keymaps.normal[opts.mappings.next_hunk] = { 99 | function() 100 | if vim.wo.diff then return ch.modules.ch.gitsigns.opts.mappings.next_hunk end 101 | vim.schedule(function() gs.next_hunk() end) 102 | return '' 103 | end, 104 | 'jump to the next hunk in the current buffer', 105 | group = 'git', 106 | { expr = true } 107 | } 108 | 109 | keymaps.normal[opts.mappings.prev_hunk] = { 110 | function() 111 | if vim.wo.diff then return ch.modules.ch.gitsigns.opts.mappings.prev_hunk end 112 | vim.schedule(function() gs.prev_hunk() end) 113 | return '' 114 | end, 115 | 'jump to the previous hunk in the current buffer', 116 | group = 'git', 117 | { expr = true } 118 | } 119 | 120 | -- Actions 121 | Keymap.group { 122 | group = 'git', 123 | { 'normal', opts.mappings.stage_hunk, gs.stage_hunk, 'stage current hunk' }, 124 | { 'visual', opts.mappings.stage_hunk, gs.stage_hunk, 'stage current hunk' }, 125 | { 'normal', opts.mappings.reset_hunk, gs.reset_hunk, 'reset the lines of the current hunk' }, 126 | { 'visual', opts.mappings.reset_hunk, gs.reset_hunk, 'reset the lines of the current hunk' }, 127 | { 'normal', opts.mappings.stage_buffer, gs.stage_buffer, 'stage buffer' }, 128 | { 'normal', opts.mappings.undo_stage_hunk, gs.undo_stage_hunk, 'undo last call to stage_hunk()' }, 129 | { 'normal', opts.mappings.reset_buffer, gs.reset_buffer, 'reset the lines of all hunks in the buffer' }, 130 | { 'normal', opts.mappings.preview_hunk, gs.preview_hunk, 'preview hunk' }, 131 | { 'normal', opts.mappings.show_line_blame, 132 | function() gs.blame_line { full = true } end, 133 | 'run git blame on the current line and show the results', 134 | }, 135 | { 'normal', opts.mappings.toggle_current_line_blame, gs.toggle_current_line_blame, 'toggle current line blame' }, 136 | { 'normal', opts.mappings.toggle_deleted, gs.toggle_deleted, 'toggle show_deleted' }, 137 | { 'normal', opts.mappings.diffthis, gs.diffthis, 'vimdiff on current file' }, 138 | { 'normal', opts.mappings.show_diff, function() gs.diffthis('~') end, 'vimdiff on current file with base ~' }, 139 | } 140 | 141 | ch.lib.keymaps.register_qf_loader('git_hunks', function() 142 | gs.setqflist('all', { open = false }) 143 | end, { handle_open = true }) 144 | 145 | -- Text object 146 | Keymap.group { 147 | group = 'git', 148 | { 'normal', { 'v', opts.mappings.select_hunk }, ':Gitsigns select_hunk', 'select inside hunk' }, 149 | { 'visual', opts.mappings.select_hunk, ':Gitsigns select_hunk', 'select inside hunk' }, 150 | } 151 | end, 152 | } 153 | -------------------------------------------------------------------------------- /lua/ch/config/highlights.lua: -------------------------------------------------------------------------------- 1 | return { 2 | module = { 3 | default = { 4 | opts = { }, 5 | }, 6 | }, 7 | setup = function(opts) 8 | -- termguicolors 9 | vim.opt.termguicolors = true 10 | -- foldcolumn off 11 | vim.opt.foldcolumn = "0" 12 | -- global statusline 13 | vim.opt.laststatus = 3 14 | 15 | -- fold chars 16 | vim.opt.fillchars:append { fold = " ", foldclose = ">" } 17 | -- endofbuffer chars 18 | vim.opt.fillchars:append { eob = " " } 19 | 20 | vim.opt.listchars:append { tab = "» ", trail = "·" } 21 | local tabwidth = ch.lib.options:get('options', 'tab_width') 22 | vim.opt.listchars:append { lead = '·', leadmultispace = ('·%s'):format((' '):rep(tabwidth-1)) } 23 | 24 | vim.opt.background = "dark" 25 | 26 | vim.cmd([[ command! Highlights source $VIMRUNTIME/syntax/hitest.vim ]]) 27 | 28 | if opts.fix then 29 | ch.lib.autocmd.create { 30 | event = 'ColorScheme', priority = GC.priority.handle.colorscheme.fix, 31 | desc = 'fix highlights', 32 | fn = function(_) 33 | opts.fix() 34 | end 35 | } 36 | end 37 | end 38 | } 39 | -------------------------------------------------------------------------------- /lua/ch/config/hl.lua: -------------------------------------------------------------------------------- 1 | return { 2 | module = { 3 | default = { 4 | opts = { 5 | -- use if your colorscheme lacks some solid defaults 6 | use_overrides = false, 7 | }, 8 | }, 9 | }, 10 | setup = function(opts) 11 | require('ch.plugin.hl').setup() 12 | 13 | if opts.use_overrides then 14 | ch.lib.autocmd.create { 15 | event = 'ColorScheme', 16 | priority = GC.priority.handle.colorscheme.theme, 17 | desc = 'load ui theme', 18 | fn = function(_) 19 | local ok, module = SR_L 'ch.ui.theme' 20 | if not ok then 21 | return 22 | end 23 | module.apply() 24 | end, 25 | } 26 | end 27 | end, 28 | } 29 | -------------------------------------------------------------------------------- /lua/ch/config/incline.lua: -------------------------------------------------------------------------------- 1 | return { 2 | module = { 3 | default = { 4 | opts = { 5 | config = { 6 | debounce_threshold = { 7 | falling = 50, 8 | rising = 10, 9 | }, 10 | hide = { 11 | cursorline = true, 12 | focused_win = false, 13 | only_win = false, 14 | }, 15 | highlight = { 16 | groups = { 17 | InclineNormal = { 18 | default = true, 19 | group = 'NormalFloat', 20 | }, 21 | InclineNormalNC = { 22 | default = true, 23 | group = 'NormalFloat', 24 | }, 25 | }, 26 | }, 27 | ignore = { 28 | buftypes = 'special', 29 | filetypes = {}, 30 | floating_wins = true, 31 | unlisted_buffers = true, 32 | wintypes = 'special', 33 | }, 34 | render = 'basic', 35 | window = { 36 | margin = { 37 | horizontal = 1, 38 | vertical = 1, 39 | }, 40 | options = { 41 | signcolumn = 'no', 42 | wrap = false, 43 | }, 44 | padding = 1, 45 | padding_char = ' ', 46 | placement = { 47 | horizontal = 'right', 48 | vertical = 'top', 49 | }, 50 | width = 'fit', 51 | winhighlight = { 52 | active = { 53 | EndOfBuffer = 'None', 54 | Normal = 'InclineNormal', 55 | Search = 'None', 56 | }, 57 | inactive = { 58 | EndOfBuffer = 'None', 59 | Normal = 'InclineNormalNC', 60 | Search = 'None', 61 | }, 62 | }, 63 | zindex = 50, 64 | }, 65 | }, 66 | }, 67 | }, 68 | }, 69 | setup = function(opts) 70 | ch.log('incline.setup', 'loading incline.') 71 | require('ch.plugins').load 'incline' 72 | 73 | local ok, incline = SR_L 'incline' 74 | if not ok then 75 | return 76 | end 77 | 78 | incline.setup(opts.config) 79 | end, 80 | } 81 | -------------------------------------------------------------------------------- /lua/ch/config/indent.lua: -------------------------------------------------------------------------------- 1 | return { 2 | module = { 3 | default = { 4 | event = 'BufAdd', 5 | opts = { 6 | config = { 7 | indent = { 8 | highlight = { 9 | '@ibl.indent.char.1', 10 | '@ibl.indent.char.2', 11 | '@ibl.indent.char.3', 12 | '@ibl.indent.char.4', 13 | '@ibl.indent.char.5', 14 | '@ibl.indent.char.6', 15 | '@ibl.indent.char.7', 16 | '@ibl.indent.char.8', 17 | '@ibl.indent.char.9', 18 | '@ibl.indent.char.10', 19 | '@ibl.indent.char.11', 20 | '@ibl.indent.char.12', 21 | }, 22 | }, 23 | scope = { 24 | enabled = false, 25 | }, 26 | }, 27 | }, 28 | }, 29 | }, 30 | apply_hl = function() 31 | local highlight = ch.lib.options:get('indent', 'config', 'indent', 'highlight') 32 | for _, hi_name in ipairs(highlight) do 33 | local hi = vim.api.nvim_get_hl(0, { name = hi_name }) 34 | if vim.tbl_isempty(hi) then 35 | vim.api.nvim_set_hl(0, hi_name, { link = "NonText", default = true }) 36 | end 37 | end 38 | end, 39 | setup = function(opts) 40 | ch.log('indent.setup', 'loading indent.') 41 | require('ch.plugins').load 'indent' 42 | 43 | local ok, ibl = SR_L 'ibl' 44 | if not ok then 45 | return 46 | end 47 | require 'ch.config.indent'.apply_hl() 48 | 49 | ch.lib.autocmd.create { 50 | event = 'ColorScheme', priority = GC.priority.handle.colorscheme.plugin, 51 | desc = 'apply indent hls', 52 | fn = function(_) 53 | require 'ch.config.indent'.apply_hl() 54 | end, 55 | } 56 | ibl.setup(opts.config) 57 | end, 58 | } 59 | -------------------------------------------------------------------------------- /lua/ch/config/lazy.lua: -------------------------------------------------------------------------------- 1 | return { 2 | module = { 3 | default = { 4 | opts = { 5 | config = { 6 | ui = { 7 | -- a number <1 is a percentage., >1 is a fixed size 8 | size = { width = 90, height = 0.8 }, 9 | wrap = true, -- wrap the lines in the ui 10 | -- The border to use for the UI window. Accepts same border values as |nvim_open_win()|. 11 | border = 'none', 12 | -- The backdrop opacity. 0 is fully opaque, 100 is fully transparent. 13 | backdrop = 100, 14 | icons = { 15 | cmd = "! ", 16 | config = ch.lib.icons.syntax.constructor, 17 | event = ch.lib.icons.syntax.event, 18 | ft = ch.lib.icons.syntax.file, 19 | init = ch.lib.icons.syntax.constructor, 20 | import = ch.lib.icons.syntax.reference, 21 | keys = ch.lib.icons.syntax.snippet, 22 | lazy = ch.lib.icons.syntax.fn, 23 | loaded = ch.lib.icons.info.loaded, 24 | not_loaded = ch.lib.icons.info.not_loaded, 25 | plugin = ch.lib.icons.syntax.package, 26 | runtime = ch.lib.icons.syntax.null, 27 | source = ch.lib.icons.syntax.module, 28 | start = ch.lib.icons.debug.start, 29 | task = ch.lib.icons.ui.item_prefix, 30 | list = { 31 | '-', 32 | '*', 33 | '*', 34 | '-', 35 | }, 36 | }, 37 | }, 38 | }, 39 | }, 40 | }, 41 | overwrite = { 42 | reload = false, 43 | opts = { 44 | config = { 45 | root = nil, -- directory where plugins will be installed 46 | -- required for ch bootstrap 47 | performance = { 48 | reset_packpath = false, 49 | rtp = { 50 | reset = false, 51 | }, 52 | }, 53 | }, 54 | }, 55 | }, 56 | }, 57 | setup = function(opts) 58 | -- set lazy path 59 | opts.config.root = ch.path.lazy 60 | 61 | -- update global 62 | ch.modules.ch.lazy.opts = opts 63 | 64 | ch.log('lazy.setup', 'loading lazy.') 65 | require 'ch.plugins'.load 'lazy.nvim' 66 | 67 | ch.log('lazy.setup', 'loading plugins.') 68 | ---@diagnostic disable-next-line: redundant-parameter 69 | require 'lazy'.setup(ch.config.inputs, opts.config) 70 | end, 71 | } 72 | -------------------------------------------------------------------------------- /lua/ch/config/lualine.lua: -------------------------------------------------------------------------------- 1 | ---@alias fmt_f fun(str: string): string 2 | ---@alias wrapper_f fun(): string 3 | ---@alias separator { left: string, right: string }|string 4 | --- 5 | ---@class LualineConfig__component 6 | ---@field [1] string 7 | ---@field fmt? fmt_f 8 | ---@field icon? string 9 | ---@field separator? separator 10 | ---@field cond? function 11 | ---@field draw_empty? boolean 12 | ---@field color? any 13 | ---@field type? any 14 | ---@field padding? integer 15 | ---@field on_click? function 16 | 17 | ---@class LualineConfig__options 18 | ---@field icons_enabled? boolean 19 | ---@field theme? 'auto'|string 20 | ---@field component_separators? separator 21 | ---@field section_separators? separator 22 | ---@field always_divide_middle? boolean 23 | ---@field globalstatus? boolean 24 | ---@field refresh? { ['statusline'|'tabline'|'winbar']: integer } 25 | ---@class LualineConfig__sections 26 | ---@field lualine_a? LualineConfig__section 27 | ---@field lualine_b? LualineConfig__section 28 | ---@field lualine_c? LualineConfig__section 29 | ---@field lualine_x? LualineConfig__section 30 | ---@field lualine_y? LualineConfig__section 31 | ---@field lualine_z? LualineConfig__section 32 | ---@alias LualineConfig__section (LualineConfig__component|wrapper_f|string)[] 33 | 34 | ---@class LualineConfig 35 | ---@field options? LualineConfig__options 36 | ---@field sections? LualineConfig__sections 37 | ---@field inactive_sections? LualineConfig__sections 38 | 39 | ---@alias LualineStyle 'minimal' 40 | 41 | ---@type { [LualineStyle]: LualineConfig } 42 | local styles = { 43 | minimal = { 44 | options = { 45 | component_separators = '', 46 | section_separators = '', 47 | } 48 | } 49 | } 50 | 51 | ---@class chLualineOpts__options 52 | ---@field separators? 'slant'|'round'|'block'|'arrow' 53 | 54 | ---@class chLualineOpts 55 | ---@field options chLualineOpts__options 56 | ---@field config LualineConfig 57 | ---@field style? LualineStyle 58 | 59 | return { 60 | module = { 61 | default = { 62 | opts = { 63 | ---@type chLualineOpts__options 64 | options = { 65 | separators = nil, 66 | }, 67 | ---@type LualineConfig 68 | config = { 69 | options = { 70 | icons_enabled = true, 71 | theme = 'auto', 72 | component_separators = { left = '', right = '' }, 73 | section_separators = { left = '', right = '' }, 74 | disabled_filetypes = { 75 | statusline = {}, 76 | winbar = {}, 77 | }, 78 | ignore_focus = {}, 79 | always_divide_middle = true, 80 | globalstatus = true, 81 | refresh = { 82 | statusline = 1000, 83 | tabline = 1000, 84 | winbar = 1000, 85 | } 86 | }, 87 | sections = { 88 | lualine_a = { { 'mode', fmt = function(str) return str:sub(1, 1) end } }, 89 | lualine_b = { 90 | 'branch', 91 | function() return CUTIL.PATH_DIR {} end, 92 | 'diff', 93 | { 94 | 'diagnostics', 95 | symbols = 96 | { 97 | error = ch.lib.icons.diagnostic.error, 98 | warn = ch.lib.icons.diagnostic.warn, 99 | info = ch.lib.icons.diagnostic.info, 100 | hint = ch.lib.icons.diagnostic.hint 101 | } 102 | } 103 | }, 104 | lualine_c = { 'filename' }, 105 | lualine_x = { 'filetype' }, 106 | lualine_y = { function() return CUTIL.FILE_INFO {} end }, 107 | lualine_z = { function() 108 | local row, column = unpack(vim.api.nvim_win_get_cursor(0)) 109 | return "L" .. row .. ":" .. column 110 | end } 111 | }, 112 | inactive_sections = { 113 | lualine_a = {}, 114 | lualine_b = {}, 115 | lualine_c = { 'filename' }, 116 | lualine_x = { function() return vim.fn.expand('%l:%L') end }, 117 | lualine_y = {}, 118 | lualine_z = {} 119 | }, 120 | tabline = {}, 121 | 122 | winbar = {}, 123 | inactive_winbar = {}, 124 | 125 | extensions = {} 126 | }, 127 | }, 128 | }, 129 | overwrite = { 130 | reload = false, 131 | }, 132 | }, 133 | ---@param opts chLualineOpts 134 | setup = function(opts) 135 | ch.log('lualine.setup', 'loading lualine.') 136 | require 'ch.plugins'.load 'lualine' 137 | 138 | local ok, lualine = SR_L 'lualine' 139 | if not ok then 140 | return 141 | end 142 | 143 | local config = opts.config 144 | 145 | if not opts.options.separators then 146 | opts.options.separators = ch.config.ui.separator_style 147 | end 148 | 149 | if opts.options.separators and ch.lib.icons.separator[opts.options.separators] then 150 | config = vim.tbl_deep_extend('force', config, styles.minimal) 151 | 152 | local sep = ch.lib.icons.separator[opts.options.separators] 153 | config.options.section_separators = { left = sep.right, right = sep.left } 154 | end 155 | 156 | if opts.style and styles[opts.style] then 157 | config = vim.tbl_deep_extend('force', config, styles[opts.style]) 158 | end 159 | 160 | lualine.setup(config) 161 | end 162 | } 163 | -------------------------------------------------------------------------------- /lua/ch/config/luasnip.lua: -------------------------------------------------------------------------------- 1 | return { 2 | module = { 3 | default = { 4 | opts = { 5 | mappings = { 6 | -- jump in dynamic snippets 7 | jump_next = '', 8 | jump_prev = '', 9 | -- choose item in choice node 10 | choose_next = '', 11 | choose_prev = '', 12 | }, 13 | config = { 14 | -- This tells LuaSnip to remember to keep around the last snippet. 15 | -- You can jump back into it even if you move outside of the selection 16 | history = true, 17 | updateevents = 'InsertLeave', 18 | enable_autosnippets = false, 19 | ext_opts = nil, 20 | }, 21 | -- can import a table of languages; set to true to import all 22 | -- import_languages = { 'rust', 'go', 'lua', 'c', 'cpp', 'html', 'js', 'bash' }, 23 | import_languages = true, 24 | }, 25 | }, 26 | }, 27 | setup = function(opts) 28 | ch.log('luasnip.setup', 'loading luasnip.') 29 | require 'ch.plugins'.load 'luasnip' 30 | 31 | local ok, _ = SR_L 'luasnip' 32 | if not ok then 33 | return 34 | end 35 | local plugins = { 36 | 'saadparwaiz1/cmp_luasnip', 37 | 'rafamadriz/friendly-snippets', 38 | } 39 | require('ch.plugins').load_plugins(plugins) 40 | 41 | require 'luasnip'.config.set_config(opts.config) 42 | 43 | -- load snippets from snippets directory 44 | local snippets_opts = { 45 | paths = ('%s/%s'):format(ch.path.lazy, 'friendly-snippets'), 46 | } 47 | if opts.import_languages and type(opts.import_languages) == 'table' then 48 | snippets_opts.include = opts.import_languages 49 | end 50 | require 'luasnip.loaders.from_vscode'.lazy_load(snippets_opts) 51 | 52 | -- this will expand the current item or jump to the next item within the snippet. 53 | vim.keymap.set({ 'i', 's' }, opts.mappings.jump_next, function() 54 | if require 'luasnip'.expand_or_jumpable() then 55 | require 'luasnip'.expand_or_jump() 56 | end 57 | end, { silent = true, desc = '[luasnip] expand or jump' }) 58 | 59 | -- is the jump backwards key. 60 | -- this always moves to the previous item within the snippet 61 | vim.keymap.set({ 'i', 's' }, opts.mappings.jump_prev, function() 62 | if require 'luasnip'.jumpable(-1) then 63 | require 'luasnip'.jump(-1) 64 | end 65 | end, { silent = true, desc = '[luasnip] jump backwards' }) 66 | 67 | -- selects the next item within a list of options. 68 | -- This is useful for choice nodes 69 | vim.keymap.set('i', opts.mappings.choose_next, function() 70 | if require 'luasnip'.choice_active() then 71 | require 'luasnip'.change_choice(1) 72 | end 73 | end, { desc = '[luasnip] choose next' }) 74 | -- selects the previous item within a list of options. 75 | vim.keymap.set('i', opts.mappings.choose_prev, function() 76 | if require 'luasnip'.choice_active() then 77 | require 'luasnip'.change_choice(-1) 78 | end 79 | end, { desc = '[luasnip] choose previous' }) 80 | end, 81 | } 82 | -------------------------------------------------------------------------------- /lua/ch/config/null.lua: -------------------------------------------------------------------------------- 1 | return { 2 | module = { 3 | default = { 4 | opts = { 5 | ---@type fun(null): table 6 | ---```lua 7 | ---function(null) 8 | --- return { 9 | --- null.builtins.formatting.stylua 10 | --- } 11 | ---end 12 | ---``` 13 | sources = nil, 14 | mappings = { 15 | format = ',fn', 16 | }, 17 | config = { 18 | sources = {}, 19 | }, 20 | }, 21 | }, 22 | }, 23 | setup = function(opts) 24 | ch.log('null.setup', 'loading null-ls.') 25 | require('ch.plugins').load 'null' 26 | 27 | local ok, null_ls = SR_L 'null-ls' 28 | if not ok then 29 | return 30 | end 31 | 32 | if opts.sources and type(opts.sources) == 'function' then 33 | opts.config.sources = 34 | vim.tbl_deep_extend('force', opts.config.sources, opts.sources(null_ls)) 35 | end 36 | 37 | null_ls.setup(opts.config) 38 | 39 | Keymap.group { 40 | group = 'null', 41 | { 42 | { 43 | 'normal', 44 | opts.mappings.format, 45 | function() 46 | vim.lsp.buf.format { async = true, name = 'null-ls' } 47 | end, 48 | 'format with null-ls', 49 | }, 50 | } 51 | } 52 | end, 53 | } 54 | -------------------------------------------------------------------------------- /lua/ch/config/options.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | M.module = { 4 | default = { 5 | opts = { 6 | cursorline = false, 7 | -- true|false or 'relative' for relative line numbers 8 | number = true, 9 | tab_width = 2, 10 | use_ripgrep = true, 11 | scrolloff = 2, 12 | treesitter_folds = false, 13 | load_plugins = { }, 14 | cmdheight = 0, 15 | cursorstyle = { 16 | normal = 'block', 17 | insert = { 'bar', 25 }, 18 | replace = { 'underscore', 20 }, 19 | }, 20 | clipboard = 'selection', 21 | }, 22 | }, 23 | } 24 | 25 | local default_cursor_style = 'block' 26 | 27 | local function get_cursor_style_value(style) 28 | if type(style) == 'string' and style == 'block' then 29 | return 'block' 30 | end 31 | 32 | if #style ~= 2 then return default_cursor_style end 33 | 34 | if type(style[1]) ~= 'string' or type(style[2]) ~= 'number' then return default_cursor_style end 35 | 36 | local styles = { 37 | bar = 'ver', 38 | underscore = 'hor', 39 | } 40 | return ('%s%s'):format(styles[style[1]], style[2]) 41 | end 42 | 43 | ---@class OptionsConfig 44 | ---@field cursorline boolean 45 | ---@field number boolean|'relative' 46 | ---@field tab_width integer 47 | ---@field scrolloff integer 48 | ---@field use_ripgrep boolean 49 | ---@field treesitter_folds boolean 50 | ---@field load_plugins string[] 51 | ---@field cmdheight boolean 52 | ---@field cursorstyle table<'normal'|'insert'|'replace','string'|{[1]: string, [2]: integer}> 53 | ---@field clipboard 'system'|'selection' 54 | 55 | --- Setup options 56 | ---@param opts OptionsConfig 57 | function M.setup(opts) 58 | vim.opt.encoding = 'utf-8' 59 | vim.opt.fileencoding = 'utf-8' 60 | 61 | -- basic UI 62 | vim.opt.title = true 63 | vim.o.titlestring = '%f · nvim' 64 | vim.opt.errorbells = false 65 | vim.opt.mouse = 'nv' 66 | 67 | vim.opt.guicursor = ('n-v-sm:%s-NCursor,i-c-ci-ve:%s-ICursor,r-cr-o:%s-RCursor'):format( 68 | get_cursor_style_value(opts.cursorstyle.normal), 69 | get_cursor_style_value(opts.cursorstyle.insert), 70 | get_cursor_style_value(opts.cursorstyle.replace) 71 | ) 72 | vim.opt.cursorline = opts.cursorline 73 | vim.opt.showmode = false 74 | vim.opt.showcmd = false 75 | vim.opt.cmdheight = opts.cmdheight 76 | 77 | vim.opt.number = true 78 | vim.opt.relativenumber = false 79 | if opts.number == false then 80 | vim.opt.number = false 81 | vim.opt.relativenumber = false 82 | end 83 | if opts.number == 'relative' then 84 | vim.opt.relativenumber = true 85 | end 86 | vim.opt.numberwidth = 3 87 | vim.opt.signcolumn = 'yes:1' 88 | vim.opt.smarttab = true 89 | 90 | vim.opt.pumheight = 5 91 | vim.opt.wildoptions = { 'fuzzy', 'pum', 'tagfile' } 92 | vim.opt.wildmode = 'longest:full,full' 93 | 94 | vim.opt.conceallevel = 2 95 | vim.opt.concealcursor = 'c' 96 | 97 | vim.opt.shortmess = 'filnrxoOtTIF' 98 | vim.opt.formatoptions = 'tcrqj' 99 | 100 | -- allow cursor to move paste the end of the line in visual block mode 101 | vim.opt.virtualedit = 'block' 102 | 103 | vim.o.timeout = true 104 | vim.o.timeoutlen = 300 105 | 106 | -- indention 107 | vim.opt.cindent = true 108 | vim.opt.smartindent = true 109 | 110 | -- no tab indention 111 | vim.opt.tabstop = opts.tab_width 112 | vim.opt.softtabstop = 1 113 | vim.opt.shiftwidth = opts.tab_width 114 | vim.opt.expandtab = true 115 | 116 | -- Lifecycle 117 | vim.opt.shell = vim.env['SHELL'] or '/usr/bin/bash' 118 | vim.opt.swapfile = false 119 | vim.opt.backup = false 120 | vim.opt.undodir = vim.fn.stdpath 'state' .. '/undodir' 121 | vim.opt.undofile = true 122 | vim.opt.hidden = true 123 | 124 | -- Searching 125 | vim.opt.grepprg = opts.use_ripgrep and 'rg --vimgrep' 126 | or 'grep -n $* /dev/null' 127 | vim.opt.grepformat = '%f:%l:%c:%m' 128 | vim.opt.incsearch = true 129 | vim.opt.hlsearch = false 130 | vim.opt.ignorecase = true 131 | vim.opt.smartcase = true 132 | -- substitution with preview window 133 | vim.opt.inccommand = 'split' 134 | 135 | -- Clipboard 136 | local clipboardstyles = { 137 | system = 'unnamedplus', -- '+' 138 | selection = 'unnamed', -- '*' 139 | } 140 | vim.opt.clipboard = clipboardstyles[opts.clipboard] 141 | 142 | -- Scrolling 143 | vim.opt.scrolloff = opts.scrolloff 144 | vim.opt.sidescrolloff = opts.scrolloff 145 | 146 | -- Folding 147 | vim.opt.foldenable = true 148 | vim.opt.foldlevelstart = 0 149 | vim.opt.foldnestmax = 4 150 | vim.opt.foldmethod = 'marker' 151 | if opts.treesitter_folds then 152 | vim.opt.foldmethod = 'expr' 153 | vim.opt.foldexpr = 'nvim_treesitter#foldexpr()' 154 | vim.opt.foldenable = false 155 | end 156 | 157 | -- window splits 158 | vim.opt.splitright = true 159 | vim.opt.splitbelow = true 160 | 161 | vim.iter(ipairs { 'menu', 'menuone', 'noselect', 'preview' }):each(function(_, option) 162 | if not vim.tbl_contains(vim.opt.completeopt, option) then 163 | vim.opt.completeopt:append(option) 164 | end 165 | end) 166 | 167 | -- by default unload all vim plugins 168 | local loaded_plugins = { 169 | 'zipPlugin', 170 | 'zip', 171 | 'tarPlugin', 172 | 'tar', 173 | 'gzip', 174 | 'tutor_mode_plugin', 175 | 'matchit', 176 | } 177 | vim.iter(ipairs(loaded_plugins)):each(function(_, k) 178 | if opts.load_plugins and opts.load_plugins[k] then 179 | vim.g['loaded_' .. k] = 0 180 | else 181 | vim.g['loaded_' .. k] = 1 182 | end 183 | end) 184 | end 185 | 186 | return M 187 | -------------------------------------------------------------------------------- /lua/ch/config/telescope.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | M.module = { 4 | default = { 5 | opts = { 6 | theme = 'main', 7 | mappings = { 8 | -- resume last telescope search 9 | resume = ';', 10 | find_files = '', 11 | live_grep = 'fr', 12 | simple_find_file = 'ff', 13 | search = 'f/', 14 | symbols = 'fs', 15 | git_files = 'fg', 16 | buffers = "fb", 17 | keymaps = "fk", 18 | mappings = "fm", 19 | help_tags = "fh", 20 | colorscheme = "", 21 | quickfix = 'fq', 22 | }, 23 | config = { 24 | defaults = { 25 | prompt_prefix = '$ ', 26 | file_previewer = require 'telescope.previewers'.vim_buffer_cat.new, 27 | path_display = nil, 28 | file_ignore_patterns = { 'node_modules', '%.git/' }, 29 | borderchars = { 30 | { '─', '│', '─', '│', '┌', '┐', '┘', '└' }, 31 | prompt = { "─", "│", "─", "│", '┌', '┐', "┘", "└" }, 32 | results = { "─", "│", "─", "│", "┌", "┐", "┘", "└" }, 33 | preview = { '─', '│', '─', '│', '┌', '┐', '┘', '└' }, 34 | }, 35 | }, 36 | extensions = { 37 | fzf = { 38 | fuzzy = true, -- false will only do exact matching 39 | override_generic_sorter = true, -- override the generic sorter 40 | override_file_sorter = true, -- override the file sorter 41 | case_mode = 'smart_case', -- or 'ignore_case' or 'respect_case', defaults to 'smart_case' 42 | }, 43 | ['ui-select'] = { 44 | require 'ch.plugin.telescope'.style.dropdown, 45 | }, 46 | }, 47 | pickers = { 48 | find_files = { 49 | disable_devicons = false, 50 | }, 51 | }, 52 | }, 53 | }, 54 | }, 55 | overwrite = {}, 56 | } 57 | 58 | local function use_theme(theme_name) 59 | local themes = { 60 | minimal = { 61 | borderchars = { 62 | { '─', '│', '─', '│', '┌', '┐', '┘', '└' }, 63 | prompt = { ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ' }, 64 | results = { ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ' }, 65 | preview = { ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ' }, 66 | }, 67 | highlights = { 68 | TelescopeTitle = { link = 'FloatTitle' }, 69 | TelescopeNormal = { link = 'Normal' }, 70 | TelescopePromptNormal = { 'none', ch.lib.hl:get('ui', 'bg_accent') }, 71 | TelescopeSelection = { 'none', ch.lib.hl:get('ui', 'current') }, 72 | TelescopeMatching = { link = 'Search' }, 73 | TelescopeBorder = { link = 'FloatBorder' }, 74 | }, 75 | }, 76 | main = { 77 | borderchars = { 78 | { '─', '│', '─', '│', '╭', '╮', '╯', '╰' }, 79 | prompt = { " ", "│", "─", "│", '│', '│', "╯", "╰" }, 80 | results = { "─", "│", " ", "│", "╭", "╮", "│", "│" }, 81 | preview = { '─', '│', '─', '│', '╭', '╮', '╯', '╰' }, 82 | }, 83 | highlights = { 84 | TelescopeTitle = { link = 'FloatTitle' }, 85 | TelescopeNormal = { link = 'Normal' }, 86 | TelescopePromptNormal = { 'none', ch.lib.hl:get('ui', 'bg_accent') }, 87 | TelescopeSelection = { 'none', ch.lib.hl:get('ui', 'current') }, 88 | TelescopeMatching = { link = 'Search' }, 89 | TelescopeBorder = { link = 'FloatBorder' }, 90 | }, 91 | }, 92 | } 93 | 94 | if not theme_name then 95 | return 96 | end 97 | vim.api.nvim_set_hl(0, 'Telescope', {}) 98 | 99 | local theme = themes[theme_name] 100 | if not theme then 101 | ch.log('telescope.setup', 'theme with name `' .. theme_name .. '` not found', 'error') 102 | return 103 | end 104 | 105 | require 'telescope'.setup { 106 | defaults = { 107 | borderchars = theme.borderchars, 108 | }, 109 | } 110 | 111 | require 'ch.plugin.highlight'.apply(theme.highlights) 112 | end 113 | 114 | ---@class chTelescopeOpts 115 | ---@field config TelescopeConfig 116 | ---@field use_fzf? boolean 117 | ---@field theme? 'main'|'minimal' 118 | ---@field mappings { [string]: string } 119 | 120 | ---@param opts chTelescopeOpts 121 | M.setup = function(opts) 122 | require 'telescope'.setup(opts.config) 123 | 124 | if opts.use_fzf then 125 | require 'ch.plugins'.load 'telescope_fzf' 126 | require 'telescope'.load_extension 'fzf' 127 | end 128 | 129 | require 'ch.plugins'.load 'telescope_select' 130 | require 'telescope'.load_extension 'ui-select' 131 | 132 | if opts.theme and type(opts.theme) == 'string' then 133 | ch.lib.autocmd.create { 134 | event = 'ColorScheme', priority = GC.priority.handle.colorscheme.plugin, 135 | desc = 'load telescope hls', 136 | fn = function(_) 137 | use_theme(ch.modules.ch.telescope.opts.theme) 138 | end 139 | } 140 | end 141 | 142 | local pickers = require 'ch.plugin.telescope'.picker 143 | local style = require 'ch.plugin.telescope'.get_style 144 | local builtins = require 'telescope.builtin' 145 | 146 | Keymap.group { 147 | group = 'telescope', 148 | { 'normal', opts.mappings.resume, builtins.resume, 'resume' }, 149 | { 'normal', opts.mappings.find_files, pickers.find_files, 'find files' }, 150 | { 'normal', opts.mappings.live_grep, pickers.grep, 'find string' }, 151 | { 'normal', opts.mappings.simple_find_file, pickers.explorer, 'find file' }, 152 | { 'normal', opts.mappings.symbols, pickers.symbols, 'find symbols' }, 153 | { 'normal', opts.mappings.git_files, pickers.git_files, 'find git file' }, 154 | { 'normal', opts.mappings.buffers, builtins.buffers, 'find buffer' }, 155 | { 'normal', opts.mappings.keymaps, builtins.keymaps, 'find keymap' }, 156 | { 'normal', opts.mappings.help_tags, builtins.help_tags, 'find help tag' }, 157 | { 'normal', opts.mappings.quickfix, builtins.quickfix, 'search in qf list' }, 158 | { 'normal', opts.mappings.colorscheme, 159 | function() builtins.colorscheme(style('dropdown', { prompt_title = 'select colorscheme' })) end, 'find colorscheme' }, 160 | { 'normal', opts.mappings.search, 161 | function() R 'ch.plugin.telescope'.picker.grep_current_file {} end, 'find in file' }, 162 | { 'normal', opts.mappings.mappings, require 'keymaps'.telescope, 'find mapping' }, 163 | } 164 | end 165 | 166 | return M 167 | -------------------------------------------------------------------------------- /lua/ch/config/todo_comments.lua: -------------------------------------------------------------------------------- 1 | return { 2 | module = { 3 | default = { 4 | opts = { 5 | merge_keywords = false, 6 | keywords = { 7 | FIX = { 8 | icon = '! ', -- icon used for the sign, and in search results 9 | color = 'warning', -- can be a hex color, or a named color (see below) 10 | alt = { 'FIXME', 'BUG', 'FIXIT', 'ISSUE' }, -- a set of other keywords that all map to this FIX keywords 11 | -- signs = false, -- configure signs for some keywords individually 12 | }, 13 | TODO = { icon = '/ ', color = 'info' }, 14 | NOTE = { icon = '. ', color = 'note', alt = { 'INFO' } }, 15 | WARN = { icon = '! ', color = 'warning', alt = { 'WARNING', 'XXX' } }, 16 | PERF = { 17 | icon = '* ', 18 | color = 'test', 19 | alt = { 'OPTIM', 'PERFORMANCE', 'OPTIMIZE' }, 20 | }, 21 | TEST = { 22 | icon = '* ', 23 | color = 'test', 24 | alt = { 'TESTING', 'PASSED', 'FAILED' }, 25 | }, 26 | }, 27 | -- list of named colors where we try to extract the guifg from the 28 | -- list of highlight groups or use the hex color if hl not found as a fallback 29 | colors = { 30 | default = { '@label', '#AACCFF' }, 31 | error = { '@comment.error', 'DiagnosticError', 'ErrorMsg', '#FFA89A' }, 32 | warning = { 33 | '@comment.warning', 34 | 'DiagnosticWarn', 35 | 'WarningMsg', 36 | '#FBBB8B', 37 | }, 38 | info = { '@comment.todo', 'DiagnosticInfo', '#FFCCAE' }, 39 | note = { '@comment.note', 'DiagnosticHint', '#FFFFAA' }, 40 | test = { '@comment.todo', '@conditional', 'Identifier', '#AACCFF' }, 41 | }, 42 | highlight = { 43 | pattern = { [[.*<(KEYWORDS)\s*:]] }, 44 | }, 45 | search = { 46 | command = 'rg', 47 | args = { 48 | '--color=never', 49 | '--no-heading', 50 | '--with-filename', 51 | '--line-number', 52 | '--column', 53 | }, 54 | -- regex that will be used to match keywords. 55 | -- don't replace the (KEYWORDS) placeholder 56 | pattern = { [[\b(KEYWORDS):]] }, -- ripgrep regex 57 | -- pattern = [[\b(KEYWORDS)\b]], -- match without the extra colon. You'll likely get false positives 58 | }, 59 | }, 60 | } 61 | }, 62 | setup = function(opts) 63 | ch.log('todo_comments.setup', 'loading todo_comments.') 64 | require('ch.plugins').load 'todo_comments' 65 | 66 | local ok, tc = SR_L 'todo-comments' 67 | if not ok then 68 | return 69 | end 70 | 71 | tc.setup(opts) 72 | end, 73 | } 74 | -------------------------------------------------------------------------------- /lua/ch/config/treesitter.lua: -------------------------------------------------------------------------------- 1 | local ensure = { 'markdown', 'markdown_inline', 'vimdoc' } 2 | 3 | return { 4 | module = { 5 | default = { 6 | opts = { 7 | ---@type TSConfig 8 | config = { 9 | -- Install parsers synchronously (only applied to `ensure_installed`) 10 | sync_install = false, 11 | -- Automatically install missing parsers when entering buffer 12 | auto_install = true, 13 | -- List of parsers to ignore installing (for "all") 14 | ignore_install = {}, 15 | highlight = { 16 | enable = true, 17 | disable = {}, 18 | -- Setting this to true will run `:h syntax` and tree-sitter at the same time. 19 | -- Set this to `true` if you depend on 'syntax' being enabled (like for indentation). 20 | -- Using this option may slow down your editor, and you may see some duplicate highlights. 21 | -- Instead of true it can also be a list of languages 22 | additional_vim_regex_highlighting = false, 23 | }, 24 | textobjects = {}, 25 | indent = { enable = true }, 26 | ensure_installed = {}, -- configure with opts.ensure_installed 27 | modules = {}, 28 | }, 29 | ensure_installed = {}, 30 | }, 31 | }, 32 | }, 33 | setup = function(opts) 34 | ch.log('treesitter.setup', 'loading treesitter.') 35 | require('ch.plugins').load 'treesitter' 36 | 37 | local ok, treesitter = SR_L 'nvim-treesitter' 38 | if not ok then 39 | return 40 | end 41 | treesitter.setup() 42 | 43 | if 44 | type(opts.ensure_installed) == 'string' 45 | and opts.ensure_installed ~= 'all' 46 | then 47 | opts.ensure_installed = { opts.ensure_installed } 48 | end 49 | if type(opts.ensure_installed) == 'table' then 50 | vim.iter(ipairs(ensure)):each(function(_, v) 51 | ---@diagnostic disable-next-line: assign-type-mismatch 52 | opts.ensure_installed[#opts.ensure_installed + 1] = v 53 | end) 54 | end 55 | opts.config.ensure_installed = opts.ensure_installed 56 | require('nvim-treesitter.configs').setup(opts.config) 57 | 58 | keymaps.normal['gm'] = 59 | { vim.show_pos, 'Show TS highlight groups under cursor' } 60 | end, 61 | } 62 | -------------------------------------------------------------------------------- /lua/ch/config/trouble.lua: -------------------------------------------------------------------------------- 1 | return { 2 | module = { 3 | default = { 4 | opts = { 5 | config = { 6 | -- position of the list can be: bottom, top, left, right 7 | position = 'bottom', 8 | -- height of the trouble list when position is top or bottom 9 | height = 10, 10 | -- width of the list when position is left or right 11 | width = 50, 12 | -- use devicons for filenames 13 | icons = false, 14 | -- "workspace_diagnostics", "document_diagnostics", "quickfix", 15 | -- "lsp_references", "loclist" 16 | mode = 'workspace_diagnostics', 17 | -- nil (ALL) or vim.diagnostic.severity.ERROR | WARN | INFO | HINT 18 | severity = nil, 19 | -- icon used for open folds 20 | fold_open = ch.lib.icons.info.fold_open, 21 | -- icon used for closed folds 22 | fold_closed = ch.lib.icons.info.fold_closed, 23 | -- group results by file 24 | group = true, 25 | -- add an extra new line on top of the list 26 | padding = true, 27 | -- cycle item list when reaching beginning or end of list 28 | cycle_results = true, 29 | -- key mappings for actions in the trouble list 30 | -- map to {} to remove a mapping 31 | action_keys = { 32 | -- close the list 33 | close = 'q', 34 | -- cancel the preview and get back to your last window / buffer / cursor 35 | cancel = '', 36 | -- manually refresh 37 | refresh = 'r', 38 | -- jump to the diagnostic or open / close folds 39 | jump = { '', '', '<2-leftmouse>' }, 40 | -- open buffer in new split 41 | open_split = { '' }, 42 | -- open buffer in new vsplit 43 | open_vsplit = { '' }, 44 | -- open buffer in new tab 45 | open_tab = { '' }, 46 | -- jump to the diagnostic and close the list 47 | jump_close = { 'o' }, 48 | -- toggle between "workspace" and "document" diagnostics mode 49 | toggle_mode = 'm', 50 | -- switch "diagnostics" severity filter level to HINT / INFO / WARN / ERROR 51 | switch_severity = 's', 52 | -- toggle auto_preview 53 | toggle_preview = 'P', 54 | -- opens a small popup with the full multiline message 55 | hover = 'K', 56 | -- preview the diagnostic location 57 | preview = 'p', 58 | -- if present, open a URI with more information about the diagnostic error 59 | open_code_href = ch.lib.options:get('lsp', 'mappings', 'goto_definition'), 60 | -- close all folds 61 | close_folds = { 'zM', 'zm' }, 62 | -- open all folds 63 | open_folds = { 'zR', 'zr' }, 64 | -- toggle fold of current file 65 | toggle_fold = { 'zA', 'za' }, 66 | -- previous item 67 | previous = 'k', 68 | -- next item 69 | next = 'j', 70 | -- help menu 71 | help = 'g?', 72 | }, 73 | -- render multi-line messages 74 | multiline = true, 75 | -- add an indent guide below the fold icons 76 | indent_lines = true, 77 | -- window configuration for floating windows. See |nvim_open_win()|. 78 | win_config = { border = 'single' }, 79 | -- automatically open the list when you have diagnostics 80 | auto_open = false, 81 | -- automatically close the list when you have no diagnostics 82 | auto_close = false, 83 | -- automatically preview the location of the diagnostic. to close preview and go back to last window 84 | auto_preview = true, 85 | -- automatically fold a file trouble list at creation 86 | auto_fold = false, 87 | -- for the given modes, automatically jump if there is only a single result 88 | auto_jump = { 'lsp_definitions' }, 89 | -- for the given modes, include the declaration of the current symbol in the results 90 | include_declaration = { 91 | 'lsp_references', 92 | 'lsp_implementations', 93 | 'lsp_definitions', 94 | }, 95 | -- icons / text used for a diagnostic 96 | signs = { 97 | error = ch.lib.icons.diagnostic.error, 98 | warning = ch.lib.icons.diagnostic.warn, 99 | hint = ch.lib.icons.diagnostic.hint, 100 | information = ch.lib.icons.diagnostic.info, 101 | other = ch.lib.icons.diagnostic.info, 102 | }, 103 | -- enabling this will use the signs defined in your lsp client 104 | use_diagnostic_signs = true, 105 | }, 106 | }, 107 | }, 108 | }, 109 | setup = function(opts) 110 | ch.log('trouble.setup', 'loading trouble.') 111 | require('ch.plugins').load 'trouble' 112 | 113 | local ok, trouble = SR_L 'trouble' 114 | if not ok then 115 | return 116 | end 117 | 118 | opts.config.icons = ch.config.ui.devicons 119 | trouble.setup(opts.config) 120 | 121 | ch.lib.hl.apply { 122 | TroubleTextWarning = { link = '@text' }, 123 | TroubleLocation = { link = 'NonText' }, 124 | } 125 | end, 126 | } 127 | -------------------------------------------------------------------------------- /lua/ch/config/ui.lua: -------------------------------------------------------------------------------- 1 | return { 2 | module = { 3 | default = { 4 | opts = { 5 | cursor = { 6 | enabled = true, 7 | }, 8 | statusline = { 9 | enabled = true, 10 | }, 11 | bufferline = { 12 | enabled = true, 13 | -- add numbers to each tab in bufferline 14 | show_numbers = true, 15 | -- callback fn to modify bufferline modules 16 | overriden_modules = nil, 17 | }, 18 | ---@class ch.types.ui.term.config 19 | terminal = { 20 | enabled = true, 21 | mappings = { 22 | open_float = 'tt', 23 | open_vertical = 'tv', 24 | open_horizontal = 'th', 25 | }, 26 | ui = { 27 | float = { 28 | relative = 'editor', 29 | -- row = 0.3, 30 | -- col = 0.25, 31 | width = 0.64, 32 | height = 0.64, 33 | }, 34 | horizontal = { location = 'rightbelow', split_ratio = 0.3 }, 35 | vertical = { location = 'rightbelow', split_ratio = 0.5 }, 36 | }, 37 | behavior = { 38 | autoclose_on_quit = { 39 | enabled = false, 40 | confirm = true, 41 | }, 42 | close_on_exit = true, 43 | auto_insert = true, 44 | }, 45 | terminals = { 46 | list = {}, 47 | }, 48 | }, 49 | input = { 50 | -- Set to false to disable the vim.ui.input implementation 51 | enabled = true, 52 | 53 | -- Default prompt string 54 | default_prompt = '', 55 | 56 | -- Can be 'left', 'right', or 'center' 57 | title_pos = 'left', 58 | 59 | -- When true, will close the modal 60 | insert_only = true, 61 | 62 | -- When true, input will start in insert mode. 63 | start_in_insert = true, 64 | 65 | -- These are passed to nvim_open_win 66 | border = 'rounded', 67 | -- 'editor' and 'win' will default to being centered 68 | relative = 'cursor', 69 | 70 | -- These can be integers or a float between 0 and 1 (e.g. 0.4 for 40%) 71 | prefer_width = 40, 72 | width = nil, 73 | -- min_width and max_width can be a list of mixed types. 74 | -- min_width = {20, 0.2} means 'the greater of 20 columns or 20% of total' 75 | max_width = { 140, 0.9 }, 76 | min_width = { 20, 0.2 }, 77 | 78 | buf_options = {}, 79 | win_options = { 80 | -- Disable line wrapping 81 | wrap = false, 82 | -- Indicator for when text exceeds window 83 | list = true, 84 | listchars = 'precedes:…,extends:…', 85 | -- Increase this for more context when text scrolls off the window 86 | sidescrolloff = 0, 87 | }, 88 | 89 | -- Set to `false` to disable 90 | mappings = { 91 | n = { 92 | close = '', 93 | confirm = '', 94 | }, 95 | i = { 96 | close = '', 97 | confirm = '', 98 | }, 99 | }, 100 | 101 | override = function(conf) 102 | -- This is the config that will be passed to nvim_open_win. 103 | -- Change values here to customize the layout 104 | return conf 105 | end, 106 | }, 107 | }, 108 | }, 109 | }, 110 | setup = function(opts) 111 | ch.log('ui.setup', 'set up ui') 112 | 113 | if opts.input.enabled then 114 | ---@diagnostic disable-next-line: duplicate-set-field 115 | vim.ui.input = function(...) 116 | require 'ch.ui.input' (...) 117 | end 118 | end 119 | 120 | if opts.cursor.enabled then 121 | require 'ch.ui.cursor'.setup() 122 | end 123 | 124 | if opts.statusline.enabled then 125 | ---@diagnostic disable-next-line: inject-field 126 | ch.modules.ch.lualine.enabled = false 127 | 128 | require 'ch.ui.statusline.hl'.setup_highlights() 129 | ---@class ch.types.global 130 | ---@field statusline fun() 131 | _G.ch.statusline = function() 132 | return R 'ch.ui.statusline'.run() 133 | end 134 | vim.opt.statusline = "%!v:lua.ch.statusline()" 135 | end 136 | 137 | if opts.bufferline.enabled then 138 | require 'ch.ui.bufferline.load'.setup() 139 | 140 | ---@class ch.types.global 141 | ---@field bufferline fun() 142 | _G.ch.bufferline = function() 143 | vim.opt.showtabline = 2 144 | return R 'ch.ui.bufferline.modules'.run() 145 | end 146 | vim.opt.tabline = "%!v:lua.ch.bufferline()" 147 | end 148 | 149 | if opts.terminal.enabled then 150 | require 'ch.ui.term'.setup(opts.terminal) 151 | end 152 | end, 153 | } 154 | -------------------------------------------------------------------------------- /lua/ch/config/whichkey.lua: -------------------------------------------------------------------------------- 1 | return { 2 | module = { 3 | default = { 4 | opts = { 5 | config = { 6 | replace = { 7 | -- key labels are imported from ui->key_labels 8 | key = nil, 9 | desc = { 10 | { "%(?(.*)%)?", "%1" }, 11 | { "^%+", "" }, 12 | { "<[cC]md>", "" }, 13 | { "<[cC][rR]>", "" }, 14 | { "<[sS]ilent>", "" }, 15 | { "^lua%s+", "" }, 16 | { "^call%s+", "" }, 17 | { "^:%s*", "" }, 18 | }, 19 | } 20 | }, 21 | }, 22 | }, 23 | }, 24 | setup = function(opts) 25 | ch.log('whichkey.setup', 'loading whichkey.') 26 | require('ch.plugins').load 'whichkey' 27 | 28 | local ok, which = SR_L 'which-key' 29 | if not ok then 30 | return 31 | end 32 | 33 | if not opts.config.replace then 34 | local default_key_labels = { 35 | function(key) 36 | return require("which-key.view").format(key) 37 | end, 38 | } 39 | vim.list_extend(default_key_labels, ch.config.ui.key_labels) 40 | 41 | opts.config.replace.key = default_key_labels 42 | end 43 | which.setup(opts.config) 44 | 45 | which.add { 46 | { '.', group = 'toggle' }, 47 | { ',', group = 'edit' }, 48 | { 'f', group = 'find' }, 49 | { 's', group = 'show' }, 50 | { 'g', group = 'go' }, 51 | } 52 | end, 53 | } 54 | -------------------------------------------------------------------------------- /lua/ch/init.lua: -------------------------------------------------------------------------------- 1 | ---@mod ch 2 | 3 | if vim.fn.has("nvim-0.10.0") ~= 1 then 4 | error("chaivim requires Neovim >= 0.10.0") 5 | end 6 | 7 | ---@alias ch.types.module.main 'ch'|'config'|'custom'|string 8 | 9 | ---@class ch.types.module.spec 10 | ---@field name ch.types.module.name 11 | ---@field event string 12 | ---@field opts table|string|nil 13 | ---@field reload boolean 14 | 15 | ---@alias ch.types.module.name 'options'|'highlights'|'base'|'maps'|'plugins'|string 16 | ---@alias ch.types.module.table { [ch.types.module.main]: ch.types.module.spec[] } 17 | 18 | ---@class ch.config 19 | ---@field log_level integer 20 | ---@field ui ch.config.ui 21 | ---@field config_module string 22 | ---@field modules ch.types.module.table 23 | ---@field inputs LazyPluginSpec[] 24 | ---@field plugins string 25 | 26 | ---@class ch.config.ui 27 | ---@field colorscheme string 28 | ---@field transparent_background boolean 29 | ---@field separator_style 'slant'|'round'|'block'|'arrow' 30 | ---@field float_border vim.api.keyset.win_config.border 31 | ---@field devicons boolean 32 | ---@field theme_config ch.config.ui.theme_config 33 | ---@field key_labels table 34 | 35 | ---@class ch.config.ui.theme_config 36 | ---@field keyword table<'italic', boolean> 37 | ---@field types table<'italic', boolean> 38 | ---@field comment table<'italic', boolean> 39 | ---@field search table<'reverse', boolean> 40 | ---@field inc_search table<'reverse', boolean> 41 | 42 | ---@class ch.types.global 43 | ---@field config ch.config 44 | ---@field group_id integer 45 | ---@field path ch.types.global.path 46 | ---@field loaded boolean 47 | ---@field modules InternalModules parsed module configs 48 | ---@field lib ch.types.lib 49 | 50 | ---@class ch.types.lib 51 | ---@field icons chIcons 52 | ---@field hl ch.types.lib.hl.table 53 | --- ... `ch.lib` 54 | 55 | ---@class ch.types.global.path 56 | ---@field root string 57 | ---@field ch string 58 | ---@field lazy string 59 | ---@field log string 60 | 61 | ---@alias InternalModules { [ch.types.module.main]: { [ch.types.module.name]: ch.types.module.spec } } 62 | 63 | local M = {} 64 | 65 | ---@type ch.types.global 66 | ---@diagnostic disable: missing-fields 67 | _G.ch = _G.ch or {} 68 | 69 | ---@type ch.config 70 | _G.ch.config = require 'ch.config'.setup(_G.ch.config or {}) 71 | 72 | local root_path = vim.fn.stdpath("data") .. "/ch" 73 | _G.ch.path = { 74 | root = root_path, 75 | log = ('%s/ch_log.txt'):format(vim.fn.stdpath("data")), 76 | } 77 | _G.ch.path.ch = _G.ch.path.root .. "/chai" 78 | 79 | _G.ch.modules = _G.ch.modules or {} 80 | 81 | require 'ch.load' 82 | require 'ch.log' 83 | 84 | local parts = require 'ch.parts' 85 | 86 | ---@param ... any 87 | function M.setup(...) 88 | local args = { ... } 89 | if #args == 0 then 90 | ch.log('ch.setup', 'not enough arguments provided', 'error') 91 | return 92 | end 93 | local config = args[1] 94 | local modules = args[2] or false 95 | if type(config) == 'string' then 96 | local status, opts = SR(config) 97 | if not status or type(opts) ~= 'table' then 98 | ch.log('ch.setup', 'config module ' .. config .. ' was not found', 'error') 99 | return 100 | end 101 | return M.setup(opts, modules) 102 | end 103 | if modules and type(modules) == 'string' then 104 | local import_mod = modules 105 | local status, result = SR(import_mod) 106 | if not status or type(result) ~= 'table' then 107 | ch.log('ch.setup', 'modules from module ' .. import_mod .. ' were not found', 'error') 108 | return 109 | end 110 | modules = result 111 | end 112 | CONFIG_MODULE = config.config_module or 'custom' 113 | 114 | config.config_module = CONFIG_MODULE 115 | config.modules = modules or config.modules 116 | 117 | require 'ch.config'.setup(config) 118 | 119 | -- preload keymaps module 120 | parts.preload {} 121 | 122 | M.load() 123 | end 124 | 125 | --- load config 126 | function M.load() 127 | if ch.loaded then 128 | M.reload() 129 | return 130 | end 131 | 132 | ch.log('ch.startup', 'loading config') 133 | 134 | if vim.loader and vim.fn.has "nvim-0.9.1" == 1 then vim.loader.enable() end 135 | ch.group_id = vim.api.nvim_create_augroup('ch:' .. CONFIG_MODULE, {}) 136 | require 'ch.load.autocmds'.setup { 137 | group_id = ch.group_id, 138 | } 139 | 140 | parts.load_modules {} 141 | 142 | parts.colorscheme {} 143 | 144 | parts.load_transparency {} 145 | 146 | parts.platform {} 147 | 148 | ch.loaded = true 149 | end 150 | 151 | function M.reload() 152 | ch.log('ch.reload', 'reloading config') 153 | 154 | vim.api.nvim_del_augroup_by_id(ch.group_id) 155 | ch.group_id = vim.api.nvim_create_augroup("config:" .. CONFIG_MODULE, {}) 156 | require 'ch.load.autocmds'.setup { 157 | group_id = ch.group_id, 158 | } 159 | 160 | parts.load_modules {} 161 | 162 | parts.colorscheme {} 163 | 164 | parts.load_transparency {} 165 | 166 | parts.platform {} 167 | 168 | vim.api.nvim_exec_autocmds('ColorScheme', {}) 169 | end 170 | 171 | return M 172 | -------------------------------------------------------------------------------- /lua/ch/lazy/plugins.lua: -------------------------------------------------------------------------------- 1 | return { 2 | { 'comfysage/chaivim', dir = ch.path.ch }, 3 | { 'folke/lazy.nvim' }, 4 | { name = 'keymaps', 'comfysage/keymaps.nvim' }, 5 | { name = 'yosu', 'comfysage/yosu.nvim' }, 6 | { 'comfysage/base46', 7 | cond = function() return ch.config.ui.base46 ~= nil and ch.config.ui.colorscheme == 'base46' end }, 8 | { name = 'plenary', 'nvim-lua/plenary.nvim' }, 9 | { name = 'telescope', 'nvim-telescope/telescope.nvim', 10 | dependencies = { 11 | { 'nvim-telescope/telescope-fzf-native.nvim', build = 'make' }, 12 | { 'nvim-telescope/telescope-ui-select.nvim' }, 13 | }, 14 | }, 15 | { name = 'telescope_fzf', 'nvim-telescope/telescope-fzf-native.nvim', build = 'make' }, 16 | { name = 'telescope_select', 'nvim-telescope/telescope-ui-select.nvim' }, 17 | { name = 'lualine', 'nvim-lualine/lualine.nvim', 18 | cond = function() return ch.lib.options:enabled 'lualine' end }, 19 | { 'comfysage/evergarden' }, 20 | { name = 'treesitter', 'nvim-treesitter/nvim-treesitter', 21 | version = false, -- last release is way too old and doesn't work on Windows 22 | build = ":TSUpdate", 23 | }, 24 | { 25 | 'hrsh7th/nvim-cmp', 26 | 'hrsh7th/cmp-nvim-lsp', 27 | 'hrsh7th/cmp-nvim-lua', 28 | { 'hrsh7th/cmp-buffer', 'hrsh7th/cmp-path', 'hrsh7th/cmp-cmdline' }, 29 | }, 30 | { name = 'lspconfig', 'neovim/nvim-lspconfig' }, 31 | { name = 'luasnip', 'L3MON4D3/LuaSnip', 32 | cond = function() return ch.lib.options:enabled 'luasnip' end, 33 | version = 'v2.*', 34 | dependencies = { 35 | 'saadparwaiz1/cmp_luasnip', 36 | 'rafamadriz/friendly-snippets', 37 | }, 38 | }, 39 | { name = 'null', 'nvimtools/none-ls.nvim' }, 40 | { name = 'mini', 'echasnovski/mini.nvim' }, 41 | { name = 'gitsigns', 'lewis6991/gitsigns.nvim' }, 42 | { name = 'whichkey', 'folke/which-key.nvim', 43 | cond = function() return ch.lib.options:enabled 'whichkey' end }, 44 | { name = 'todo_comments', 'folke/todo-comments.nvim', 45 | dependencies = 'nvim-lua/plenary.nvim', 46 | cond = function() return ch.lib.options:enabled 'todo_comments' end }, 47 | { name = 'trouble', 'folke/trouble.nvim', 48 | cond = function() return ch.lib.options:enabled 'trouble' end }, 49 | { name = 'incline', 'b0o/incline.nvim', 50 | cond = function() return ch.lib.options:enabled 'incline' end }, 51 | { name = 'indent', 'lukas-reineke/indent-blankline.nvim', 52 | cond = function() return ch.lib.options:enabled 'indent' end }, 53 | { name = 'fidget', 'j-hui/fidget.nvim', 54 | cond = function() return ch.lib.options:enabled 'fidget' end }, 55 | { 'nvim-tree/nvim-web-devicons', 56 | cond = function() return ch.config.ui.devicons end }, 57 | { import = ch.config.plugins }, 58 | } 59 | -------------------------------------------------------------------------------- /lua/ch/lib/autocmd.lua: -------------------------------------------------------------------------------- 1 | ---@class ch.types.lib.autocmd 2 | ---@field create _ 3 | ch.lib.autocmd = {} 4 | ch.lib.autocmd.create = require 'ch.load.handle'.create 5 | -------------------------------------------------------------------------------- /lua/ch/lib/color.lua: -------------------------------------------------------------------------------- 1 | ch.lib.color = {} 2 | 3 | ---@alias ch.types.lib.color.Color__internal integer|'none' 4 | ---@alias ch.types.lib.color.Color__rgb { r: integer, g: integer, b: integer } 5 | ---@alias ch.types.lib.color.Color ch.types.lib.color.Color__rgb 6 | ---@alias ch.types.lib.color.Color__hsl { hue: integer, sat: integer, lum: integer } 7 | 8 | ---@class ch.types.lib.color 9 | ---@field rgb fun(props: ch.types.lib.color.Color__rgb): integer 10 | function ch.lib.color.rgb(props) 11 | if not props or not props.r or not props.g or not props.b then return 0 end 12 | return ch.lib.math.components_to_hex { [1] = props.r, [2] = props.g, [3] = props.b } 13 | end 14 | 15 | ---@class ch.types.lib.color 16 | ---@field mix fun(ratio, props): integer 17 | ---@param ratio number Ratio of color2 mixed into color1; 1.0 means only color2 18 | ---@param props tuple 19 | function ch.lib.color.mix(ratio, props) 20 | if not props or not props[1] or not props[2] then return 0 end 21 | local color1 = ch.lib.math.hex_to_rgb(props[1]) 22 | local color2 = ch.lib.math.hex_to_rgb(props[2]) 23 | if not props or not props[1] or not props[2] then return 0 end 24 | 25 | local mix = { r = 0, g = 0, b = 0 } 26 | for slider, _ in pairs(mix) do 27 | mix[slider] = math.floor((1 - ratio) * color1[slider] + ratio * color2[slider]) 28 | end 29 | 30 | return ch.lib.color.rgb(mix) 31 | end 32 | 33 | ---@class ch.types.lib.color 34 | ---@field hsl_mix fun(ratio, props): integer 35 | ---@param ratio { hue?: number, sat?: number, lum?: number } Ratio of color2 mixed into color1; 1.0 means only color2 36 | ---@param props tuple 37 | function ch.lib.color.hsl_mix(ratio, props) 38 | if not props or not props[1] or not props[2] then return 0 end 39 | local color1__rgb = ch.lib.math.hex_to_rgb(props[1]) 40 | local color2__rgb = ch.lib.math.hex_to_rgb(props[2]) 41 | local color1 = ch.lib.color.rgb_to_hsl(color1__rgb) 42 | local color2 = ch.lib.color.rgb_to_hsl(color2__rgb) 43 | 44 | local mix = { hue = 0, lum = 0, sat = 0 } 45 | for slider, _ in pairs(mix) do 46 | local r = ratio[slider] 47 | if ratio[slider] then 48 | mix[slider] = (1 - r) * color1[slider] + r * color2[slider] 49 | else 50 | mix[slider] = color1[slider] 51 | end 52 | end 53 | 54 | local mix__rgb = ch.lib.color.hsl_to_rgb(mix) 55 | return ch.lib.color.rgb(mix__rgb) 56 | end 57 | 58 | ---@class ch.types.lib.color 59 | ---@field rgb_to_hsl fun(props: ch.types.lib.color.Color__rgb): ch.types.lib.color.Color__hsl 60 | function ch.lib.color.rgb_to_hsl(props) 61 | local hsl = { hue = 0, sat = 0, lum = 0 } 62 | -- make r, g, and b fractions of 1 63 | local r = props.r / 255; 64 | local g = props.g / 255; 65 | local b = props.b / 255; 66 | 67 | -- find greatest and smallest channel values 68 | local cmin = math.min(r, g, b) 69 | local cmax = math.max(r, g, b) 70 | local delta = cmax - cmin 71 | local h = 0 72 | local s = 0 73 | local l = 0 74 | 75 | -- calculate hue 76 | -- no difference 77 | if (delta == 0) then 78 | h = 0 79 | -- red is max 80 | elseif (cmax == r) then 81 | h = ((g - b) / delta) % 6 82 | -- green is max 83 | elseif (cmax == g) then 84 | h = (b - r) / delta + 2 85 | -- blue is max 86 | else 87 | h = (r - g) / delta + 4 88 | end 89 | 90 | h = math.floor(h * 60); 91 | 92 | -- make negative hues positive behind 360° 93 | if (h < 0) then 94 | h = h + 360 95 | end 96 | 97 | -- calculate lightness 98 | l = (cmax + cmin) / 2 99 | 100 | -- calculate saturation 101 | s = delta == 0 and 0 or delta / (1 - math.abs(2 * l - 1)) 102 | 103 | hsl = { 104 | hue = h, -- as x 105 | sat = s, -- as 0.x 106 | lum = l, -- as 0.x 107 | } 108 | 109 | return hsl 110 | end 111 | 112 | ---@class ch.types.lib.color 113 | ---@field hsl_to_rgb fun(props: ch.types.lib.color.Color__hsl): ch.types.lib.color.Color__rgb 114 | function ch.lib.color.hsl_to_rgb(props) 115 | local color = { r = 0, g = 0, b = 0 } 116 | 117 | -- all as fractions 118 | local h = props.hue / 360 119 | local s = props.sat 120 | local l = props.lum 121 | local r = 0 122 | local g = 0 123 | local b = 0 124 | 125 | if s == 0 then 126 | -- achromatic 127 | r = l 128 | g = l 129 | b = l 130 | else 131 | local q = l < 0.5 and l * (1 + s) or l + s - l * s 132 | local p = 2 * l - q 133 | r = ch.lib.color.hue_to_rgb(p, q, h + 1 / 3) 134 | g = ch.lib.color.hue_to_rgb(p, q, h) 135 | b = ch.lib.color.hue_to_rgb(p, q, h - 1 / 3) 136 | end 137 | 138 | color = { 139 | r = math.ceil(r * 255), 140 | g = math.ceil(g * 255), 141 | b = math.ceil(b * 255), 142 | } 143 | return color 144 | end 145 | 146 | ---@class ch.types.lib.color 147 | ---@field hue_to_rgb fun(p, q, t): number 148 | function ch.lib.color.hue_to_rgb(p, q, t) 149 | if t < 0 then t = t + 1 end 150 | if t > 1 then t = t - 1 end 151 | if (t < 1 / 6) then 152 | return p + (q - p) * 6 * t 153 | end 154 | if (t < 1 / 2) then 155 | return q 156 | end 157 | if (t < 2 / 3) then 158 | return p + (q - p) * (2 / 3 - t) * 6 159 | end 160 | return p 161 | end 162 | 163 | ---@class ch.types.lib.color 164 | ---@field color_overlay fun(ratio, props): integer 165 | ---@param ratio number Ratio of color2 overlayed ontop of color1; 1.0 means only color2 166 | ---@param props tuple 167 | function ch.lib.color.color_overlay(ratio, props) 168 | if not props[1] or not props[2] then return 0 end 169 | local f = ch.lib.math.hex_to_rgb(props[1]) 170 | local t = ch.lib.math.hex_to_rgb(props[2]) 171 | 172 | if (ratio < 0) then 173 | ratio = ratio * -1 174 | end 175 | local p = 1 - ratio; 176 | 177 | local r = math.ceil(((p * f.r ^ 2) + (ratio * t.r ^ 2)) ^ 0.5) 178 | local g = math.ceil(((p * f.g ^ 2) + (ratio * t.g ^ 2)) ^ 0.5) 179 | local b = math.ceil(((p * f.b ^ 2) + (ratio * t.b ^ 2)) ^ 0.5) 180 | 181 | local color = ch.lib.color.rgb { r = r, g = g, b = b } 182 | 183 | return color 184 | end 185 | -------------------------------------------------------------------------------- /lua/ch/lib/event.lua: -------------------------------------------------------------------------------- 1 | ---@class ch.types.lib.event 2 | ---@field trigger fun(ev: string) 3 | ch.lib.event = {} 4 | ch.lib.event.trigger = require 'ch.load.handle'.start 5 | -------------------------------------------------------------------------------- /lua/ch/lib/fmt.lua: -------------------------------------------------------------------------------- 1 | ---@class ch.types.lib.fmt 2 | ch.lib.fmt = ch.lib.fmt or {} 3 | 4 | ---@class ch.types.lib.fmt 5 | ---@field space fun(str, n, sep): string 6 | ---@param str string 7 | ---@param n? integer 8 | ---@param sep? string 9 | ch.lib.fmt.space = function(str, n, sep) 10 | if not n then n = 1 end 11 | if not sep then sep = ' ' end 12 | return string.rep(sep, n) .. str .. string.rep(sep, n) 13 | end 14 | -------------------------------------------------------------------------------- /lua/ch/lib/hl.lua: -------------------------------------------------------------------------------- 1 | ---@class ch.types.lib.highlight 2 | ---@field apply fun(hls: HLGroups ) 3 | ch.lib.hl = ch.lib.hl or {} 4 | ch.lib.hl.apply = require 'ch.plugin.highlight'.apply 5 | 6 | ---@class ch.types.lib.highlight 7 | ---@field get_hl fun(props: { name: string }): ch.types.hl.highlight 8 | ch.lib.hl.get_hl = function(props) 9 | return vim.api.nvim_get_hl(0, props) 10 | end 11 | 12 | ---@class ch.types.lib.highlight 13 | ---@field get fun(self, ...: string): integer 14 | function ch.lib.hl:get(...) 15 | return vim.tbl_get(self.__value, ...) 16 | end 17 | -------------------------------------------------------------------------------- /lua/ch/lib/init.lua: -------------------------------------------------------------------------------- 1 | ---@alias tuple { [1]: T, [2]: T } 2 | 3 | -- statically allocated instead of dynamically by function wrapping 4 | ---@class ch.types.lib 5 | ---@field autocmd ch.types.lib.autocmd 6 | ---@field event ch.types.lib.event 7 | ---@field keymaps ch.types.lib.keymaps 8 | ---@field hl ch.types.lib.highlight 9 | ---@field options ch.types.lib.options 10 | ---@field fmt ch.types.lib.fmt 11 | ---@field color ch.types.lib.color 12 | ---@field math ch.types.lib.math 13 | 14 | return { 15 | setup = function() 16 | ---@diagnostic disable-next-line: missing-fields 17 | ch.lib = ch.lib or {} 18 | require 'ch.lib.preload' 19 | require 'ch.lib.autocmd' 20 | require 'ch.lib.event' 21 | require 'ch.lib.keymaps' 22 | require 'ch.lib.hl' 23 | require 'ch.lib.fmt' 24 | 25 | ---@class ch.types.lib 26 | ---@field get fun(field: string, ...: string): any 27 | function ch.lib:get(field, ...) 28 | local query_fn = { 29 | ---@type fun(...: string): Keymap 30 | keymaps = function(...) return vim.tbl_get(keymaps, 'prototype', ...) end 31 | } 32 | local fn = query_fn[field] 33 | if fn and type(fn) == 'function' then 34 | return fn(...) 35 | end 36 | if ch.lib[field] and ch.lib[field].get then 37 | return ch.lib[field]:get(...) 38 | end 39 | return nil 40 | end 41 | 42 | setmetatable(ch.lib, 43 | { 44 | __call = ch.lib.get, 45 | }) 46 | end, 47 | } 48 | -------------------------------------------------------------------------------- /lua/ch/lib/keymaps.lua: -------------------------------------------------------------------------------- 1 | ---@class ch.types.lib.keymaps 2 | ---@field fmt fun(lhs: string): string 3 | ch.lib.keymaps = {} 4 | ch.lib.keymaps.fmt = require 'ch.plugin.keymaps'.fmt 5 | 6 | ---@class ch.types.lib.keymaps 7 | ---@field open_qf_list function 8 | ch.lib.keymaps.open_qf_list = function() 9 | if ch.lib.options:enabled 'trouble' then 10 | require("trouble").toggle("quickfix") 11 | else 12 | vim.cmd.copen() 13 | end 14 | end 15 | 16 | ---@class ch.types.lib.keymaps 17 | ---@field register_qf_loader fun(key: string, cb: function, opts: { handle_open?: boolean }) 18 | --- *opts* 19 | --- - *handle_open*: if true cb is wrapped in a fn that opens the qf list 20 | ch.lib.keymaps.register_qf_loader = function(key, cb, opts) 21 | if ch.modules.ch.keymaps.opts.qf_loaders[key] then return end 22 | if opts.handle_open then 23 | local _cb = vim.schedule_wrap(cb) 24 | cb = function() 25 | _cb() 26 | ch.lib.keymaps.open_qf_list() 27 | end 28 | end 29 | ch.modules.ch.keymaps.opts.qf_loaders[key] = cb 30 | end 31 | -------------------------------------------------------------------------------- /lua/ch/lib/math.lua: -------------------------------------------------------------------------------- 1 | ch.lib.math = {} 2 | 3 | ---@class ch.types.lib.math 4 | ---@field parse_hex_str fun(props: string): integer 5 | function ch.lib.math.parse_hex_str(props) 6 | if not props or type(props) ~= 'string' then return end 7 | local n = string.sub(props, 2) 8 | return tonumber(n, 16) 9 | end 10 | 11 | ---@class ch.types.lib.math 12 | ---@field components_to_hex fun(props: Array): integer 13 | function ch.lib.math.components_to_hex(props) 14 | return vim.iter(ipairs(props)):fold(0, function(n, i, v) 15 | local m = #props - i 16 | return n + ((256 ^ m) * v) 17 | end) 18 | end 19 | 20 | ---@class ch.types.lib.math 21 | ---@field hex_to_components fun(n: integer, v: integer): Array 22 | function ch.lib.math.hex_to_components(n, v) 23 | local _components = {} 24 | local components = {} 25 | local _n = v 26 | 27 | for i = 1, n, 1 do 28 | _components[i] = (256 ^ (n - i)) 29 | components[i] = math.floor(_n / _components[i]) 30 | components[i] = components[i] > 0 and components[i] or 0 31 | _n = _n - components[i] * _components[i] 32 | end 33 | 34 | return components 35 | end 36 | 37 | ---@class ch.types.lib.math 38 | ---@field hex_to_rgb fun(n): ch.types.lib.color.Color 39 | ---@param n ch.types.lib.color.Color__internal 40 | function ch.lib.math.hex_to_rgb(n) 41 | if n == 'none' then 42 | n = 0 43 | end 44 | ---@diagnostic disable-next-line: param-type-mismatch 45 | local components = ch.lib.math.hex_to_components(3, n) 46 | return { r = components[1], g = components[2], b = components[3] } 47 | end 48 | 49 | ---@class ch.types.lib.math 50 | ---@field avg fun(props: integer[]): integer 51 | function ch.lib.math.avg(props) 52 | local sum = vim.iter(ipairs(props)):fold(0, function(sum, _, v) 53 | return sum + v 54 | end) 55 | return sum / #props 56 | end 57 | -------------------------------------------------------------------------------- /lua/ch/lib/options.lua: -------------------------------------------------------------------------------- 1 | ---@class ch.types.lib.options 2 | ch.lib.options = ch.lib.options or {} 3 | ch.lib.options.__value = ch.lib.options.__value or {} 4 | 5 | ---@class ch.types.lib.options 6 | ---@field get fun(self, name: string, ...: string): any 7 | function ch.lib.options:get(name, ...) 8 | if not name then return end 9 | local query = { ... } 10 | if #query == 0 then return end 11 | local module = self.__value[name] 12 | if module and type(module.opts) == 'table' then 13 | ---@diagnostic disable-next-line: param-type-mismatch 14 | return vim.tbl_get(module.opts, ...) 15 | end 16 | end 17 | 18 | ---@class ch.types.lib.options 19 | ---@field enabled fun(self, name: string): any 20 | function ch.lib.options:enabled(name) 21 | if not name then return end 22 | local module = self.__value[name] 23 | if module then 24 | ---@diagnostic disable-next-line: undefined-field 25 | if module.enabled == false then 26 | return false 27 | end 28 | return true 29 | end 30 | return false 31 | end 32 | -------------------------------------------------------------------------------- /lua/ch/lib/preload.lua: -------------------------------------------------------------------------------- 1 | require 'ch.lib.options' 2 | require 'ch.plugin.icons'.setup() 3 | require 'ch.lib.math' 4 | require 'ch.lib.color' 5 | require 'ch.plugin.hl'.setup() 6 | -------------------------------------------------------------------------------- /lua/ch/load/async.lua: -------------------------------------------------------------------------------- 1 | -- create async thread 2 | ---@param fn function callback function in async thread 3 | ---@param ... any arguments to callback function 4 | ---@return userdata 5 | _G.async_wrap = function(fn, ...) 6 | return vim.uv.new_thread(fn, ...) 7 | end 8 | -------------------------------------------------------------------------------- /lua/ch/load/autocmds.lua: -------------------------------------------------------------------------------- 1 | local parts = require 'ch.parts' 2 | 3 | return { 4 | setup = function(opts) 5 | vim.api.nvim_create_autocmd('BufWritePost', { 6 | -- command = 'source ', 7 | callback = function(props) 8 | vim.notify('reloading module: ' .. CONFIG_MODULE .. '\t' .. props.file, vim.log.levels.INFO) 9 | local status, config = SR(CONFIG_MODULE) 10 | if not status then 11 | require 'ch.utils'.log('autocmds.config_reload', 'config module ' .. CONFIG_MODULE .. ' was not found', 'error') 12 | return 13 | end 14 | if type(config) == 'table' then 15 | require 'ch'.setup(config) 16 | end 17 | return 18 | end, 19 | group = opts.group_id, 20 | pattern = 'lua/' .. CONFIG_MODULE .. '/init.lua', 21 | desc = 'config:reload:' .. CONFIG_MODULE, 22 | }) 23 | end, 24 | ---@param spec ch.types.module.spec 25 | create_reload = function (module, spec) 26 | local file_name = string.gsub(module, '[.]', '/') 27 | vim.api.nvim_create_autocmd('BufWritePost', { 28 | callback = function (props) 29 | vim.notify('reloading module: ' .. module .. '\t' .. props.file, vim.log.levels.INFO) 30 | package.loaded[module] = nil 31 | parts.load(module, spec) 32 | end, 33 | group = ch.group_id, 34 | pattern = 'lua/' .. file_name .. '.lua', 35 | desc = 'config:reload:' .. module, 36 | }) 37 | end, 38 | } 39 | -------------------------------------------------------------------------------- /lua/ch/load/constants.lua: -------------------------------------------------------------------------------- 1 | ---@class ch.types.constants 2 | GC = {} 3 | 4 | ---@class ch.types.constants.priority 5 | ---@field signs { ['lsp'|'git']: integer } 6 | ---@field handle ch.types.constants.priority.handle 7 | 8 | ---@class ch.types.constants.priority.handle 9 | ---@field colorscheme table<'hl'|'theme'|'plugin'|'fix'|'transparency', integer> 10 | 11 | ---@class ch.types.constants 12 | ---@field priority ch.types.constants.priority 13 | GC.priority = { 14 | signs = { 15 | -- starts at 16 to provide room for other plugins 16 | lsp = 16, 17 | git = 18, 18 | }, 19 | handle = { 20 | colorscheme = { 21 | hl = 2, 22 | theme = 4, 23 | plugin = 6, 24 | fix = 26, 25 | transparency = 100, 26 | }, 27 | }, 28 | } 29 | 30 | ---@class ch.types.constants 31 | ---@field diagnostic_signs table 32 | GC.get_diagnostic_signs = function() 33 | return { 34 | [vim.diagnostic.severity.ERROR] = ch.lib.icons.diagnostic.error, 35 | [vim.diagnostic.severity.WARN] = ch.lib.icons.diagnostic.warn, 36 | [vim.diagnostic.severity.INFO] = ch.lib.icons.diagnostic.info, 37 | [vim.diagnostic.severity.HINT] = ch.lib.icons.diagnostic.hint, 38 | } 39 | end 40 | -------------------------------------------------------------------------------- /lua/ch/load/globals.lua: -------------------------------------------------------------------------------- 1 | ---@class ch.types.global 2 | ---@field error fun(err: string): string 3 | ch.error = function(err) 4 | local context = debug.getinfo(2) 5 | local level = 2 6 | while not context.name or context.name == 'assert' or context.name == 'pcall' do 7 | level = level + 1 8 | if debug.getinfo(level) then 9 | context = debug.getinfo(level) 10 | end 11 | end 12 | ch.log(context.name, err, 'error') 13 | return error(err) 14 | end 15 | 16 | ---@generic T 17 | ---@param v T 18 | ---@param err string 19 | ---@return T, string 20 | ---@diagnostic disable-next-line: inject-field 21 | ch.assert = function(v, err) 22 | if v then 23 | return v, err 24 | else 25 | return nil, ch.error(err) 26 | end 27 | end 28 | 29 | ---@type string 30 | CR = CR or "~/.config" 31 | 32 | ---@type fun(v: string): string 33 | ENV = function(v) 34 | if not vim.fn.has_key(vim.fn.environ(), v) then 35 | return "" 36 | end 37 | return vim.fn.environ()[v] 38 | end 39 | 40 | ---@type fun(v: string): string 41 | CR_PATH = function (v) 42 | return CR .. "/" .. v 43 | end 44 | 45 | ---@generic T : any 46 | ---@param v T 47 | ---@return T 48 | P = function (v) 49 | print(vim.inspect(v)) 50 | return v 51 | end 52 | 53 | --- Secure reload module 54 | ---@param module_name string 55 | ---@param starts_with_only? boolean 56 | ---@return boolean 57 | ---@return any|nil|string 58 | SR = function(module_name, starts_with_only) 59 | -- Default to starts with only 60 | if starts_with_only == nil then 61 | starts_with_only = true 62 | end 63 | 64 | -- TODO: Might need to handle cpath / compiled lua packages? Not sure. 65 | local matcher 66 | if not starts_with_only then 67 | matcher = function(pack) 68 | return string.find(pack, module_name, 1, true) 69 | end 70 | else 71 | local module_name_pattern = vim.pesc(module_name) 72 | matcher = function(pack) 73 | return string.find(pack, "^" .. module_name_pattern) 74 | end 75 | end 76 | 77 | -- Handle impatient.nvim automatically. 78 | ---@diagnostic disable-next-line: undefined-field 79 | local luacache = (_G.__luacache or {}).cache 80 | 81 | vim.iter(pairs(package.loaded)):each(function(pack, _) 82 | if matcher(pack) then 83 | package.loaded[pack] = nil 84 | 85 | if luacache then 86 | luacache[pack] = nil 87 | end 88 | end 89 | end) 90 | 91 | return pcall(require, module_name) 92 | end 93 | 94 | --- secure reload and log if module is not found 95 | ---@param ... unknown 96 | ---@return boolean 97 | ---@return any 98 | SR_L = function (...) 99 | local ok, result = SR(...) 100 | if not ok then 101 | vim.notify('error while loading module\n\t' .. result, vim.log.levels.ERROR) 102 | end 103 | return ok, result 104 | end 105 | 106 | 107 | --- wrapper fn for plenary reload 108 | ---@param module string 109 | ---@param name_only boolean|nil 110 | RELOAD = function(module, name_only) 111 | return require("plenary.reload").reload_module(module, name_only) 112 | end 113 | 114 | --- wrapper fn for module reload and require 115 | ---@param name string 116 | ---@return any 117 | R = function (name) 118 | RELOAD(name) 119 | return require(name) 120 | end 121 | 122 | MT = function (t1, t2) 123 | local tnew = {} 124 | vim.iter(pairs(t1)):each(function(k, v) 125 | tnew[k] = v 126 | end) 127 | vim.iter(pairs(t2)):each(function(k, v) 128 | if type(v) == "table" then 129 | if type(tnew[k] or false) == "table" then 130 | MT(tnew[k] or {}, t2[k] or {}) 131 | else 132 | tnew[k] = v 133 | end 134 | else 135 | tnew[k] = v 136 | end 137 | end) 138 | return tnew 139 | end 140 | 141 | ---@type table 142 | CUTIL = {} 143 | 144 | ---@param buf integer 145 | ---@return string|string[] 146 | CUTIL.PATH_DIR = function (buf) 147 | local path = vim.api.nvim_buf_get_name(buf) 148 | local parent = vim.fs.dirname(path) 149 | local dir_name = vim.fn.getcwd()..'/' 150 | local name = string.gsub(parent, dir_name, '') 151 | return name 152 | end 153 | 154 | --- if in visual mode, returns number of visually selected words 155 | ---@param _ integer 156 | ---@return string 157 | CUTIL.WORD_COUNT = function (_) 158 | local w_count = vim.fn.wordcount() 159 | local count = w_count['visual_words'] or w_count['words'] or 0 160 | if count == 0 then 161 | return "" 162 | end 163 | return tostring(count) 164 | end 165 | 166 | --- if in visual mode, returns number of visually selected lines, 167 | --- else return line count in file 168 | ---@param buf integer 169 | ---@return integer 170 | CUTIL.LINE_COUNT = function (buf) 171 | local _vstart = vim.fn.line('v') 172 | local _vend = vim.fn.line('.') 173 | 174 | local diff = _vend - _vstart 175 | if diff == 0 then 176 | return vim.api.nvim_buf_line_count(buf) 177 | end 178 | 179 | return math.abs(diff) 180 | end 181 | 182 | --- return file info based on filetype 183 | --- default: LINE_COUNT 184 | --- markdown: WORD_COUNT 185 | ---@param buf integer 186 | ---@param show_icon? boolean 187 | ---@return string|integer 188 | ---@diagnostic disable-next-line: redundant-parameter 189 | CUTIL.FILE_INFO = function (buf, show_icon) 190 | local type_info = { 191 | markdown = { 'W', CUTIL.WORD_COUNT }, 192 | } 193 | local t = vim.filetype.match { buf = buf } 194 | local info = type_info[t] or { 'L', CUTIL.LINE_COUNT } 195 | local fn = info[2] 196 | local text = fn(buf) 197 | local icon = info[1] 198 | 199 | if show_icon then 200 | return ' ' .. icon .. text .. ' ' 201 | end 202 | return text 203 | end 204 | -------------------------------------------------------------------------------- /lua/ch/load/handle.lua: -------------------------------------------------------------------------------- 1 | ---@class ch.types.global 2 | ---@field handle ch.types.global.handle 3 | ---@alias ch.types.global.handle table 4 | ch.handle = ch.handle or {} 5 | 6 | ---@class ch.types.handle 7 | ---@field event string|'custom' 8 | ---@field fn AutoCmdCallback 9 | ---@field priority? integer 10 | ---@field type? string 11 | ---@field desc? string 12 | 13 | ---@diagnostic disable duplicate-doc-alias 14 | 15 | ---@alias AutoCmdCallback fun(ev: AutoCmdCallbackOpts) 16 | ---@class AutoCmdCallbackOpts 17 | ---@field id number autocommand id 18 | ---@field event string name of the triggered event `autocmd-events` 19 | ---@field group number|nil autocommand group id, if any 20 | ---@field match string expanded value of `` 21 | ---@field buf number expanded value of `` 22 | ---@field file string expanded value of `` 23 | ---@field data any arbitrary data passed from `nvim_exec_autocmds()` 24 | 25 | return { 26 | ---@param event string 27 | ---@param ev? string 28 | setup = function (event, ev) 29 | ev = ev or event 30 | ch.handle[ev] = ch.handle[ev] or {} 31 | vim.api.nvim_create_autocmd(event, { 32 | group = ch.group_id, 33 | desc = 'ch handle for ' .. event, 34 | pattern = ev ~= event and ev or nil, 35 | ---@type AutoCmdCallback 36 | callback = function(opts) 37 | -- loop over priorities of current event 38 | vim.iter(pairs(ch.handle[ev])):each(function(priority_i, priority_t) 39 | ch.log('autocmds.callback', string.format('autocmds:%s:%d', ev, priority_i)) 40 | -- loop over handles of current priority 41 | vim.iter(ipairs(priority_t)):each(function(_, handle) 42 | handle.fn(opts) 43 | end) 44 | end) 45 | end, 46 | }) 47 | end, 48 | --- ```lua 49 | --- handle.create { 50 | --- event = 'ColorScheme', priority = 0, 51 | --- fn = function() ch.log 'hi' end, 52 | --- } 53 | --- handle.create { 54 | --- event = 'custom', type = 'event', priority = 0, 55 | --- fn = function() ch.log 'hi' end, 56 | --- } 57 | --- ``` 58 | ---@param props ch.types.handle 59 | create = function(props) 60 | if not props.event or not props.fn then 61 | return 62 | end 63 | 64 | -- event name 65 | local event = props.event 66 | -- event id 67 | local ev = props.event 68 | if event == 'custom' then 69 | event = 'User' 70 | ev = props.type 71 | end 72 | 73 | local priority = props.priority or 50 74 | 75 | if not ch.handle[ev] then 76 | require 'ch.load.handle'.setup (event, ev) 77 | end 78 | 79 | ch.handle[ev][priority] = ch.handle[ev][priority] or {} 80 | local next = #ch.handle[ev][priority] + 1 81 | ch.handle[ev][priority][next] = props 82 | end, 83 | --- trigger a custom event 84 | start = function(ev) 85 | ch.log('autocmds.setup', string.format('start:custom:%s', ev)) 86 | vim.api.nvim_exec_autocmds('User', { pattern = ev }) 87 | end, 88 | } 89 | -------------------------------------------------------------------------------- /lua/ch/load/init.lua: -------------------------------------------------------------------------------- 1 | require 'ch.load.globals' 2 | require 'ch.load.constants' 3 | require 'ch.load.async' 4 | require 'ch.load.spec' 5 | -------------------------------------------------------------------------------- /lua/ch/load/spec.lua: -------------------------------------------------------------------------------- 1 | Spec = Spec or {} 2 | 3 | Spec.defaults = { 4 | lazy = true, 5 | priority = 500, 6 | } 7 | 8 | setmetatable(Spec, { 9 | __call = function(t, props) 10 | if type(props) ~= 'table' then return end 11 | local spec = props 12 | vim.iter(pairs(t.defaults)):each(function(k, v) 13 | spec[k] = v 14 | end) 15 | return spec 16 | end 17 | }) 18 | 19 | --- adds priority and lazy fields to plugin spec 20 | --- example: 21 | --- ```lua 22 | --- return Spec.colorscheme { 23 | --- 'comfysage/evergarden', 24 | --- opts = {}, 25 | --- } 26 | --- ``` 27 | --- is the same as: 28 | --- ```lua 29 | --- return { 30 | --- 'comfysage/evergarden', 31 | --- priority = 1200, 32 | --- lazy = true, 33 | --- opts = {}, 34 | --- } 35 | --- ``` 36 | ---@param props LazyPluginSpec 37 | ---@return LazyPluginSpec 38 | function Spec.colorscheme(props) 39 | ---@type LazyPluginSpec 40 | local _opts = { 41 | lazy = true, 42 | priority = 1200, 43 | } 44 | return vim.tbl_deep_extend("force", _opts, props) 45 | end 46 | -------------------------------------------------------------------------------- /lua/ch/log.lua: -------------------------------------------------------------------------------- 1 | ---@return string 2 | local function get_time() 3 | ---@diagnostic disable-next-line: return-type-mismatch 4 | return os.date('%Y_%m_%d_%T') 5 | end 6 | 7 | local log_levels = { 8 | debug = vim.log.levels.DEBUG, 9 | info = vim.log.levels.INFO, 10 | warn = vim.log.levels.WARN, 11 | error = vim.log.levels.ERROR, 12 | [vim.log.levels.DEBUG] = 'debug', 13 | [vim.log.levels.INFO] = 'info', 14 | [vim.log.levels.WARN] = 'warn', 15 | [vim.log.levels.ERROR] = 'error', 16 | } 17 | 18 | --- Log Data type 19 | ---@class ch.types.log.data 20 | ---@field __index ch.types.log.data 21 | ---@field items ch.types.log.data.item[] 22 | ---@field log_levels { [integer]: string } 23 | local Data = {} 24 | 25 | Data.__index = Data 26 | 27 | ---@class ch.types.log.data 28 | ---@field new fun(self: ch.types.log.data): ch.types.log.data 29 | function Data:new() 30 | local data = setmetatable({ 31 | items = {}, 32 | log_levels = log_levels, 33 | }, self) 34 | 35 | local json_log_path = ('%s.json'):format(ch.path.log) 36 | os.remove(json_log_path) 37 | 38 | return data 39 | end 40 | 41 | ---@alias ch.types.log.data.item { [1]: string, [2]: integer, [3]: string } 42 | 43 | ---@class ch.types.log.data 44 | ---@field write fun(self: ch.types.log.data, props: ch.types.log.data.item) 45 | function Data:write(props) 46 | if 47 | (not props[1] or not props[2]) 48 | or (not type(props[1]) == 'integer' or not type(props[2]) == 'string') 49 | then 50 | return 51 | end 52 | 53 | local source, level, msg = unpack(props, 1, 3) 54 | local item = { source, level, msg } 55 | 56 | -- save to log 57 | local log_path = ch.path.log 58 | local fh = io.open(log_path, 'a') 59 | if not fh then 60 | vim.notify( 61 | ('log file can;t be opened: %s'):format(log_path), 62 | vim.log.levels.ERROR 63 | ) 64 | else 65 | local log_line = ('[%s] %s\n'):format(level, msg) 66 | fh:write(log_line) 67 | fh:close() 68 | end 69 | 70 | -- save to json log 71 | local json_log_path = ('%s.json'):format(ch.path.log) 72 | local r_fh_json = io.open(json_log_path, 'r') 73 | local contents = '{"items":{}}' 74 | if r_fh_json then 75 | local _contents = r_fh_json:read '*a' 76 | if type(_contents) == 'string' and string.len(_contents) > 0 then 77 | contents = _contents 78 | end 79 | r_fh_json:close() 80 | end 81 | local w_fh_json = io.open(json_log_path, 'w+') 82 | if not w_fh_json then 83 | vim.notify( 84 | ('log file can;t be opened: %s'):format(json_log_path), 85 | vim.log.levels.ERROR 86 | ) 87 | else 88 | local data = vim.json.decode(contents) 89 | if data == vim.NIL or not data.items or data.items == vim.empty_dict() then 90 | data.items = {} 91 | end 92 | data.items[#data.items+1] = item 93 | local log_content = vim.json.encode(data) 94 | w_fh_json:write(log_content) 95 | w_fh_json:close() 96 | end 97 | end 98 | 99 | --- Log type 100 | ---@class ch.types.log 101 | ---@field __index ch.types.log 102 | ---@field data ch.types.log.data 103 | ---@field log_levels { [string]: integer } 104 | local Log = {} 105 | 106 | Log.__index = Log 107 | 108 | ---@class ch.types.log 109 | ---@field new fun(self: ch.types.log): ch.types.log 110 | function Log:new() 111 | local log = setmetatable({ 112 | data = Data:new(), 113 | log_levels = log_levels, 114 | }, self) 115 | 116 | local time = get_time() 117 | log:write('log', ('initial: log start [%s]'):format(time)) 118 | 119 | return log 120 | end 121 | 122 | ---@class ch.types.log 123 | ---@field write fun(self: ch.types.log, source: string, msg: string, level: 'debug'|'info'|'warn'|'error'|nil) 124 | function Log:write(source, msg, level) 125 | level = level or 'debug' 126 | local log_level = self.log_levels[level] or self.log_levels['debug'] 127 | self.data:write({ source, level, msg }) 128 | 129 | -- notify 130 | if log_level < ch.config.log_level then 131 | return 132 | end 133 | vim.notify(('[%s] %s'):format(source, msg), log_level) 134 | end 135 | 136 | ---@class ch.types.log 137 | ---@field __call fun(self: ch.types.log, source: string, msg: string, level: 'debug'|'info'|'warn'|'error'|nil) 138 | function Log:__call(...) 139 | return self:write(...) 140 | end 141 | 142 | ---@class ch.types.global 143 | ---@field log ch.types.log 144 | ---@type ch.types.log 145 | _G.ch.log = Log:new() 146 | -------------------------------------------------------------------------------- /lua/ch/modules/default.lua: -------------------------------------------------------------------------------- 1 | return { 2 | default = { 3 | reload = true, 4 | event = false, 5 | }, 6 | overwrite = {}, 7 | } 8 | -------------------------------------------------------------------------------- /lua/ch/modules/init.lua: -------------------------------------------------------------------------------- 1 | local default_modules = { 2 | ch = { 3 | init = { 4 | 'base', 'options', 'hl', 'ui', 'highlights', 'keymaps', 5 | 'lazy', 'lualine', 'treesitter', 'lsp', 'null', 6 | }, 7 | buf = { 'luasnip', 'cmp', }, 8 | ui = { 9 | 'telescope', 'mini', 'gitsigns', 'whichkey', 10 | 'dash', 11 | 'commands', 12 | 'trouble', 'todo_comments', 13 | 'incline', 'indent', 14 | }, 15 | }, 16 | } 17 | 18 | local event_map = { 19 | ui = 'UIEnter', 20 | buf = 'BufAdd', 21 | } 22 | 23 | return { 24 | get_module = function(main, module) 25 | local ok, import = SR(string.format('%s.%s', main == 'ch' and 'ch.config' or main, module)) 26 | if ok and type(import) == 'table' and import.module then 27 | return import.module 28 | end 29 | return {} 30 | end, 31 | get_defaults = function(main) 32 | if not default_modules[main] then 33 | return {} 34 | end 35 | 36 | local modules = {} 37 | vim.iter(pairs(default_modules[main])):each(function(event, list) 38 | vim.iter(ipairs(list)):each(function(i, module) 39 | modules[module] = require 'ch.modules'.setup(main, module, { 40 | priority = i, 41 | event = event_map[event] or nil, 42 | }) 43 | end) 44 | end) 45 | return modules 46 | end, 47 | setup = function(main, module, spec) 48 | local ok, default = SR_L 'ch.modules.default' 49 | if not ok then 50 | return 51 | end 52 | local import = require 'ch.modules'.get_module(main, module) 53 | import = vim.tbl_deep_extend('force', default, import) 54 | 55 | ---@type ch.types.module.spec 56 | local _spec = { 57 | name = module, 58 | reload = nil, 59 | event = nil, 60 | opts = nil, 61 | loaded = false, 62 | } 63 | 64 | _spec = vim.tbl_deep_extend('force', _spec, spec) 65 | _spec = vim.tbl_deep_extend('force', import.default, _spec) 66 | 67 | if ch.loaded and ch.modules[main] and ch.modules[main][module] then 68 | _spec.loaded = ch.modules[main][module].loaded 69 | end 70 | 71 | _spec = vim.tbl_deep_extend('force', _spec, import.overwrite) 72 | 73 | return _spec 74 | end 75 | } 76 | -------------------------------------------------------------------------------- /lua/ch/parts.lua: -------------------------------------------------------------------------------- 1 | local Plugins = require 'ch.plugins' 2 | 3 | local parts = {} 4 | 5 | function parts.load_modules(_) 6 | parts.load_config {} 7 | 8 | vim.iter(pairs(ch.modules)):each(function(main_mod, modules) 9 | vim.iter(pairs(modules)):each(function(_, spec) 10 | ch.lib.autocmd.create { 11 | event = 'custom', type = 'lazych', priority = spec.priority or nil, 12 | desc = ('lazych:%s:%s'):format(main_mod, spec.name), 13 | fn = function() 14 | parts.lazy_load(main_mod, spec.name) 15 | end 16 | } 17 | end) 18 | end) 19 | 20 | require 'ch.load.handle'.start 'lazych' 21 | end 22 | 23 | function parts.load_config(_) 24 | if not ch.config.modules['ch'] then 25 | ch.log('ch.parts', 'ch modules are not defined.', 'error') 26 | return 27 | end 28 | 29 | vim.iter(pairs(ch.config.modules)):each(function(main_mod, modules) 30 | ch.log('ch.parts', 'loading ' .. main_mod .. ' modules.') 31 | ch.modules[main_mod] = ch.modules[main_mod] or {} 32 | 33 | vim.iter(pairs(modules)):each(function(_, spec) 34 | if spec.opts and type(spec.opts) == 'string' then 35 | spec.opts = require(spec.opts) 36 | end 37 | local name = spec.name or spec[1] 38 | spec = require 'ch.modules'.setup(main_mod, name, spec) 39 | 40 | ch.modules[main_mod][name] = spec 41 | end) 42 | 43 | ch.modules[main_mod] = vim.tbl_deep_extend("keep", ch.modules[main_mod], 44 | require 'ch.modules'.get_defaults(main_mod)) 45 | end) 46 | 47 | -- update options table 48 | ch.lib.options.__value = ch.modules.ch 49 | end 50 | 51 | ---@param main string 52 | ---@param name string 53 | function parts.lazy_load(main, name) 54 | ---@type ch.types.module.name 55 | local module = main .. '.' .. name 56 | if main == 'ch' then 57 | module = main .. '.config.' .. name 58 | end 59 | 60 | local spec = ch.modules[main][name] 61 | 62 | parts.load(module, spec) 63 | spec.loaded = true 64 | 65 | if spec.reload then 66 | require 'ch.load.autocmds'.create_reload(module, spec) 67 | end 68 | end 69 | 70 | ---@param module string 71 | ---@param spec ch.types.module.spec 72 | function parts.load(module, spec) 73 | if spec.enabled == false then 74 | ch.log('ch.parts', 'skipping loading module: ' .. module) 75 | return 76 | end 77 | if spec.loaded and spec.reload == false then 78 | ch.log('ch.parts', 'skipping reloading module: ' .. module) 79 | return 80 | end 81 | 82 | ---@param source string 83 | ---@param opts table 84 | local callback = function(source, opts) 85 | local status, result = pcall(require, source) 86 | if not status then 87 | ch.log('ch.parts', "failed to load " .. source .. "\n\t" .. result, 'error') 88 | return 89 | end 90 | if type(result) == 'table' then 91 | if result.setup then 92 | if not type(spec.opts) == 'table' then 93 | return 94 | end 95 | result.setup(opts) 96 | end 97 | end 98 | end 99 | 100 | if spec.event and not spec.loaded then 101 | vim.api.nvim_create_autocmd({ spec.event }, { 102 | group = ch.group_id, 103 | once = true, 104 | callback = function() 105 | callback(module, spec.opts) 106 | end, 107 | }) 108 | else 109 | callback(module, spec.opts) 110 | end 111 | end 112 | 113 | function parts.colorscheme(_) 114 | ---@diagnostic disable-next-line: undefined-field 115 | if ch.config.ui.base46 ~= nil and ch.config.ui.colorscheme == 'base46' then 116 | require('ch.plugins').load 'base46' 117 | end 118 | local ok, result = pcall(vim.cmd.colorscheme, ch.config.ui.colorscheme) 119 | if not ok then 120 | ch.log('ch.parts', "couldn't load colorscheme\n\t"..result, 'error') 121 | end 122 | 123 | vim.api.nvim_create_autocmd({ 'UIEnter' }, { 124 | group = ch.group_id, 125 | once = true, 126 | callback = function() 127 | vim.api.nvim_exec_autocmds('ColorScheme', {}) 128 | end 129 | }) 130 | end 131 | 132 | function parts.load_transparency(_) 133 | require 'ch.plugin.transparency'.setup() 134 | end 135 | 136 | function parts.load_inputs(_) 137 | ch.path.lazy = vim.fs.joinpath(ch.path.root, 'lazy') 138 | 139 | local inputs = ch.config.inputs 140 | if type(inputs) == 'string' then 141 | local result, _inputs = pcall(require, inputs) 142 | if not result then 143 | ch.log('parts.load_inputs', ('could not load inputs [%s]:\n\t%s'):format(_inputs, result), 'error') 144 | return 145 | end 146 | inputs = _inputs 147 | end 148 | ch.config.inputs = inputs 149 | ---@class ch.types.global 150 | ---@field _inputs LazyPluginSpec[] 151 | ch._inputs = Plugins.parse_inputs(ch.config.inputs) 152 | end 153 | 154 | function parts.preload(_) 155 | parts.load_lib {} 156 | 157 | parts.load_inputs {} 158 | require 'ch.plugins'.load 'lazy.nvim' 159 | require 'ch.plugins'.load 'keymaps' 160 | local ok, result = SR_L 'keymaps' 161 | if ok then 162 | result.setup() 163 | end 164 | require 'ch.plugins'.load 'yosu' 165 | require 'ch.plugins'.load 'plenary' 166 | require 'ch.plugins'.load 'telescope' 167 | require 'ch.plugins'.load 'evergarden' 168 | 169 | if not keymaps then 170 | ch.log('ch.parts', 'global keymaps is not defined.', 'error') 171 | return 172 | end 173 | end 174 | 175 | function parts.load_lib(_) 176 | require 'ch.lib'.setup() 177 | end 178 | 179 | function parts.platform(_) 180 | local is_mac = vim.fn.has 'mac' == 1 181 | local is_win = vim.fn.has 'win32' == 1 182 | local is_neovide = vim.g.neovide 183 | 184 | if is_mac then 185 | require(CONFIG_MODULE .. '.macos') 186 | end 187 | if is_win then 188 | require(CONFIG_MODULE .. '.windows') 189 | end 190 | if is_neovide then 191 | require(CONFIG_MODULE .. '.neovide') 192 | end 193 | end 194 | 195 | return parts 196 | -------------------------------------------------------------------------------- /lua/ch/plugin/command.lua: -------------------------------------------------------------------------------- 1 | ---@class NvimCommandProps 2 | ---@field name string Command name 3 | ---@field args string The args passed to the command, if any `` 4 | ---@field fargs table The args split by unescaped whitespace (when more than one argument is allowed), if any `` 5 | ---@field nargs string Number of arguments `:command-nargs` 6 | ---@field bang boolean "true" if the command was executed with a ! modifier `` 7 | ---@field line1 number The starting line of the command range `` 8 | ---@field line2 number The final line of the command range `` 9 | ---@field range number The number of items in the command range: 0, 1, or 2 `` 10 | ---@field count number Any count supplied `` 11 | ---@field reg string The optional register, if specified `` 12 | ---@field mods string Command modifiers, if any `` 13 | ---@field smods table Command modifiers in a structured format. Has the same structure as the "mods" key of `nvim_parse_cmd()`. 14 | 15 | ---@alias chCommand { name: string, fn: fun(props: NvimCommandProps), opts: vim.api.keyset.user_command } 16 | 17 | _G.ch.commands = _G.ch.commands or {} 18 | 19 | local M = {} 20 | 21 | ---@param props chCommand 22 | M._add = function(props) 23 | _G.ch.commands[props.name] = props 24 | vim.api.nvim_create_user_command(props.name, props.fn, props.opts) 25 | end 26 | 27 | --- ```lua 28 | --- require 'ch.plugin.command'.create { 29 | --- name = 'Open', fn = function() ... end, 30 | --- opts = { ... }, 31 | --- } 32 | --- ``` 33 | ---@param props chCommand 34 | M.create = function(props) 35 | if not props or not props.name or not props.fn then 36 | return 37 | end 38 | 39 | local _a = vim.split(props.name, '') 40 | 41 | if #_a == 0 then 42 | return 43 | end 44 | _a[1] = string.upper(_a[1]) 45 | local name = vim.fn.join(_a, '') 46 | 47 | M._add { name = name, fn = props.fn, opts = props.opts or {} } 48 | end 49 | 50 | return M 51 | -------------------------------------------------------------------------------- /lua/ch/plugin/highlight.lua: -------------------------------------------------------------------------------- 1 | ---@diagnostic disable duplicate-doc-alias 2 | 3 | ---@alias Color { [1]: string, [2]: integer } 4 | ---@alias ColorSpec { [1]: Color, [2]: Color, link: string, reverse: boolean } 5 | 6 | ---@param group string 7 | ---@param colors ColorSpec 8 | local function set_hi(group, colors) 9 | if type(colors) ~= 'table' or vim.tbl_isempty(colors) then 10 | return 11 | end 12 | 13 | colors.fg = colors.fg or colors[1] or 'none' 14 | colors.bg = colors.bg or colors[2] or 'none' 15 | 16 | ---@type vim.api.keyset.highlight 17 | local color = {} 18 | 19 | vim.iter(pairs(colors)):each(function(k, v) 20 | color[k] = v 21 | end) 22 | 23 | color.fg = type(colors.fg) == 'table' and colors.fg[1] or colors.fg 24 | color.bg = type(colors.bg) == 'table' and colors.bg[1] or colors.bg 25 | color.ctermfg = type(colors.fg) == 'table' and colors.fg[2] or 'none' 26 | color.ctermbg = type(colors.bg) == 'table' and colors.bg[2] or 'none' 27 | color[1] = nil 28 | color[2] = nil 29 | color.name = nil 30 | 31 | vim.api.nvim_set_hl(0, group, color) 32 | end 33 | 34 | ---@alias HLGroups { [string]: ColorSpec } 35 | 36 | ---@param hlgroups HLGroups 37 | local function set_highlights(hlgroups) 38 | vim.iter(pairs(hlgroups)):each(function(group, colors) 39 | set_hi(group, colors) 40 | end) 41 | end 42 | 43 | return { 44 | apply = function(props) 45 | set_highlights(props) 46 | end, 47 | } 48 | -------------------------------------------------------------------------------- /lua/ch/plugin/icons.lua: -------------------------------------------------------------------------------- 1 | ---@alias Icons { [string]: string } 2 | 3 | ---@class chIcons 4 | ---@field syntax Icons 5 | ---@field item Icons 6 | ---@field info Icons 7 | ---@field diff Icons 8 | ---@field diff_status Icons 9 | ---@field diagnostic Icons 10 | ---@field ui Icons 11 | ---@field separator Icons 12 | ---@field git Icons 13 | ---@field debug Icons 14 | 15 | return { 16 | ---@return chIcons 17 | create = function() 18 | return { 19 | syntax = { 20 | text = '', 21 | method = '', 22 | fn = '', 23 | constructor = '', 24 | field = 'ﰠ', 25 | variable = '󰀫', 26 | class = 'ﴯ', 27 | interface = '', 28 | module = '', 29 | property = 'ﰠ', 30 | unit = '', 31 | value = '', 32 | enum = '', 33 | keyword = '', 34 | snippet = '', 35 | color = '', 36 | file = '', 37 | reference = '', 38 | folder = '', 39 | enummember = '', 40 | constant = '', 41 | struct = 'פּ', 42 | event = '', 43 | operator = '', 44 | typeparameter = '', 45 | namespace = '󰌗', 46 | table = '', 47 | object = '󰅩', 48 | tag = '', 49 | array = '[]', 50 | boolean = '', 51 | number = '', 52 | null = '󰟢', 53 | string = '"', 54 | package = '', 55 | }, 56 | item = { 57 | colors = '󰏘', 58 | find = '', 59 | }, 60 | info = { 61 | loaded = "●", 62 | not_loaded = "○", 63 | fold_open = '', 64 | fold_closed = '', 65 | locked = '', 66 | non_empty = '⏺', 67 | }, 68 | diff = { 69 | added = '󰐖', 70 | changed = '󰦓', 71 | deleted = '󰍵', 72 | }, 73 | diff_status = { 74 | added = '', 75 | changed = '', 76 | deleted = '', 77 | }, 78 | diagnostic = { 79 | error = '󰅙', 80 | warn = '', 81 | info = '󰋼', 82 | hint = '󰌵', 83 | }, 84 | ui = { 85 | item_prefix = '', 86 | dot = '·', 87 | }, 88 | separator = { 89 | slant = { left = "", right = "" }, 90 | round = { left = "", right = "" }, 91 | block = { left = "█", right = "█" }, 92 | arrow = { left = "", right = "" }, 93 | }, 94 | git = { 95 | branch = '', 96 | }, 97 | debug = { 98 | start = '', 99 | pause = '', 100 | continue = '', 101 | restart = '', 102 | step_into = '', 103 | step_out = '', 104 | step_over = '', 105 | step_back = '', 106 | stop = '', 107 | data = '', 108 | log = '', 109 | }, 110 | } 111 | end, 112 | setup = function() 113 | ch.lib.icons = require 'ch.plugin.icons'.create() 114 | 115 | -- [FIXME] 116 | ---@deprecated 117 | ch.icons = require 'ch.plugin.icons'.create() 118 | end, 119 | } 120 | -------------------------------------------------------------------------------- /lua/ch/plugin/keymaps.lua: -------------------------------------------------------------------------------- 1 | _G.Keymap = _G.Keymap or {} 2 | 3 | _G.Keymap.group = _G.Keymap.group or function(props) 4 | if not props.group then 5 | ch.log('globals.keymap', '`Keymap.group` requires `group` field', 'warn') 6 | return 7 | end 8 | local mappings = props[1] or {} 9 | if #props > 1 then 10 | mappings = props 11 | end 12 | vim.iter(ipairs(mappings)):each(function(_, map) 13 | if #map < 4 then 14 | ch.log('globals.keymap', '`Keymap.group` requires 4 paramaters per keymap', 'warn') 15 | return 16 | end 17 | local mode = map[1] 18 | local lhs = map[2] 19 | local rhs = map[3] 20 | local desc = map[4] 21 | keymaps[mode][lhs] = { rhs, desc, group = props.group } 22 | end) 23 | end 24 | 25 | ---@param map string 26 | ---@return string 27 | local function lhs_fmt(map) 28 | local map_str = '' 29 | 30 | local sp_open = 0 31 | local temp = '' 32 | 33 | local _map = vim.split(map, '') 34 | local i = 1 35 | while i <= #_map do 36 | temp = '' 37 | if sp_open > 0 then 38 | if _map[i] == '>' then 39 | temp = string.sub(map, sp_open, i) 40 | temp = string.lower(temp) 41 | vim.iter(pairs(keymaps_config.repl_keys)):each(function(pattern, rpl) 42 | temp = string.gsub(temp, pattern, rpl) 43 | end) 44 | sp_open = 0 45 | end 46 | else 47 | temp = _map[i] 48 | 49 | if temp == '<' then 50 | sp_open = i 51 | temp = '' 52 | end 53 | if i == #_map and sp_open > 0 then 54 | temp = string.sub(map, sp_open, i) 55 | end 56 | end 57 | 58 | if temp and #temp > 0 then 59 | if #map_str > 0 then 60 | map_str = map_str .. ' + ' 61 | end 62 | map_str = map_str .. temp 63 | end 64 | 65 | i = i + 1 66 | end 67 | 68 | return map_str 69 | end 70 | 71 | return { 72 | fmt = lhs_fmt, 73 | } 74 | -------------------------------------------------------------------------------- /lua/ch/plugin/lsp/init.lua: -------------------------------------------------------------------------------- 1 | local H = require 'ch.plugin.lsp.utils' 2 | 3 | local M = {} 4 | 5 | ---@param location table a single `Location` or `LocationLink` 6 | ---@param opts table 7 | ---@return integer|nil buffer id of float window 8 | ---@return integer|nil window id of float window 9 | function M.preview_location(location, opts) 10 | local context = opts.context or 0 11 | -- location may be LocationLink or Location (more useful for the former) 12 | local uri = location.targetUri or location.uri 13 | if uri == nil then 14 | return 15 | end 16 | local bufnr = vim.uri_to_bufnr(uri) 17 | if not vim.api.nvim_buf_is_loaded(bufnr) then 18 | vim.fn.bufload(bufnr) 19 | end 20 | local range = location.targetRange or location.range 21 | local start_line = range.start.line 22 | start_line = start_line > context and start_line - context or 0 23 | local end_line = range['end'].line + 1 + context 24 | local contents = vim.api.nvim_buf_get_lines(bufnr, start_line, end_line, false) 25 | local syntax = vim.bo[bufnr].syntax 26 | if syntax == '' then 27 | -- When no syntax is set, we use filetype as fallback. This might not result 28 | -- in a valid syntax definition. 29 | -- An empty syntax is more common now with TreeSitter, since TS disables syntax. 30 | syntax = vim.bo[bufnr].filetype 31 | end 32 | opts = opts or {} 33 | opts.focus_id = 'location' 34 | return vim.lsp.util.open_floating_preview(contents, syntax, opts) 35 | end 36 | 37 | function M.peek_definition() 38 | local params = vim.lsp.util.make_position_params() 39 | H.request('textDocument/definition', params, function(_, result) 40 | M.preview_location(result[1], { context = 2 }) 41 | end) 42 | end 43 | 44 | return M 45 | -------------------------------------------------------------------------------- /lua/ch/plugin/lsp/utils.lua: -------------------------------------------------------------------------------- 1 | local H = {} 2 | 3 | ---@param method string 4 | ---@param params table? 5 | ---@param handler function 6 | ---@return table 7 | ---@return function 8 | function H.request(method, params, handler) 9 | vim.validate({ 10 | method = { method, 's' }, 11 | handler = { handler, 'f', true }, 12 | }) 13 | return vim.lsp.buf_request(0, method, params, handler) 14 | end 15 | 16 | ---@param method string 17 | ---@param params table? 18 | ---@param options table? 19 | ---@return table 20 | ---@return function 21 | function H.request_with_options(method, params, options) 22 | local req_handler 23 | if options then 24 | req_handler = function(err, result, ctx, config) 25 | local client = vim.lsp.get_client_by_id(ctx.client_id) 26 | local handler = client.handlers[method] or vim.lsp.handlers[method] or function() end 27 | handler(err, result, ctx, vim.tbl_extend('force', config or {}, options)) 28 | end 29 | end 30 | return H.request(method, params, req_handler) 31 | end 32 | 33 | return H 34 | -------------------------------------------------------------------------------- /lua/ch/plugin/lspkind.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | local cmp_hi = { 4 | CmpItemMenu = { fg = ch.lib.hl:get('syntax', 'constant'), bg = "none", italic = true }, 5 | 6 | CmpItemAbbrDeprecated = { fg = ch.lib.hl:get('diagnostic', 'warn') }, 7 | 8 | CmpItemAbbrMatch = { fg = ch.lib.hl:get('ui', 'match') }, 9 | CmpItemAbbrMatchFuzzy = { link = "CmpItemAbbrMatch" }, 10 | } 11 | 12 | vim.iter(pairs(cmp_hi)):each(function(hi_group, hl) 13 | vim.api.nvim_set_hl(0, hi_group, hl) 14 | end) 15 | 16 | local kind_icons = { 17 | Text = ch.lib.icons.syntax.text, 18 | Method = ch.lib.icons.syntax.method, 19 | Function = ch.lib.icons.syntax.fn, 20 | Constructor = ch.lib.icons.syntax.constructor, 21 | Field = ch.lib.icons.syntax.field, 22 | Variable = ch.lib.icons.syntax.variable, 23 | Class = ch.lib.icons.syntax.class, 24 | Interface = ch.lib.icons.syntax.interface, 25 | Module = ch.lib.icons.syntax.module, 26 | Property = ch.lib.icons.syntax.property, 27 | Unit = ch.lib.icons.syntax.unit, 28 | Value = ch.lib.icons.syntax.value, 29 | Enum = ch.lib.icons.syntax.enum, 30 | Keyword = ch.lib.icons.syntax.keyword, 31 | Snippet = ch.lib.icons.syntax.snippet, 32 | Color = ch.lib.icons.syntax.color, 33 | File = ch.lib.icons.syntax.file, 34 | Reference = ch.lib.icons.syntax.reference, 35 | Folder = ch.lib.icons.syntax.folder, 36 | EnumMember = ch.lib.icons.syntax.enummember, 37 | Constant = ch.lib.icons.syntax.constant, 38 | Struct = ch.lib.icons.syntax.struct, 39 | Event = ch.lib.icons.syntax.event, 40 | Operator = ch.lib.icons.syntax.operator, 41 | TypeParameter = ch.lib.icons.syntax.typeparameter, 42 | Namespace = ch.lib.icons.syntax.namespace, 43 | Table = ch.lib.icons.syntax.table, 44 | Object = ch.lib.icons.syntax.object, 45 | Tag = ch.lib.icons.syntax.tag, 46 | Array = ch.lib.icons.syntax.array, 47 | Boolean = ch.lib.icons.syntax.boolean, 48 | Number = ch.lib.icons.syntax.number, 49 | Null = ch.lib.icons.syntax.null, 50 | String = ch.lib.icons.syntax.string, 51 | Package = ch.lib.icons.syntax.package, 52 | } 53 | 54 | M.kind_icons = kind_icons 55 | 56 | local kind_hl = { 57 | Text = ch.lib.hl:get('syntax', 'text'), 58 | Method = ch.lib.hl:get('syntax', 'method'), 59 | Function = ch.lib.hl:get('syntax', 'fn'), 60 | Constructor = ch.lib.hl:get('syntax', 'constructor'), 61 | Field = ch.lib.hl:get('syntax', 'field'), 62 | Variable = ch.lib.hl:get('syntax', 'variable'), 63 | Class = ch.lib.hl:get('syntax', 'class'), 64 | Interface = ch.lib.hl:get('syntax', 'interface'), 65 | Module = ch.lib.hl:get('syntax', 'module'), 66 | Property = ch.lib.hl:get('syntax', 'property'), 67 | Unit = ch.lib.hl:get('syntax', 'unit'), 68 | Value = ch.lib.hl:get('syntax', 'value'), 69 | Enum = ch.lib.hl:get('syntax', 'enum'), 70 | Keyword = ch.lib.hl:get('syntax', 'keyword'), 71 | Snippet = ch.lib.hl:get('syntax', 'snippet'), 72 | Color = ch.lib.hl:get('syntax', 'color'), 73 | File = ch.lib.hl:get('syntax', 'file'), 74 | Reference = ch.lib.hl:get('syntax', 'reference'), 75 | Folder = ch.lib.hl:get('syntax', 'folder'), 76 | EnumMember = ch.lib.hl:get('syntax', 'enummember'), 77 | Constant = ch.lib.hl:get('syntax', 'constant'), 78 | Struct = ch.lib.hl:get('syntax', 'struct'), 79 | Event = ch.lib.hl:get('syntax', 'event'), 80 | Operator = ch.lib.hl:get('syntax', 'operator'), 81 | TypeParameter = ch.lib.hl:get('syntax', 'typeparameter'), 82 | Namespace = ch.lib.hl:get('syntax', 'namespace'), 83 | Table = ch.lib.hl:get('syntax', 'table'), 84 | Object = ch.lib.hl:get('syntax', 'object'), 85 | Tag = ch.lib.hl:get('syntax', 'tag'), 86 | Array = ch.lib.hl:get('syntax', 'array'), 87 | Boolean = ch.lib.hl:get('syntax', 'boolean'), 88 | Number = ch.lib.hl:get('syntax', 'number'), 89 | Null = ch.lib.hl:get('syntax', 'null'), 90 | String = ch.lib.hl:get('syntax', 'string'), 91 | Package = ch.lib.hl:get('syntax', 'package'), 92 | } 93 | 94 | vim.iter(pairs(kind_hl)):each(function(kind, item) 95 | local hi_group = string.format('CmpItemKind%s', kind) 96 | ch.lib.hl.apply { 97 | [hi_group] = { fg = item }, 98 | } 99 | end) 100 | 101 | local max_count = 26 102 | 103 | function M.create_formatter(mode) 104 | local modes = { 105 | evergreen = { 106 | fields = { 'abbr', 'kind', 'menu' }, 107 | format = function(entry, vim_item) 108 | -- Kind icons 109 | vim_item.kind = string.format('%s %s', kind_icons[vim_item.kind], vim_item.kind) -- This concatonates the icons with the name of the item kind 110 | -- Source 111 | local menu_item = ({ 112 | buffer = "Buffer", 113 | nvim_lsp = "LSP", 114 | luasnip = "LuaSnip", 115 | nvim_lua = "Lua", 116 | latex_symbols = "LaTeX", 117 | })[entry.source.name] 118 | vim_item.menu = menu_item and string.format(' (%s)', menu_item) or '' 119 | 120 | local word = vim_item.abbr 121 | local len = string.len(word) 122 | if len < max_count then 123 | vim_item.abbr = word .. string.rep(' ', max_count - len) 124 | else 125 | vim_item.abbr = string.sub(word, 0, max_count - 3) .. '...' 126 | end 127 | return vim_item 128 | end, 129 | }, 130 | nyoom = { 131 | fields = { 'kind', 'abbr', 'menu' }, 132 | format = function(_, vim_item) 133 | -- Kind icons 134 | local kind = vim_item.kind 135 | vim_item.menu = kind 136 | vim_item.kind = ch.lib.fmt.space(kind_icons[kind]) 137 | -- Source 138 | 139 | local word = vim_item.abbr 140 | local len = string.len(word) 141 | if len < max_count then 142 | vim_item.abbr = word .. string.rep(' ', max_count - len) 143 | else 144 | vim_item.abbr = string.sub(word, 0, max_count - 3) .. '...' 145 | end 146 | return vim_item 147 | end, 148 | }, 149 | } 150 | local format = modes[mode] 151 | if not format then 152 | format = modes['evergreen'] 153 | end 154 | return format 155 | end 156 | 157 | 158 | return M 159 | -------------------------------------------------------------------------------- /lua/ch/plugin/telescope.lua: -------------------------------------------------------------------------------- 1 | local builtin = require 'telescope.builtin' 2 | local themes = require 'telescope.themes' 3 | 4 | local M = {} 5 | 6 | ---@alias TelescopeStyle 'dropdown'|'bottom'|'main' 7 | 8 | ---@type { [TelescopeStyle]: table } 9 | M.style = {} 10 | 11 | M.style.dropdown = themes.get_dropdown { 12 | theme = 'dropdown', 13 | layout_config = { 14 | width = function(_, max_columns, _) 15 | return math.min(max_columns, 80) 16 | end, 17 | height = function(_, _, max_lines) 18 | return math.min(max_lines, 12) 19 | end, 20 | }, 21 | previewer = false, 22 | } 23 | 24 | M.style.bottom = themes.get_ivy { 25 | theme = 'bottom', 26 | -- border = true, 27 | preview = true, 28 | shorten_path = true, 29 | hidden = true, 30 | prompt_title = '', 31 | preview_title = '', 32 | borderchars = { 33 | preview = { ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ' }, 34 | }, 35 | } 36 | 37 | M.style.main = { 38 | theme = 'main', 39 | -- winblend = 20; 40 | layout_config = { 41 | width = function(_, max_columns, _) 42 | return math.min(math.floor(max_columns * 0.8), 160) 43 | end, 44 | height = function(_, _, max_lines) 45 | return math.floor(max_lines * 0.9) 46 | end, 47 | }, 48 | show_line = false, 49 | results_title = '', 50 | prompt_prefix = '$ ', 51 | prompt_position = 'top', 52 | prompt_title = '', 53 | preview_title = '', 54 | preview_width = 0.4, 55 | } 56 | 57 | ---@param name TelescopeStyle 58 | ---@param opts table 59 | ---@return table 60 | M.get_style = function(name, opts) 61 | opts = opts or {} 62 | local style = M.style[name] 63 | return vim.tbl_deep_extend('force', style or {}, opts) 64 | end 65 | 66 | M.picker = {} 67 | 68 | function M.picker.find_files(props) 69 | props = props or {} 70 | local opts = M.get_style('main', { 71 | prompt_prefix = ch.lib.icons.item.find .. ' ', 72 | shorten_path = true, 73 | hidden = true, 74 | }) 75 | 76 | builtin.find_files(MT(opts, props)) 77 | end 78 | 79 | function M.picker.grep(props) 80 | props = props or {} 81 | local opts = M.get_style('main', { 82 | max_results = 20 83 | }) 84 | 85 | builtin.live_grep(MT(opts, props)) 86 | end 87 | 88 | -- Explorer 89 | 90 | function M.picker.explorer(props) 91 | props = props or {} 92 | local opts = M.get_style('dropdown', { 93 | preview = true, 94 | shorten_path = true, 95 | hidden = true, 96 | prompt_title = '', 97 | preview_title = '', 98 | }) 99 | 100 | builtin.find_files(MT(opts, props)) 101 | end 102 | 103 | function M.picker.git_files(props) 104 | props = props or {} 105 | builtin.git_files(M.get_style('bottom', props)) 106 | end 107 | 108 | function M.picker.config_files() 109 | M.pickers.explorer({ 110 | cwd = '~/.config/nvim', 111 | }) 112 | end 113 | 114 | function M.picker.notes() 115 | M.pickers.explorer({ 116 | cwd = '~/.notes', 117 | }) 118 | end 119 | 120 | function M.picker.symbols(props) 121 | props = props or {} 122 | local opts = M.get_style('main', { 123 | shorten_path = true, 124 | hidden = true, 125 | }) 126 | 127 | builtin.lsp_document_symbols(MT(opts, props)) 128 | end 129 | 130 | function M.picker.grep_current_file(props) 131 | props = props or {} 132 | R 'telescope.builtin'.current_buffer_fuzzy_find(M.get_style('main', props)) 133 | end 134 | 135 | return M 136 | -------------------------------------------------------------------------------- /lua/ch/plugin/transparency.lua: -------------------------------------------------------------------------------- 1 | local function hl_override(name, props) 2 | local hl = ch.lib.hl.get_hl { name = name } 3 | return vim.tbl_deep_extend('force', hl, props or {}) 4 | end 5 | 6 | local function glassify(name) 7 | return hl_override(name, { bg = 'none' }) 8 | end 9 | 10 | return { 11 | setup = function() 12 | ch.lib.autocmd.create { 13 | event = 'ColorScheme', priority = GC.priority.handle.colorscheme.transparency, 14 | desc = 'load transparency hls', 15 | fn = function(_) 16 | -- reload highlights after colorscheme is switched/reloaded with changes 17 | require 'ch.plugin.transparency'.create() 18 | require 'ch.plugin.transparency'.fix() 19 | end, 20 | } 21 | end, 22 | get = function() 23 | return _G.saved_highlights.transparent or require 'ch.plugin.transparency'.create() 24 | end, 25 | create = function() 26 | _G.saved_highlights = { 27 | transparent = { 28 | Normal = { fg = ch.lib.hl:get('ui', 'fg'), bg = 'none' }, 29 | SignColumn = glassify 'SignColumn', 30 | LineNr = glassify 'LineNr', 31 | TabLine = glassify 'TabLine', 32 | TabLineFill = glassify 'TabLineFill', 33 | }, 34 | normal = {}, 35 | } 36 | local save = vim.tbl_keys(_G.saved_highlights.transparent) 37 | vim.iter(ipairs(save)):each(function(_, name) 38 | _G.saved_highlights.normal[name] = ch.lib.hl.get_hl { name = name } 39 | end) 40 | return _G.saved_highlights.transparent 41 | end, 42 | ---@param mode boolean 43 | set = function(mode) 44 | if mode then 45 | ch.lib.hl.apply(require 'ch.plugin.transparency'.get()) 46 | else 47 | ch.lib.hl.apply(_G.saved_highlights.normal) 48 | end 49 | end, 50 | fix = function() 51 | if ch.config.ui.transparent_background ~= nil then 52 | require 'ch.plugin.transparency'.set(ch.config.ui.transparent_background) 53 | end 54 | end, 55 | toggle = function() 56 | if ch.config.ui.transparent_background ~= nil then 57 | ch.config.ui.transparent_background = not ch.config.ui.transparent_background 58 | require 'ch.plugin.transparency'.fix() 59 | end 60 | end, 61 | } 62 | -------------------------------------------------------------------------------- /lua/ch/plugins.lua: -------------------------------------------------------------------------------- 1 | local Plugins = {} 2 | 3 | -- adapted from @lazy.nvim https://github.com/folke/lazy.nvim/blob/bc620783663ab09d16bff9fdecc07da65b2a1528/lua/lazy/ch/plugin.lua#L48 4 | function Plugins.get_name(pkg) 5 | local name = pkg:sub(-4) == ".git" and pkg:sub(1, -5) or pkg 6 | name = name:sub(-1) == "/" and name:sub(1, -2) or name 7 | local slash = name:reverse():find("/", 1, true) --[[@as number?]] 8 | return slash and name:sub(#name - slash + 2) or pkg:gsub("%W+", "_") 9 | end 10 | 11 | ---@type fun(s: string): string 12 | function Plugins.expand_url(s) 13 | local url = s:sub(-4) == '.git' and s or (s .. '.git') 14 | local colon = url:find(':', 1, true) --[[@as number?]] 15 | return colon and url or ('git@github.com:%s'):format(url) 16 | end 17 | 18 | ---@param plugins string[] 19 | function Plugins.load_plugins(plugins) 20 | vim.iter(plugins):each(function(url) 21 | local name = Plugins.get_name(url) 22 | Plugins.add_to_path(('%s/%s'):format(ch.path.lazy, name)) 23 | end) 24 | end 25 | 26 | ---@param path string 27 | function Plugins.add_to_path(path) 28 | ch.log('ch.plugins', ('add "%s" to path'):format(path)) 29 | ---@diagnostic disable-next-line: undefined-field 30 | vim.opt.rtp:prepend(path) 31 | end 32 | 33 | ---@param spec LazyPluginSpec 34 | function Plugins.install(spec) 35 | local modulepath = spec.dir 36 | 37 | local obj = vim.system({ 38 | "git", 39 | "clone", 40 | "--filter=blob:none", 41 | spec.url, 42 | modulepath, 43 | }, {}):wait() 44 | if obj.code > 0 then 45 | ch.log('ch.plugins', 'error while cloning ' .. spec.name .. ' at ' .. modulepath .. 46 | '\n\t' .. obj.stdout .. '\n\t' .. obj.stderr, 'error') 47 | return 48 | end 49 | ch.log('ch.plugins', 'succesfully cloned ' .. spec.name, 'info') 50 | end 51 | 52 | ---@param spec LazyPluginSpec 53 | function Plugins.bootstrap(spec) 54 | if not vim.uv.fs_stat(spec.dir) then 55 | ch.log('ch.plugins', ('module %s [%s] not found. bootstrapping...'):format(spec.name, spec.dir), 'warn') 56 | Plugins.install(spec) 57 | end 58 | Plugins.add_to_path(spec.dir) 59 | end 60 | 61 | function Plugins.load(name) 62 | local spec = vim.iter(ch._inputs):find(function(v) 63 | return v.name == name 64 | end) 65 | if not spec then 66 | ch.log('ch.plugins', ('could not find input \'%s\''):format(name)) 67 | return 68 | end 69 | Plugins.bootstrap(spec) 70 | end 71 | 72 | ---@param props LazyPluginSpec[] 73 | ---@return LazyPluginSpec[] 74 | function Plugins.parse_inputs(props) 75 | return vim.iter(props):map(function(v) 76 | if #v == 0 and not v.url then 77 | return 78 | end 79 | v.url = v.url or Plugins.expand_url(v[1]) 80 | if not v.name then 81 | v.name = Plugins.get_name(v.url) 82 | end 83 | v.dir = v.dir or vim.fs.joinpath(ch.path.lazy, v.name) 84 | return v 85 | end):totable() 86 | end 87 | 88 | return Plugins 89 | -------------------------------------------------------------------------------- /lua/ch/ui/bufferline/hl.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | M.setup_highlights = function() 4 | ch.lib.autocmd.create { 5 | event = 'ColorScheme', priority = GC.priority.handle.colorscheme.plugin, 6 | desc = 'apply bufferline hls', 7 | fn = function(_) 8 | require 'ch.ui.bufferline.hl'.apply_highlights() 9 | end 10 | } 11 | end 12 | 13 | M.apply_highlights = function() 14 | local normal_bg = ch.lib.hl:get('ui', 'bg') 15 | local normal_fg = ch.lib.hl:get('ui', 'fg') 16 | 17 | local comment_fg = ch.lib.hl:get('ui', 'comment') 18 | 19 | local title_fg = ch.lib.hl:get('syntax', 'title') 20 | 21 | local hls = { 22 | ['BfLineFill'] = { fg = title_fg }, 23 | ['BfKillBuf'] = { fg = title_fg }, 24 | ['BfLineBufOn'] = { fg = normal_fg }, 25 | ['BfLineBufOff'] = { fg = comment_fg }, 26 | ['BfLineBufOnModified'] = { fg = normal_fg }, 27 | ['BfLineBufOffModified'] = { fg = comment_fg }, 28 | ['BfLineBufOnClose'] = { fg = ch.lib.hl:get('diagnostic', 'error') }, 29 | ['BfLineBufOffClose'] = { link = 'BfLineBufOff' }, 30 | ['BfLineTabOn'] = { fg = normal_bg, bg = ch.lib.hl:get('ui', 'accent') }, 31 | ['BfLineTabOff'] = { fg = title_fg }, 32 | ['BfLineTabCloseBtn'] = { link = 'BfLineTabOn' }, 33 | ['BfLineTabNewBtn'] = { link = 'BfTabTitle' }, 34 | ['BfTabTitle'] = { fg = normal_fg, bg = normal_bg }, 35 | ['BfTabTitleSep'] = { fg = normal_bg }, 36 | ['BfLineCloseAllBufsBtn'] = { link = 'BfTabTitle' }, 37 | } 38 | local modes = { 39 | } 40 | hls = vim.tbl_deep_extend('force', hls, modes) 41 | 42 | ch.lib.hl.apply(hls) 43 | end 44 | 45 | return M 46 | -------------------------------------------------------------------------------- /lua/ch/ui/bufferline/init.lua: -------------------------------------------------------------------------------- 1 | -- adapted from @NvChad https://github.com/NvChad/ui/blob/1737a2a98e18b635480756e817564b60ff31fc53/lua/nvchad/tabufline/init.lua 2 | 3 | local M = {} 4 | local api = vim.api 5 | 6 | M.bufilter = function() 7 | local bufs = vim.t.bufs or nil 8 | 9 | if not bufs then 10 | return {} 11 | end 12 | 13 | for i, nr in ipairs(bufs) do 14 | if not vim.api.nvim_buf_is_valid(nr) then 15 | table.remove(bufs, i) 16 | end 17 | end 18 | 19 | vim.t.bufs = bufs 20 | return bufs 21 | end 22 | 23 | M.get_buf_index = function(bufnr) 24 | for i, value in ipairs(M.bufilter()) do 25 | if value == bufnr then 26 | return i 27 | end 28 | end 29 | end 30 | 31 | M.bufferline_next = function() 32 | local bufs = M.bufilter() or {} 33 | local cur_buf_index = M.get_buf_index(api.nvim_get_current_buf()) 34 | 35 | if not cur_buf_index then 36 | vim.cmd("b" .. vim.t.bufs[1]) 37 | return 38 | end 39 | 40 | vim.cmd(cur_buf_index == #bufs and "b" .. bufs[1] or "b" .. bufs[cur_buf_index + 1]) 41 | end 42 | 43 | M.bufferline_prev = function() 44 | local bufs = M.bufilter() or {} 45 | local cur_buf_index = M.get_buf_index(api.nvim_get_current_buf()) 46 | 47 | if not cur_buf_index then 48 | vim.cmd("b" .. vim.t.bufs[1]) 49 | return 50 | end 51 | 52 | vim.cmd(cur_buf_index == 1 and "b" .. bufs[#bufs] or "b" .. bufs[cur_buf_index - 1]) 53 | end 54 | 55 | M.close_buffer = function(bufnr) 56 | if vim.bo.buftype == "terminal" then 57 | vim.cmd(vim.bo.buflisted and "set nobl | enew" or "hide") 58 | else 59 | if not vim.t.bufs then 60 | vim.cmd "bd" 61 | return 62 | end 63 | 64 | bufnr = bufnr or api.nvim_get_current_buf() 65 | local cur_buf_index = M.get_buf_index(bufnr) 66 | local bufhidden = vim.bo.bufhidden 67 | 68 | -- force close floating wins 69 | if bufhidden == "wipe" then 70 | vim.cmd "bw" 71 | return 72 | 73 | -- handle listed bufs 74 | elseif cur_buf_index and #vim.t.bufs > 1 then 75 | local new_buf_index = cur_buf_index == #vim.t.bufs and -1 or 1 76 | vim.cmd("b" .. vim.t.bufs[cur_buf_index + new_buf_index]) 77 | 78 | -- handle unlisted 79 | elseif not vim.bo.buflisted then 80 | local tmpbufnr = vim.t.bufs[1] 81 | 82 | if vim.g.nv_previous_buf and vim.api.nvim_buf_is_valid(vim.g.nv_previous_buf) then 83 | tmpbufnr = vim.g.nv_previous_buf 84 | end 85 | 86 | vim.cmd("b" .. tmpbufnr .. " | bw" .. bufnr) 87 | return 88 | else 89 | vim.cmd "enew" 90 | end 91 | 92 | if not (bufhidden == "delete") then 93 | vim.cmd("confirm bd" .. bufnr) 94 | end 95 | end 96 | 97 | vim.cmd "redrawtabline" 98 | end 99 | 100 | -- closes tab + all of its buffers 101 | M.close_all_bufs = function(action) 102 | local bufs = vim.t.bufs 103 | 104 | if action == "close_tab" then 105 | vim.cmd "tabclose" 106 | end 107 | 108 | for _, buf in ipairs(bufs) do 109 | M.close_buffer(buf) 110 | end 111 | 112 | if action ~= "close_tab" then 113 | vim.cmd "enew" 114 | end 115 | end 116 | 117 | -- closes all bufs except current one 118 | M.close_other_bufs = function() 119 | for _, buf in ipairs(vim.t.bufs) do 120 | if buf ~= api.nvim_get_current_buf() then 121 | vim.api.nvim_buf_delete(buf, {}) 122 | end 123 | end 124 | 125 | vim.cmd "redrawtabline" 126 | end 127 | 128 | -- closes all other buffers right or left 129 | M.close_bufs_at_direction = function(x) 130 | local bufindex = M.get_buf_index(api.nvim_get_current_buf()) 131 | 132 | for i, bufnr in ipairs(vim.t.bufs) do 133 | if (x == "left" and i < bufindex) or (x == "right" and i > bufindex) then 134 | M.close_buffer(bufnr) 135 | end 136 | end 137 | end 138 | 139 | M.move_buf = function(n) 140 | local bufs = vim.t.bufs 141 | 142 | for i, bufnr in ipairs(bufs) do 143 | if bufnr == vim.api.nvim_get_current_buf() then 144 | if n < 0 and i == 1 or n > 0 and i == #bufs then 145 | bufs[1], bufs[#bufs] = bufs[#bufs], bufs[1] 146 | else 147 | bufs[i], bufs[i + n] = bufs[i + n], bufs[i] 148 | end 149 | 150 | break 151 | end 152 | end 153 | 154 | vim.t.bufs = bufs 155 | vim.cmd "redrawtabline" 156 | end 157 | 158 | -- btn onclick functions -- 159 | 160 | vim.g.BfTabsToggled = 0 161 | 162 | vim.cmd [[ 163 | function! Bf_GoToBuf(bufnr,_,__,___) 164 | execute 'b'..a:bufnr 165 | endfunction 166 | ]] 167 | 168 | vim.cmd [[ 169 | function! Bf_KillBuf(bufnr,_,__,___) 170 | call luaeval('require("ch.ui.bufferline").close_buffer(_A)', a:bufnr) 171 | endfunction 172 | ]] 173 | 174 | vim.cmd [[ 175 | function! Bf_NewTab(_,__,___,____) 176 | tabnew 177 | endfunction 178 | ]] 179 | vim.cmd [[ 180 | function! Bf_GotoTab(tabnr,_,__,___) 181 | execute a:tabnr ..'tabnext' 182 | endfunction 183 | ]] 184 | vim.cmd [[ 185 | function! Bf_TabClose(_,__,___,____) 186 | lua require('ch.ui.bufferline').close_all_bufs('close_tab') 187 | endfunction 188 | ]] 189 | vim.cmd [[ 190 | function! Bf_CloseAllBufs(_,__,___,____) 191 | lua require('ch.ui.bufferline').close_all_bufs() 192 | endfunction 193 | ]] 194 | vim.cmd [[ 195 | function! Bf_ToggleTabs(_,__,___,____) 196 | let g:BfTabsToggled = !g:BfTabsToggled | redrawtabline 197 | endfunction 198 | ]] 199 | 200 | return M 201 | -------------------------------------------------------------------------------- /lua/ch/ui/bufferline/load.lua: -------------------------------------------------------------------------------- 1 | -- adapted from @NvChad https://github.com/NvChad/ui/blob/1737a2a98e18b635480756e817564b60ff31fc53/lua/nvchad/tabufline/lazyload.lua 2 | 3 | return { 4 | setup = function() 5 | local opts = ch.lib.options:get('ui', 'bufferline') 6 | local ok, bufferline = SR_L 'ch.ui.bufferline' 7 | if not ok then return end 8 | 9 | require 'ch.ui.bufferline.hl'.setup_highlights() 10 | 11 | -- store listed buffers in tab local var 12 | vim.t.bufs = vim.api.nvim_list_bufs() 13 | 14 | local listed_bufs = {} 15 | 16 | for _, val in ipairs(vim.t.bufs) do 17 | if vim.bo[val].buflisted then 18 | table.insert(listed_bufs, val) 19 | end 20 | end 21 | 22 | vim.t.bufs = listed_bufs 23 | 24 | -- autocmds for tabufline -> store bufnrs on bufadd, bufenter events 25 | -- thx to https://github.com/ii14 & stores buffer per tab -> table 26 | vim.api.nvim_create_autocmd({ 'BufAdd', 'BufEnter', 'tabnew' }, { 27 | group = ch.group_id, 28 | callback = function(args) 29 | local bufs = vim.t.bufs 30 | 31 | if vim.t.bufs == nil then 32 | vim.t.bufs = vim.api.nvim_get_current_buf() == args.buf and {} 33 | or { args.buf } 34 | else 35 | -- check for duplicates 36 | if 37 | not vim.tbl_contains(bufs, args.buf) 38 | and (args.event == 'BufEnter' or vim.bo[args.buf].buflisted or args.buf ~= vim.api.nvim_get_current_buf()) 39 | and vim.api.nvim_buf_is_valid(args.buf) 40 | and vim.bo[args.buf].buflisted 41 | then 42 | table.insert(bufs, args.buf) 43 | vim.t.bufs = bufs 44 | end 45 | end 46 | 47 | -- remove unnamed buffer which isnt current buf & modified 48 | if args.event == 'BufAdd' then 49 | local first_buf = vim.t.bufs[1] 50 | 51 | if 52 | #vim.api.nvim_buf_get_name(first_buf) == 0 53 | and not vim.api.nvim_get_option_value('modified', { buf = first_buf }) 54 | then 55 | table.remove(bufs, 1) 56 | vim.t.bufs = bufs 57 | end 58 | end 59 | end, 60 | }) 61 | 62 | vim.api.nvim_create_autocmd('BufDelete', { 63 | group = ch.group_id, 64 | callback = function(args) 65 | for _, tab in ipairs(vim.api.nvim_list_tabpages()) do 66 | local bufs = vim.t[tab].bufs 67 | if bufs then 68 | for i, bufnr in ipairs(bufs) do 69 | if bufnr == args.buf then 70 | table.remove(bufs, i) 71 | vim.t[tab].bufs = bufs 72 | break 73 | end 74 | end 75 | end 76 | end 77 | end, 78 | }) 79 | end, 80 | } 81 | -------------------------------------------------------------------------------- /lua/ch/ui/bufferline/modules.lua: -------------------------------------------------------------------------------- 1 | local api = vim.api 2 | local fn = vim.fn 3 | 4 | local bufferline_config = ch.lib.options:get('ui', 'bufferline') 5 | 6 | local separator_style = ch.config.ui.separator_style 7 | ---@type table<'left'|'right',string> 8 | ---@diagnostic disable-next-line: assign-type-mismatch 9 | local separators = ch.lib.icons.separator[separator_style] 10 | if not separators then 11 | separators = { left = '', right = '' } 12 | end 13 | 14 | local bufisvalid = function(bufnr) 15 | return vim.api.nvim_buf_is_valid(bufnr) and vim.bo[bufnr].buflisted 16 | end 17 | 18 | local function new_hl(group1, group2) 19 | local fg = fn.synIDattr(fn.synIDtrans(fn.hlID(group1)), "fg#") 20 | local bg = fn.synIDattr(fn.synIDtrans(fn.hlID(group2)), "bg#") 21 | api.nvim_set_hl(0, "Bfline" .. group1 .. group2, { fg = fg, bg = bg }) 22 | return "%#" .. "Bfline" .. group1 .. group2 .. "#" 23 | end 24 | 25 | local function get_btns_width() -- close, theme toggle btn etc 26 | local width = 6 27 | if fn.tabpagenr "$" ~= 1 then 28 | width = width + ((3 * fn.tabpagenr "$") + 2) + 10 29 | width = not vim.g.BfTabsToggled and 8 or width 30 | end 31 | return width 32 | end 33 | 34 | local function add_fileInfo(name, bufnr) 35 | -- check for same buffer names under different dirs 36 | for _, value in ipairs(vim.t.bufs) do 37 | if bufisvalid(value) then 38 | if name == fn.fnamemodify(api.nvim_buf_get_name(value), ":t") and value ~= bufnr then 39 | local other = {} 40 | for match in (vim.fs.normalize(api.nvim_buf_get_name(value)) .. "/"):gmatch("(.-)" .. "/") do 41 | table.insert(other, match) 42 | end 43 | 44 | local current = {} 45 | for match in (vim.fs.normalize(api.nvim_buf_get_name(bufnr)) .. "/"):gmatch("(.-)" .. "/") do 46 | table.insert(current, match) 47 | end 48 | 49 | name = current[#current] 50 | 51 | for i = #current - 1, 1, -1 do 52 | local value_current = current[i] 53 | local other_current = other[i] 54 | 55 | if value_current ~= other_current then 56 | if (#current - i) < 2 then 57 | name = value_current .. "/" .. name 58 | else 59 | name = value_current .. "/../" .. name 60 | end 61 | break 62 | end 63 | end 64 | break 65 | end 66 | end 67 | end 68 | 69 | -- padding around bufname; 24 = bufame length (icon + filename) 70 | local padding = (24 - #name - 5) / 2 71 | local maxname_len = 16 72 | 73 | name = (#name > maxname_len and string.sub(name, 1, 14) .. "..") or name 74 | name = (api.nvim_get_current_buf() == bufnr and "%#BfLineBufOn# " .. name) or ("%#BfLineBufOff# " .. name) 75 | 76 | local str = name 77 | return ch.lib.fmt.space(str, padding) 78 | end 79 | 80 | local function style_buffer_tab(nr) 81 | local close_btn = "%" .. nr .. "@Bf_KillBuf@ 󰅖 %X" 82 | local name = (#api.nvim_buf_get_name(nr) ~= 0) and fn.fnamemodify(api.nvim_buf_get_name(nr), ":t") or " No Name " 83 | name = "%" .. nr .. "@Bf_GoToBuf@" .. add_fileInfo(name, nr) .. "%X" 84 | 85 | -- add numbers to each tab in bufferline 86 | if bufferline_config.show_numbers then 87 | for index, value in ipairs(vim.t.bufs) do 88 | if nr == value then 89 | name = name .. index 90 | break 91 | end 92 | end 93 | end 94 | 95 | -- color close btn for focused / hidden buffers 96 | if nr == api.nvim_get_current_buf() then 97 | close_btn = (vim.bo[0].modified and "%" .. nr .. "@Bf_KillBuf@%#BfLineBufOnModified#  ") 98 | or ("%#BfLineBufOnClose#" .. close_btn) 99 | name = "%#BfLineBufOn#" .. name .. close_btn 100 | else 101 | close_btn = (vim.bo[nr].modified and "%" .. nr .. "@Bf_KillBuf@%#BfLineBufOffModified#  ") 102 | or ("%#BfLineBufOffClose#" .. close_btn) 103 | name = "%#BfLineBufOff#" .. name .. close_btn 104 | end 105 | 106 | return name 107 | end 108 | 109 | -- components -- 110 | local M = {} 111 | 112 | M.bufferlist = function() 113 | local buffers = {} -- buffersults 114 | local available_space = vim.o.columns - get_btns_width() 115 | local current_buf = api.nvim_get_current_buf() 116 | local has_current = false -- have we seen current buffer yet? 117 | 118 | for _, bufnr in ipairs(vim.t.bufs) do 119 | if bufisvalid(bufnr) then 120 | if ((#buffers + 1) * 21) > available_space then 121 | if has_current then 122 | break 123 | end 124 | 125 | table.remove(buffers, 1) 126 | end 127 | 128 | has_current = (bufnr == current_buf and true) or has_current 129 | table.insert(buffers, style_buffer_tab(bufnr)) 130 | end 131 | end 132 | 133 | vim.g.visibuffers = buffers 134 | return table.concat(buffers) .. "%#BfLineFill#" .. "%=" -- buffers + empty space 135 | end 136 | 137 | M.tablist = function() 138 | local result, number_of_tabs = "", fn.tabpagenr "$" 139 | 140 | if number_of_tabs > 1 then 141 | for i = 1, number_of_tabs, 1 do 142 | local tab_hl = ((i == fn.tabpagenr()) and "%#BfLineTabOn# ") or "%#BfLineTabOff# " 143 | result = result .. ("%" .. i .. "@Bf_GotoTab@" .. tab_hl .. i .. " ") 144 | result = (i == fn.tabpagenr() and result .. "%#BfLineTabCloseBtn#" .. "%@Bf_TabClose@󰅙 %X") or result 145 | end 146 | 147 | local is_open = vim.g.BfTabsToggled ~= 1 148 | 149 | local new_tabtn = "%#BfTabTitleSep#" .. separators.left .. "%#BfLineTabNewBtn#" .. "%@Bf_NewTab@  %X" 150 | local tabstoggleBtn = "%@Bf_ToggleTabs@%#BfTabTitleSep#" .. (is_open and '' or separators.left) 151 | .. "%#BfTabTitle# TABS " .. (is_open and '' or ch.lib.fmt.space('')) .. " %X" 152 | 153 | local function tabs_closed() 154 | return tabstoggleBtn 155 | end 156 | local function tabs_open() 157 | return new_tabtn .. tabstoggleBtn .. result 158 | end 159 | 160 | return is_open and tabs_open() 161 | or tabs_closed() 162 | end 163 | 164 | return "" 165 | end 166 | 167 | M.buttons = function() 168 | local CloseAllBufsBtn = "%@Bf_CloseAllBufs@%#BfLineCloseAllBufsBtn#" .. " 󰅖 " .. "%X" 169 | return CloseAllBufsBtn 170 | end 171 | 172 | M.run = function() 173 | local modules = { 174 | M.bufferlist(), 175 | M.tablist(), 176 | M.buttons(), 177 | } 178 | 179 | if bufferline_config.overriden_modules then 180 | bufferline_config.overriden_modules(modules) 181 | end 182 | 183 | return table.concat(modules) 184 | end 185 | 186 | return M 187 | -------------------------------------------------------------------------------- /lua/ch/ui/cursor.lua: -------------------------------------------------------------------------------- 1 | return { 2 | setcursor = function(_, mode) 3 | local hls = require 'ch.ui.cursor'.create() 4 | if not hls or not hls.hl or not hls.hl[mode] then return end 5 | 6 | ch.lib.hl.apply({ CursorLine = hls.hl[mode] }) 7 | end, 8 | setup = function() 9 | vim.opt.cursorline = true 10 | local hls = require 'ch.ui.cursor'.create() 11 | if not hls or not hls.cursor then return end 12 | ch.lib.hl.apply(hls.cursor) 13 | 14 | require 'ch.ui.cursor'.setcursor 'normal' 15 | ch.lib.autocmd.create { 16 | event = 'InsertEnter', priority = 2, 17 | desc = 'set insert mode cursor hl', 18 | fn = function(ev) 19 | require 'ch.ui.cursor'.setcursor (ev.buf, 'insert') 20 | end 21 | } 22 | ch.lib.autocmd.create { 23 | event = 'InsertLeave', priority = 2, 24 | desc = 'set normal mode cursor hl', 25 | fn = function(ev) 26 | require 'ch.ui.cursor'.setcursor (ev.buf, 'normal') 27 | end 28 | } 29 | end, 30 | create = function() 31 | local normal = ch.lib.hl:get('ui', 'bg') 32 | local cursor = ch.lib.hl:get('ui', 'fg') 33 | 34 | local main = ch.lib.hl:get('ui', 'current') 35 | 36 | local accent = ch.lib.hl:get('ui', 'accent') 37 | local line = ch.lib.hl:get('syntax', 'string') 38 | 39 | -- overlay on bg 40 | local vibrant = ch.lib.color.color_overlay(0.07, { normal, line }) 41 | -- brighten 42 | vibrant = ch.lib.color.hsl_mix({ lum = 0.07 }, { vibrant, ch.lib.color.rgb{ r = 200, g = 200, b = 200 } }) 43 | return { 44 | hl = { 45 | normal = { bg = main }, 46 | insert = { bg = vibrant }, 47 | }, 48 | cursor = { 49 | NCursor = { bg = cursor }, 50 | ICursor = { bg = accent }, 51 | }, 52 | } 53 | end, 54 | } 55 | -------------------------------------------------------------------------------- /lua/ch/ui/handles.lua: -------------------------------------------------------------------------------- 1 | local api = vim.api 2 | 3 | local model = require 'yosu.model'({ 4 | items = {}, 5 | task_pos = {}, 6 | }, { 7 | title = 'handles', 8 | size = { 9 | width = 80, 10 | height = 0.6, 11 | }, 12 | }) 13 | 14 | function model:init() 15 | local _items = ch.handle 16 | ---@type table 17 | local items = {} 18 | for event, event_t in pairs(_items) do 19 | items[event] = vim.iter(event_t):map(function(t) 20 | return { 21 | expand = false, 22 | items = t, 23 | } 24 | end):totable() 25 | end 26 | 27 | self.data.items = items 28 | self.data.task_pos = {} 29 | 30 | api.nvim_set_hl(0, 'UISpecial', { link = 'Special' }) 31 | api.nvim_set_hl(0, 'UIEvent', { fg = ch.lib.hl:get('syntax', 'event') }) 32 | api.nvim_set_hl(0, 'UITask', { fg = ch.lib.hl:get('syntax', 'fn') }) 33 | api.nvim_set_hl(0, 'UIItem', { fg = ch.lib.hl:get('syntax', 'field') }) 34 | 35 | self:add_mapping('n', '', 'open_section') 36 | end 37 | 38 | local indent = function(n) 39 | return string.rep(' ', vim.o.shiftwidth * n) 40 | end 41 | 42 | local fmt = function(tpe, ...) 43 | local fmts = { 44 | event = ch.lib.icons.syntax.event..' %s', 45 | priority = function(priority) 46 | return (ch.lib.icons.info.loaded .. ' %s'):format(priority) 47 | end, 48 | task = function(desc) 49 | return (ch.lib.icons.info.loaded .. ' %s'):format(desc or ch.lib.icons.syntax.fn) 50 | end, 51 | } 52 | local f = fmts[tpe] 53 | if not f then 54 | return '' 55 | end 56 | if type(f) == 'function' then 57 | return f(...) 58 | end 59 | return f:format(...) 60 | end 61 | 62 | function model:view() 63 | local lines = {} 64 | self.data.task_pos = {} 65 | 66 | ---@type table 67 | local items = self.data.items 68 | 69 | local y = 0 70 | lines = {} 71 | for event, event_t in pairs(items) do 72 | local event_str = fmt('event', event) 73 | local prefix = indent(1) 74 | y = y + 1 75 | lines[y] = prefix .. event_str 76 | self:add_hl( 77 | 'Event', 78 | y, 79 | string.len(prefix), 80 | string.len(prefix) + 1 + string.len(event_str) + 1 81 | ) 82 | 83 | for priority_i, priority_t in pairs(event_t) do 84 | local task_str = fmt('priority', priority_i) 85 | prefix = indent(2) 86 | y = y + 1 87 | lines[y] = prefix .. task_str 88 | self:add_hl('Special', y, string.len(prefix), string.len(prefix) + 3) 89 | self:add_hl( 90 | 'Task', 91 | y, 92 | string.len(prefix) + 3, 93 | string.len(prefix) + 2 + string.len(task_str) 94 | ) 95 | self.data.task_pos[y] = { event, priority_i } 96 | 97 | if priority_t.expand then 98 | for _, v in ipairs(priority_t.items) do 99 | local item_str = fmt('task', v.desc) 100 | prefix = indent(3) 101 | y = y + 1 102 | lines[y] = prefix .. item_str 103 | self:add_hl('Special', y, string.len(prefix), string.len(prefix) + 3) 104 | self:add_hl( 105 | 'Item', 106 | y, 107 | string.len(prefix) + 1, 108 | string.len(prefix) + 1 + string.len(item_str) + 1 109 | ) 110 | end 111 | end 112 | end 113 | end 114 | 115 | return lines 116 | end 117 | 118 | function model:update(msg) 119 | local fn = { 120 | cursormove = function() 121 | return true 122 | end, 123 | open_section = function() 124 | local item = self.data.task_pos[self.internal.cursor[1]] 125 | if not item or #item < 2 then 126 | return 127 | end 128 | local event, task = unpack(item, 1, 2) 129 | self.data.items[event][task].expand = 130 | not self.data.items[event][task].expand 131 | return true 132 | end, 133 | } 134 | 135 | if not fn[msg] or type(fn[msg]) ~= 'function' then 136 | return 137 | end 138 | return fn[msg]() 139 | end 140 | 141 | return model 142 | -------------------------------------------------------------------------------- /lua/ch/ui/status.lua: -------------------------------------------------------------------------------- 1 | local api = vim.api 2 | 3 | local model = require 'yosu.model'({ 4 | items = {}, 5 | module_order = {}, 6 | task_pos = {}, 7 | }, { 8 | title = 'status', 9 | size = { 10 | width = 80, 11 | height = 0.6, 12 | }, 13 | }) 14 | 15 | function model:init() 16 | local json_log_path = ('%s.json'):format(ch.path.log) 17 | local fh_json = io.open(json_log_path, 'r') 18 | if not fh_json then 19 | ch.log(('could not open log file [%s]'):format(json_log_path), 'error') 20 | return 21 | end 22 | local json_content = vim.json.decode(fh_json:read '*a') 23 | local _items = {} 24 | if not json_content or not json_content.items then 25 | ch.log( 26 | ('could not deserialize log file [%s]'):format(json_log_path), 27 | 'error' 28 | ) 29 | return 30 | end 31 | _items = json_content.items 32 | local module_order = {} 33 | local _module_order = {} 34 | local items = {} 35 | vim.iter(ipairs(_items)):each(function(_, item) 36 | local split = vim.split(item[1], '%.') 37 | if #split > 1 then 38 | local module, task = unpack(split, 1, 2) 39 | if not _module_order[module] then 40 | _module_order[module] = true 41 | module_order[#module_order + 1] = module 42 | end 43 | items[module] = items[module] or {} 44 | items[module][task] = items[module][task] 45 | or { expand = false, items = {} } 46 | items[module][task].items[#items[module][task].items + 1] = 47 | { item[2], item[3] } 48 | end 49 | end) 50 | 51 | self.data.items = items 52 | self.data.module_order = module_order 53 | self.data.task_pos = {} 54 | 55 | api.nvim_set_hl(0, 'UISpecial', { link = 'Special' }) 56 | api.nvim_set_hl( 57 | 0, 58 | 'UIModule', 59 | { fg = ch.lib.hl:get('syntax', 'module') } 60 | ) 61 | api.nvim_set_hl(0, 'UITask', { fg = ch.lib.hl:get('syntax', 'fn') }) 62 | api.nvim_set_hl(0, 'UIItem', { fg = ch.lib.hl:get('syntax', 'field') }) 63 | 64 | self:add_mapping('n', '', 'open_section') 65 | end 66 | 67 | local indent = function(n) 68 | return string.rep(' ', vim.o.shiftwidth * n) 69 | end 70 | 71 | local fmt = function(tpe, ...) 72 | local fmts = { 73 | module = ':%s', 74 | task = ch.lib.icons.info.loaded .. ' %s', 75 | log = function(level, msg) 76 | if level == 'debug' then 77 | return (ch.lib.icons.ui.item_prefix .. ' %s'):format(msg) 78 | end 79 | return (ch.lib.icons.ui.item_prefix .. ' [%s] %s'):format(level, msg) 80 | end, 81 | } 82 | local f = fmts[tpe] 83 | if not f then 84 | return '' 85 | end 86 | if type(f) == 'function' then 87 | return f(...) 88 | end 89 | return f:format(...) 90 | end 91 | 92 | function model:view() 93 | local lines = {} 94 | self.data.task_pos = {} 95 | 96 | local module_order = self.data.module_order 97 | local items = self.data.items 98 | 99 | local y = 0 100 | lines = {} 101 | vim.iter(ipairs(module_order)):each(function(_, module) 102 | local module_str = fmt('module', module) 103 | local prefix = indent(1) 104 | y = y + 1 105 | lines[y] = prefix .. module_str 106 | self:add_hl( 107 | 'Module', 108 | y, 109 | string.len(prefix), 110 | string.len(prefix) + 1 + string.len(module_str) + 1 111 | ) 112 | 113 | vim.iter(pairs(items[module])):each(function(task, task_props) 114 | local task_str = fmt('task', task) 115 | prefix = indent(2) 116 | y = y + 1 117 | lines[y] = prefix .. task_str 118 | self:add_hl('Special', y, string.len(prefix), string.len(prefix) + 3) 119 | self:add_hl( 120 | 'Task', 121 | y, 122 | string.len(prefix) + 3, 123 | string.len(prefix) + 2 + string.len(task_str) 124 | ) 125 | self.data.task_pos[y] = { module, task } 126 | 127 | if task_props.expand then 128 | vim.iter(ipairs(task_props.items)):each(function(_, v) 129 | local item_str = fmt('log', v[1], v[2]) 130 | prefix = indent(3) 131 | y = y + 1 132 | lines[y] = prefix .. item_str 133 | self:add_hl('Special', y, string.len(prefix), string.len(prefix) + 3) 134 | self:add_hl( 135 | 'Item', 136 | y, 137 | string.len(prefix) + 1, 138 | string.len(prefix) + 1 + string.len(item_str) + 1 139 | ) 140 | end) 141 | end 142 | end) 143 | end) 144 | 145 | return lines 146 | end 147 | 148 | function model:update(msg) 149 | local fn = { 150 | cursormove = function() 151 | return true 152 | end, 153 | open_section = function() 154 | local item = self.data.task_pos[self.internal.cursor[1]] 155 | if not item or #item < 2 then 156 | return 157 | end 158 | local module, task = unpack(item, 1, 2) 159 | self.data.items[module][task].expand = 160 | not self.data.items[module][task].expand 161 | return true 162 | end, 163 | } 164 | 165 | if not fn[msg] or type(fn[msg]) ~= 'function' then 166 | return 167 | end 168 | return fn[msg]() 169 | end 170 | 171 | return model 172 | -------------------------------------------------------------------------------- /lua/ch/ui/statusline/hl.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | M.setup_highlights = function() 4 | ch.lib.autocmd.create { 5 | event = 'ColorScheme', priority = GC.priority.handle.colorscheme.plugin, 6 | desc = 'load statusline hls', 7 | fn = function(_) 8 | require 'ch.ui.statusline.hl'.apply_highlights() 9 | end 10 | } 11 | end 12 | 13 | ---@param palette table<'normal'|'insert'|'visual'|'command', { a?: vim.api.keyset.hl_info, b?: vim.api.keyset.hl_info, c?: vim.api.keyset.hl_info }> 14 | ---@return boolean success 15 | local function apply_palette(palette) 16 | local fallback = palette.normal 17 | for _, s in ipairs({'a', 'b', 'c'}) do 18 | if not fallback[s] then 19 | return false 20 | end 21 | end 22 | ---@param mode 'normal'|'insert'|'visual'|'command' 23 | ---@param section 'a'|'b'|'c' 24 | ---@return vim.api.keyset.hl_info 25 | local function palget(mode, section) 26 | if palette[mode] and palette[mode][section] then 27 | return palette[mode][section] 28 | end 29 | return fallback[section] 30 | end 31 | 32 | local hls = { 33 | ['St_normal'] = { fg = palget('normal', 'c').fg, palget('normal', 'c').fg }, 34 | } 35 | local sections = {} 36 | for _, mode in ipairs({'normal', 'insert', 'visual', 'command'}) do 37 | for _, section in ipairs({'a','b','c'}) do 38 | sections['St_'..mode..'_'..section] = palget(mode, section) 39 | end 40 | sections['St_'..mode..'_x'] = { link = 'St_' .. mode .. '_c' } 41 | sections['St_'..mode..'_y'] = { link = 'St_' .. mode .. '_b' } 42 | sections['St_'..mode..'_z'] = { link = 'St_' .. mode .. '_a' } 43 | end 44 | hls = vim.tbl_deep_extend('force', hls, sections) 45 | local sections_sep = {} 46 | for _, mode in ipairs({'normal', 'insert', 'visual', 'command'}) do 47 | sections_sep['St_'..mode..'_a_sep'] = { fg = sections['St_'..mode..'_a'].bg, bg = sections['St_'..mode..'_b'].bg } 48 | sections_sep['St_'..mode..'_b_sep'] = { fg = sections['St_'..mode..'_b'].bg, bg = sections['St_'..mode..'_c'].bg } 49 | sections_sep['St_'..mode..'_y_sep'] = { link = 'St_'..mode..'_b_sep' } 50 | sections_sep['St_'..mode..'_z_sep'] = { link = 'St_'..mode..'_a_sep' } 51 | end 52 | hls = vim.tbl_deep_extend('force', hls, sections_sep) 53 | 54 | ch.lib.hl.apply(hls) 55 | 56 | return true 57 | end 58 | 59 | M.apply_highlights = function() 60 | local scheme_name = ch.config.ui.colorscheme 61 | local ok, palette = pcall(require, ('lualine.themes.%s'):format(scheme_name)) 62 | if not ok then 63 | local normal_bg = ch.lib.hl:get('ui', 'bg') 64 | local normal_fg = ch.lib.hl:get('ui', 'fg') 65 | 66 | local accent_bg = ch.lib.hl:get('ui', 'border') 67 | local comment_fg = ch.lib.hl:get('ui', 'comment') 68 | palette = { 69 | normal = { 70 | a = { fg = normal_bg, bg = ch.lib.hl:get('ui', 'accent') }, 71 | b = { fg = normal_fg, bg = accent_bg }, 72 | c = { fg = comment_fg, bg = ch.lib.hl:get('ui', 'bg_accent') }, 73 | }, 74 | insert = { 75 | a = { fg = normal_bg, bg = ch.lib.hl:get('ui', 'focus') }, 76 | }, 77 | visual = { 78 | a = { fg = normal_bg, bg = ch.lib.hl:get('syntax', 'constant') }, 79 | }, 80 | } 81 | end 82 | apply_palette(palette) 83 | end 84 | 85 | return M 86 | -------------------------------------------------------------------------------- /lua/ch/ui/statusline/init.lua: -------------------------------------------------------------------------------- 1 | local utils = R 'ch.ui.statusline.utils' 2 | 3 | local M = {} 4 | 5 | ---@alias NvMode 'n'|'no'|'nov'|'noV'|'noCTRL-V'|'niI'|'niR'|'niV'|'nt'|'ntT'|'v'|'vs'|'V'|'Vs'|'␖'|'i'|'ic'|'ix'|'t'|'R'|'Rc'|'Rx'|'Rv'|'Rvc'|'Rvx'|'s'|'S'|'␓'|'c'|'cv'|'ce'|'r'|'rm'|'r?'|'x'|'!' 6 | 7 | ---@type NvMode 8 | _G.nvmode = 'n' 9 | 10 | local separator_style = ch.config.ui.separator_style 11 | 12 | ---@type { left: string, right: string } 13 | ---@diagnostic disable-next-line: assign-type-mismatch 14 | local separators = ch.lib.icons.separator[separator_style] 15 | if not separators then 16 | separators = { left = '', right = '' } 17 | end 18 | local sep = { 19 | r = separators.right, 20 | l = separators.left, 21 | mid = '%=', 22 | } 23 | 24 | local config_modules = { 25 | a = { 'mode' }, 26 | b = { 'fileinfo' }, 27 | c = { 'git_branch', 'lsp_diagnostics' }, 28 | m = { { 'macro', function(key) return ('recording %s ...'):format(key) end } }, 29 | x = { 'git_status', 'lsp_status' }, 30 | y = { 'cwd' }, 31 | z = { 'textinfo', 'cursor_position' }, 32 | } 33 | 34 | local function parse_c(c, ...) 35 | if type(c) == 'string' then 36 | local ok, mod = pcall(require, ('ch.ui.statusline.modules.%s'):format(c)) 37 | if ok then 38 | ---@diagnostic disable-next-line: redefined-local 39 | local ok, str = pcall(mod, ...) 40 | if ok then 41 | return str 42 | end 43 | end 44 | else 45 | local ok, str = pcall(c, ...) 46 | if ok then 47 | return str 48 | end 49 | end 50 | end 51 | 52 | local function parse_components(components) 53 | return vim.iter(components):map(function(c) 54 | if type(c) == 'table' then 55 | return parse_c(unpack(c)) 56 | end 57 | return parse_c(c) 58 | end):totable() 59 | end 60 | 61 | M.parse = function() 62 | local modules = {} 63 | 64 | ---@type { ['a'|'b'|'c'|'x'|'y'|'z']: string[] } 65 | local _modules = { 66 | a = parse_components(config_modules.a), 67 | b = parse_components(config_modules.b), 68 | c = parse_components(config_modules.c), 69 | m = parse_components(config_modules.m), 70 | x = parse_components(config_modules.x), 71 | y = parse_components(config_modules.y), 72 | z = parse_components(config_modules.z), 73 | } 74 | 75 | local m = utils.getmode() 76 | 77 | modules[#modules + 1] = m.hl('a') 78 | .. table.concat(_modules.a) 79 | .. m.sep_hl('a') 80 | .. sep.r 81 | modules[#modules + 1] = m.hl('b') 82 | .. table.concat(_modules.b) 83 | .. m.sep_hl('b') 84 | .. sep.r 85 | modules[#modules + 1] = m.hl('c') .. table.concat(_modules.c) 86 | modules[#modules + 1] = sep.mid 87 | modules[#modules + 1] = table.concat(_modules.m) 88 | modules[#modules + 1] = sep.mid 89 | modules[#modules + 1] = m.hl('x') .. table.concat(_modules.x) 90 | modules[#modules + 1] = m.sep_hl('y') 91 | .. sep.l 92 | .. m.hl('y') 93 | .. table.concat(_modules.y) 94 | modules[#modules + 1] = m.sep_hl('z') .. sep.l .. m.hl('z') .. table.concat(_modules.z) 95 | 96 | -- return table.concat(_modules) 97 | 98 | return modules 99 | end 100 | 101 | M.run = function() 102 | _G.nvmode = vim.api.nvim_get_mode().mode 103 | local modules = M.parse() 104 | return table.concat(modules) 105 | end 106 | 107 | return M 108 | -------------------------------------------------------------------------------- /lua/ch/ui/statusline/modules/cursor_position.lua: -------------------------------------------------------------------------------- 1 | return function() 2 | local icon = ch.lib.fmt.space(ch.lib.icons.syntax.text) 3 | 4 | local current_line = vim.fn.line('.', vim.g.statusline_winid) 5 | local total_line = vim.fn.line('$', vim.g.statusline_winid) 6 | local text = math.modf((current_line / total_line) * 100) .. tostring '%%' 7 | text = string.format('%4s', text) 8 | 9 | text = (current_line == 1 and 'Top') or text 10 | text = (current_line == total_line and 'Bot') or text 11 | 12 | return icon .. text .. ' ' 13 | end 14 | -------------------------------------------------------------------------------- /lua/ch/ui/statusline/modules/cwd.lua: -------------------------------------------------------------------------------- 1 | return function() 2 | local dir_icon = ch.lib.fmt.space(ch.lib.icons.syntax.folder) 3 | local dir_name = vim.fn.fnamemodify(vim.fn.getcwd(), ":t") 4 | if vim.o.columns > 85 then 5 | return dir_icon .. dir_name .. ' ' 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /lua/ch/ui/statusline/modules/fileinfo.lua: -------------------------------------------------------------------------------- 1 | local utils = require 'ch.ui.statusline.utils' 2 | 3 | return function() 4 | local bufnr = utils.stbufnr() 5 | local icon = '󰈚' 6 | local path = vim.api.nvim_buf_get_name(bufnr) 7 | local name = (path == '' and 'empty') or path:match '([^/\\]+)[/\\]*$' 8 | 9 | if name ~= 'empty' and ch.config.ui.devicons then 10 | local devicons_present, devicons = pcall(require, 'nvim-web-devicons') 11 | 12 | if devicons_present then 13 | local ft_icon = devicons.get_icon(name) 14 | if ft_icon then 15 | icon = ft_icon 16 | end 17 | end 18 | end 19 | 20 | if vim.bo[bufnr].buftype == '' and vim.bo[bufnr].modified then 21 | name = name .. ' ' .. ch.lib.icons.info.non_empty 22 | end 23 | 24 | if vim.bo[bufnr].readonly then 25 | name = name .. ' ' .. ch.lib.icons.info.locked 26 | end 27 | 28 | return ch.lib.fmt.space(icon) .. name .. ' ' 29 | end 30 | -------------------------------------------------------------------------------- /lua/ch/ui/statusline/modules/git_branch.lua: -------------------------------------------------------------------------------- 1 | local utils = require 'ch.ui.statusline.utils' 2 | 3 | return function() 4 | if 5 | not vim.b[utils.stbufnr()].gitsigns_head 6 | or vim.b[utils.stbufnr()].gitsigns_git_status 7 | then 8 | return '' 9 | end 10 | 11 | local git_status = vim.b[utils.stbufnr()].gitsigns_status_dict 12 | 13 | local branch_name = ch.lib.fmt.space(ch.lib.icons.git.branch) 14 | .. git_status.head 15 | 16 | return branch_name 17 | end 18 | -------------------------------------------------------------------------------- /lua/ch/ui/statusline/modules/git_status.lua: -------------------------------------------------------------------------------- 1 | local utils = require 'ch.ui.statusline.utils' 2 | 3 | return function() 4 | if 5 | not vim.b[utils.stbufnr()].gitsigns_head 6 | or vim.b[utils.stbufnr()].gitsigns_git_status 7 | then 8 | return '' 9 | end 10 | 11 | local git_status = vim.b[utils.stbufnr()].gitsigns_status_dict 12 | 13 | local added = (git_status.added and git_status.added ~= 0) 14 | and (ch.lib.fmt.space(ch.lib.icons.diff_status.added) .. git_status.added) 15 | or '' 16 | local changed = (git_status.changed and git_status.changed ~= 0) 17 | and (ch.lib.fmt.space(ch.lib.icons.diff_status.changed) .. git_status.changed) 18 | or '' 19 | local removed = (git_status.removed and git_status.removed ~= 0) 20 | and (ch.lib.fmt.space(ch.lib.icons.diff_status.deleted) .. git_status.removed) 21 | or '' 22 | 23 | return added .. changed .. removed .. ' ' 24 | end 25 | -------------------------------------------------------------------------------- /lua/ch/ui/statusline/modules/lsp_diagnostics.lua: -------------------------------------------------------------------------------- 1 | local utils = require 'ch.ui.statusline.utils' 2 | 3 | return function() 4 | local errors = #vim.diagnostic.get( 5 | utils.stbufnr(), 6 | { severity = vim.diagnostic.severity.ERROR } 7 | ) 8 | local warnings = #vim.diagnostic.get( 9 | utils.stbufnr(), 10 | { severity = vim.diagnostic.severity.WARN } 11 | ) 12 | local info = #vim.diagnostic.get( 13 | utils.stbufnr(), 14 | { severity = vim.diagnostic.severity.INFO } 15 | ) 16 | local hints = #vim.diagnostic.get( 17 | utils.stbufnr(), 18 | { severity = vim.diagnostic.severity.HINT } 19 | ) 20 | 21 | local error_str = '' 22 | if errors and errors > 0 then 23 | error_str = '%#St_lspError#' .. ch.lib.fmt.space(ch.lib.icons.diagnostic.error) .. errors .. ' ' 24 | end 25 | local warning_str = '' 26 | if warnings and warnings > 0 then 27 | warning_str = '%#St_lspWarning#' .. ch.lib.fmt.space(ch.lib.icons.diagnostic.warn) .. warnings .. ' ' 28 | end 29 | local info_str = '' 30 | if info and info > 0 then 31 | info_str = '%#St_lspInfo#' .. ch.lib.fmt.space(ch.lib.icons.diagnostic.info) .. info .. ' ' 32 | end 33 | local hint_str = '' 34 | if hints and hints > 0 then 35 | hint_str = '%#St_lspHints#' .. ch.lib.fmt.space(ch.lib.icons.diagnostic.hint) .. hints .. ' ' 36 | end 37 | 38 | return error_str .. warning_str .. hint_str .. info_str 39 | end 40 | -------------------------------------------------------------------------------- /lua/ch/ui/statusline/modules/lsp_status.lua: -------------------------------------------------------------------------------- 1 | local utils = require 'ch.ui.statusline.utils' 2 | 3 | return function() 4 | local clients = vim.lsp.get_clients({ bufnr = utils.stbufnr() }) 5 | local null = {} 6 | local lsp = {} 7 | for _, client in ipairs(clients) do 8 | if client.name == 'null-ls' then 9 | null[#null+1] = client.name 10 | else 11 | lsp[#lsp+1] = client.name 12 | end 13 | end 14 | 15 | local client = nil 16 | if #lsp == 0 then 17 | if #null > 0 then 18 | client = null[1] 19 | end 20 | else 21 | client = lsp[1] 22 | end 23 | if client then 24 | local str = '' 25 | if vim.o.columns > 100 then 26 | str = str .. ch.lib.fmt.space('') .. 'lsp' .. ch.lib.fmt.space(ch.lib.icons.ui.item_prefix) .. client .. ' ' 27 | else 28 | str = str .. ch.lib.fmt.space('') .. 'lsp' .. ' ' 29 | end 30 | return str 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /lua/ch/ui/statusline/modules/macro.lua: -------------------------------------------------------------------------------- 1 | return function(fmt) 2 | local key = vim.fn.reg_recording() 3 | key = #key > 0 and key or nil 4 | if not key then 5 | return 6 | end 7 | 8 | local str = fmt(key) 9 | return ch.lib.fmt.space(str) 10 | end 11 | -------------------------------------------------------------------------------- /lua/ch/ui/statusline/modules/mode.lua: -------------------------------------------------------------------------------- 1 | local utils = require 'ch.ui.statusline.utils' 2 | 3 | return function() 4 | if not utils.is_activewin() then 5 | return 6 | end 7 | 8 | local m = utils.getmode() 9 | 10 | return ' ' .. m.label 11 | end 12 | -------------------------------------------------------------------------------- /lua/ch/ui/statusline/modules/textinfo.lua: -------------------------------------------------------------------------------- 1 | local utils = require 'ch.ui.statusline.utils' 2 | 3 | return function() 4 | local bufnr = utils.stbufnr() 5 | 6 | if vim.bo[bufnr].buftype == 'nofile' then 7 | return '' 8 | end 9 | 10 | return CUTIL.FILE_INFO(bufnr, true) 11 | end 12 | -------------------------------------------------------------------------------- /lua/ch/ui/statusline/utils.lua: -------------------------------------------------------------------------------- 1 | ---@class vim.var_accessor 2 | ---@field statusline_winid integer 3 | 4 | local utils = {} 5 | 6 | function utils.stwinid() 7 | return vim.g.statusline_winid or 0 8 | end 9 | 10 | function utils.stbufnr() 11 | return vim.api.nvim_win_get_buf(utils.stwinid()) 12 | end 13 | 14 | function utils.is_activewin() 15 | return vim.api.nvim_get_current_win() == utils.stwinid() 16 | end 17 | 18 | ---@type { [NvMode]: { [1]: string, [2]: string } } 19 | utils.modes = { 20 | ["n"] = { "NORMAL", "normal" }, 21 | ["no"] = { "NORMAL (no)", "normal" }, 22 | ["nov"] = { "NORMAL (nov)", "normal" }, 23 | ["noV"] = { "NORMAL (noV)", "normal" }, 24 | ["noCTRL-V"] = { "NORMAL", "normal" }, 25 | ["niI"] = { "NORMAL i", "normal" }, 26 | ["niR"] = { "NORMAL r", "normal" }, 27 | ["niV"] = { "NORMAL v", "normal" }, 28 | ["nt"] = { "NTERMINAL", "normal" }, 29 | ["ntT"] = { "NTERMINAL (ntT)", "normal" }, 30 | 31 | ["v"] = { "VISUAL", "visual" }, 32 | ["vs"] = { "V-CHAR (Ctrl O)", "visual" }, 33 | ["V"] = { "V-LINE", "visual" }, 34 | ["Vs"] = { "V-LINE", "visual" }, 35 | ['\22'] = { "V-BLOCK", "visual" }, 36 | 37 | ["i"] = { "INSERT", "insert" }, 38 | ["ic"] = { "INSERT (completion)", "insert" }, 39 | ["ix"] = { "INSERT completion", "insert" }, 40 | 41 | ["t"] = { "TERMINAL", "normal" }, 42 | 43 | ["R"] = { "REPLACE", "insert" }, 44 | ["Rc"] = { "REPLACE (Rc)", "insert" }, 45 | ["Rx"] = { "REPLACEa (Rx)", "insert" }, 46 | ["Rv"] = { "V-REPLACE", "insert" }, 47 | ["Rvc"] = { "V-REPLACE (Rvc)", "insert" }, 48 | ["Rvx"] = { "V-REPLACE (Rvx)", "insert" }, 49 | 50 | ["s"] = { "SELECT", "visual" }, 51 | ["S"] = { "S-LINE", "visual" }, 52 | ['\19'] = { "S-BLOCK", "visual" }, 53 | ["c"] = { "COMMAND", "command" }, 54 | ["cv"] = { "COMMAND", "command" }, 55 | ["ce"] = { "COMMAND", "command" }, 56 | ["r"] = { "PROMPT", "command" }, 57 | ["rm"] = { "MORE", "command" }, 58 | ["r?"] = { "CONFIRM", "command" }, 59 | ["x"] = { "CONFIRM", "command" }, 60 | ["!"] = { "SHELL", "command" }, 61 | } 62 | 63 | ---@param mode? NvMode 64 | ---@return { label: string, name: string, hl: string, sep_hl: string } 65 | function utils.getmode(mode) 66 | if not _G.nvmode then return utils.getmode 'n' end 67 | if not mode then mode = _G.nvmode end 68 | 69 | local mode_label = utils.modes[mode][1] 70 | local mode_name = utils.modes[mode][2] 71 | 72 | return { 73 | label = mode_label, 74 | name = mode_name, 75 | hl = function(section) 76 | return utils.construct_hl(mode_name, section) 77 | end, 78 | sep_hl = function(section) 79 | return utils.construct_hl(mode_name, section, true) 80 | end, 81 | } 82 | end 83 | 84 | ---@param mode 'normal'|'insert'|'visual'|'command' 85 | ---@param section 'a'|'b'|'c' 86 | ---@param sep? boolean 87 | ---@return string 88 | function utils.construct_hl(mode, section, sep) 89 | return '%#St_' .. mode .. '_' .. (sep and (section .. '_sep') or section) .. '#' 90 | end 91 | 92 | return utils 93 | -------------------------------------------------------------------------------- /lua/ch/ui/term/init.lua: -------------------------------------------------------------------------------- 1 | -- adapted from @NvChad https://github.com/NvChad/nvterm/blob/9d7ba3b6e368243175d38e1ec956e0476fd86ed9/lua/nvterm/init.lua 2 | 3 | ---@alias ch.types.ui.term.type 'horizontal'|'vertical'|'float' 4 | 5 | ---@class ch.types.ui.term.config 6 | ---@field enabled boolean 7 | ---@field terminals ch.types.ui.term.config.terminals 8 | ---@field ui ch.types.ui.term.config.ui 9 | 10 | ---@class ch.types.ui.term.terminal 11 | ---@field open boolean 12 | ---@field buf integer 13 | ---@field win integer 14 | ---@field job_id integer 15 | 16 | ---@class ch.types.ui.term.config.terminals 17 | ---@field list ch.types.ui.term.terminal[] 18 | 19 | ---@class ch.types.ui.term.config.ui 20 | ---@field float vim.api.keyset.win_config 21 | ---@field horizontal ch.types.ui.term.config.ui.split_opts 22 | ---@field vertical ch.types.ui.term.config.ui.split_opts 23 | ---@class ch.types.ui.term.config.ui.split_opts 24 | ---@field location 'rightbelow'|'leftabove'|'left'|'right' 25 | ---@field split_ratio number 26 | 27 | local M = {} 28 | local api = vim.api 29 | 30 | M.set_behavior = function(behavior) 31 | if behavior.autoclose_on_quit.enabled then 32 | local function force_exit() 33 | require('ch.ui.term.terminal').close_all_terms() 34 | api.nvim_command 'qa' 35 | end 36 | api.nvim_create_autocmd({ 'WinClosed' }, { 37 | callback = vim.schedule_wrap(function() 38 | local open_terms = 39 | require('ch.ui.term.terminal').list_active_terms 'win' 40 | 41 | local non_terms = vim.iter(api.nvim_list_wins()):filter(function(win) 42 | return not vim.tbl_contains(open_terms, win) 43 | end) 44 | 45 | if not vim.tbl_isempty(non_terms) then 46 | return 47 | end 48 | 49 | if not behavior.autoclose_on_quit.confirm then 50 | return force_exit() 51 | end 52 | 53 | vim.ui.input( 54 | { prompt = 'Close all terms and quit? (y/N): ' }, 55 | function(input) 56 | if not input or not string.lower(input) == 'y' then 57 | return 58 | end 59 | force_exit() 60 | end 61 | ) 62 | end), 63 | }) 64 | end 65 | if behavior.close_on_exit then 66 | api.nvim_create_autocmd({ 'TermClose' }, { 67 | callback = function(ev) 68 | api.nvim_buf_call( 69 | ev.buf, 70 | vim.schedule_wrap(function() 71 | api.nvim_input '' 72 | end) 73 | ) 74 | end, 75 | }) 76 | end 77 | 78 | if behavior.auto_insert then 79 | api.nvim_create_autocmd({ 'BufEnter' }, { 80 | callback = function() 81 | vim.cmd 'startinsert' 82 | end, 83 | pattern = 'term://*', 84 | }) 85 | 86 | api.nvim_create_autocmd({ 'BufLeave' }, { 87 | callback = function() 88 | vim.cmd 'stopinsert' 89 | end, 90 | pattern = 'term://*', 91 | }) 92 | end 93 | end 94 | 95 | M.setup = function(opts) 96 | require('ch.ui.term').set_behavior(opts.behavior) 97 | require('ch.ui.term.terminal').init(opts.terminals) 98 | end 99 | 100 | M.setup_keymaps = function(opts) 101 | Keymap.group { 102 | group = 'terminal', 103 | { 104 | 'normal', 105 | opts.mappings.open_float, 106 | function() 107 | require('ch.ui.term.terminal').toggle 'float' 108 | end, 109 | 'toggle floating terminal', 110 | }, 111 | { 112 | 'normal', 113 | opts.mappings.open_vertical, 114 | function() 115 | require('ch.ui.term.terminal').toggle 'vertical' 116 | end, 117 | 'toggle vertical terminal', 118 | }, 119 | { 120 | 'normal', 121 | opts.mappings.open_horizontal, 122 | function() 123 | require('ch.ui.term.terminal').toggle 'horizontal' 124 | end, 125 | 'toggle horizontal terminal', 126 | }, 127 | } 128 | vim.keymap.set('t', opts.mappings.open_float, function() 129 | require('ch.ui.term.terminal').toggle 'float' 130 | end, { desc = 'toggle floating terminal' }) 131 | vim.keymap.set('t', opts.mappings.open_vertical, function() 132 | require('ch.ui.term.terminal').toggle 'vertical' 133 | end, { desc = 'toggle vertical terminal' }) 134 | vim.keymap.set('t', opts.mappings.open_horizontal, function() 135 | require('ch.ui.term.terminal').toggle 'horizontal' 136 | end, { desc = 'toggle horizontal terminal' }) 137 | end 138 | 139 | return M 140 | -------------------------------------------------------------------------------- /lua/ch/ui/term/terminal.lua: -------------------------------------------------------------------------------- 1 | -- adapted from @NvChad https://github.com/NvChad/nvterm/blob/9d7ba3b6e368243175d38e1ec956e0476fd86ed9/lua/nvterm/terminal.lua 2 | 3 | local util = require 'ch.ui.term.utils' 4 | local api = vim.api 5 | local chaiterm = {} 6 | ---@type ch.types.ui.term.terminal[] 7 | local terminals = {} 8 | 9 | local function filter_type(type) 10 | return function(t) 11 | return t.type == type 12 | end 13 | end 14 | 15 | local function get_type(type) 16 | return vim.iter(terminals):filter(filter_type(type)) 17 | end 18 | 19 | ---@return Iter 20 | local function get_still_open() 21 | if not terminals then 22 | return {} 23 | end 24 | return vim.iter(terminals):filter(function(t) 25 | return t.open == true 26 | end) 27 | end 28 | 29 | local function get_last_still_open() 30 | return get_still_open():last() 31 | end 32 | 33 | local function get_term(key, value) 34 | -- assumed to be unique, will only return 1 term regardless 35 | return vim.iter(terminals):filter(function(t) 36 | return t[key] == value 37 | end):next() 38 | end 39 | 40 | local create_term_window = function(type) 41 | local existing = get_still_open():filter(filter_type(type)):next() 42 | util.execute_type_cmd( 43 | type, 44 | ch.lib.options:get('ui', 'terminal', 'ui'), 45 | existing 46 | ) 47 | vim.wo.relativenumber = false 48 | vim.wo.number = false 49 | vim.wo.winhl = 'Normal:Normal' 50 | return api.nvim_get_current_win() 51 | end 52 | 53 | ---@return Iter 54 | local verify_terminals = function() 55 | return vim.iter(terminals):filter(function(term) 56 | if not term.buf then return false end 57 | return vim.api.nvim_buf_is_valid(term.buf) 58 | end):map(function(term) 59 | term.open = vim.api.nvim_win_is_valid(term.win) 60 | return term 61 | end) 62 | end 63 | 64 | local ensure_and_send = function(cmd, type) 65 | terminals = verify_terminals():totable() 66 | local function select_term() 67 | if not type then 68 | return get_last_still_open() or chaiterm.new 'horizontal' 69 | else 70 | return get_type(type):last() or chaiterm.new(type) 71 | end 72 | end 73 | local term = select_term() 74 | api.nvim_chan_send(term.job_id, cmd .. '\n') 75 | end 76 | 77 | local call_and_restore = function(fn, opts) 78 | local current_win = api.nvim_get_current_win() 79 | local mode = api.nvim_get_mode().mode == 'i' and 'startinsert' or 'stopinsert' 80 | 81 | fn(unpack(opts)) 82 | api.nvim_set_current_win(current_win) 83 | 84 | vim.cmd(mode) 85 | end 86 | 87 | chaiterm.send = function(cmd, type) 88 | if not cmd then 89 | return 90 | end 91 | call_and_restore(ensure_and_send, { cmd, type }) 92 | end 93 | 94 | chaiterm.hide_term = function(term) 95 | terminals[term.id].open = false 96 | api.nvim_win_close(term.win, false) 97 | end 98 | 99 | chaiterm.show_term = function(term) 100 | term.win = create_term_window(term.type) 101 | api.nvim_win_set_buf(term.win, term.buf) 102 | terminals[term.id].open = true 103 | vim.cmd 'startinsert' 104 | end 105 | 106 | chaiterm.get_and_show = function(key, value) 107 | local term = get_term(key, value) 108 | chaiterm.show_term(term) 109 | end 110 | 111 | chaiterm.get_and_hide = function(key, value) 112 | local term = get_term(key, value) 113 | chaiterm.hide_term(term) 114 | end 115 | 116 | chaiterm.hide = function(type) 117 | local term = type and get_type(type):last() or vim.iter(terminals):last() 118 | chaiterm.hide_term(term) 119 | end 120 | 121 | chaiterm.show = function(type) 122 | terminals = verify_terminals():totable() 123 | local term = type and get_type(type):last() or vim.iter(terminals):last() 124 | chaiterm.show_term(term) 125 | end 126 | 127 | chaiterm.new = function(type, shell_override) 128 | local win = create_term_window(type) 129 | local buf = api.nvim_create_buf(false, true) 130 | api.nvim_set_option_value('filetype', 'terminal', { buf = buf }) 131 | api.nvim_set_option_value('buflisted', false, { buf = buf }) 132 | 133 | api.nvim_set_option_value('cursorline', false, { win = win }) 134 | api.nvim_set_option_value('signcolumn', 'no', { win = win }) 135 | api.nvim_win_set_buf(win, buf) 136 | 137 | local job_id = vim.fn.termopen(vim.o.shell or shell_override) 138 | local id = #terminals + 1 139 | local term = 140 | { id = id, win = win, buf = buf, open = true, type = type, job_id = job_id } 141 | terminals[id] = term 142 | vim.cmd 'startinsert' 143 | return term 144 | end 145 | 146 | chaiterm.toggle = function(type) 147 | terminals = verify_terminals():totable() 148 | local term = get_type(type):last() 149 | 150 | if not term then 151 | term = chaiterm.new(type) 152 | elseif term.open then 153 | chaiterm.hide_term(term) 154 | else 155 | chaiterm.show_term(term) 156 | end 157 | end 158 | 159 | chaiterm.toggle_all_terms = function() 160 | terminals = verify_terminals():totable() 161 | 162 | for _, term in ipairs(terminals) do 163 | if term.open then 164 | chaiterm.hide_term(term) 165 | else 166 | chaiterm.show_term(term) 167 | end 168 | end 169 | end 170 | 171 | chaiterm.close_all_terms = function() 172 | for _, buf in ipairs(chaiterm.list_active_terms 'buf') do 173 | vim.cmd('bd! ' .. tostring(buf)) 174 | end 175 | end 176 | 177 | chaiterm.list_active_terms = function(property) 178 | local terms = get_still_open() 179 | if property then 180 | return terms:map(function(t) 181 | return t[property] 182 | end):totable() 183 | end 184 | return terms 185 | end 186 | 187 | chaiterm.list_terms = function() 188 | return terminals 189 | end 190 | 191 | chaiterm.init = function(term_config) 192 | terminals = term_config 193 | end 194 | 195 | return chaiterm 196 | -------------------------------------------------------------------------------- /lua/ch/ui/term/utils.lua: -------------------------------------------------------------------------------- 1 | -- adapted from @NvChad https://github.com/NvChad/nvterm/blob/9d7ba3b6e368243175d38e1ec956e0476fd86ed9/lua/nvterm/termutil.lua 2 | 3 | local util = {} 4 | local api = vim.api 5 | 6 | ---calculate window options with float opts 7 | ---@param opts vim.api.keyset.win_config 8 | ---@return table 9 | util.calc_float_opts = function(opts) 10 | local width = math.ceil(opts.width * vim.o.columns) 11 | local height = math.ceil(opts.height * vim.o.lines) 12 | return { 13 | relative = 'editor', 14 | width = width, 15 | height = height, 16 | row = opts.row and math.floor(opts.row * vim.o.lines) or (vim.o.lines - height) / 2, 17 | col = opts.col and math.floor(opts.col * vim.o.columns) or (vim.o.columns - width) / 2, 18 | border = opts.border or ch.config.ui.float_border, 19 | } 20 | end 21 | 22 | ---@param type ch.types.ui.term.type 23 | ---@param ratio number 24 | ---@return integer 25 | util.get_split_dims = function(type, ratio) 26 | local type_switch = type == 'horizontal' 27 | local type_func = type_switch and api.nvim_win_get_height 28 | or api.nvim_win_get_width 29 | return math.floor(type_func(0) * ratio) 30 | end 31 | 32 | ---@param type ch.types.ui.term.type 33 | ---@param ui_opts ch.types.ui.term.config.ui 34 | ---@param override boolean 35 | util.execute_type_cmd = function(type, ui_opts, override) 36 | local opts = ui_opts[type] 37 | local dims = type ~= 'float' and util.get_split_dims(type, opts.split_ratio) 38 | or util.calc_float_opts(opts) 39 | dims = override and '' or dims 40 | local type_cmds = { 41 | horizontal = function() 42 | vim.cmd(opts.location .. dims .. ' split') 43 | end, 44 | vertical = function() 45 | vim.cmd(opts.location .. dims .. ' vsplit') 46 | end, 47 | float = function() 48 | api.nvim_open_win(0, true, dims) 49 | end, 50 | } 51 | 52 | type_cmds[type]() 53 | end 54 | 55 | return util 56 | -------------------------------------------------------------------------------- /lua/ch/ui/theme.lua: -------------------------------------------------------------------------------- 1 | return { 2 | ---@param theme ch.types.lib.hl.table 3 | ---@param config table 4 | create = function(theme, config) 5 | local hl_groups = { 6 | NormalFloat = { theme.ui.fg, theme.ui.bg_accent }, 7 | FloatTitle = { theme.ui.bg, theme.ui.accent }, 8 | FloatBorder = { theme.ui.border }, 9 | StatusLine = { theme.ui.comment, theme.ui.bg_accent }, 10 | StatusLineNC = { theme.ui.comment, theme.ui.bg_accent }, 11 | FloatShadow = { 'none', 'none' }, 12 | FloatShadowThrough = { 'none', 'none' }, 13 | 14 | OkText = { theme.diagnostic.ok, 'none' }, 15 | ErrorText = { theme.diagnostic.error, 'none' }, 16 | WarningText = { theme.diagnostic.warn, 'none' }, 17 | InfoText = { theme.diagnostic.info, 'none' }, 18 | HintText = { theme.diagnostic.hint, 'none' }, 19 | OkFloat = { theme.diagnostic.ok, theme.ui.bg_accent }, 20 | ErrorFloat = { theme.diagnostic.error, theme.ui.bg_accent }, 21 | WarningFloat = { theme.diagnostic.warn, theme.ui.bg_accent }, 22 | InfoFloat = { theme.diagnostic.info, theme.ui.bg_accent }, 23 | HintFloat = { theme.diagnostic.hint, theme.ui.bg_accent }, 24 | 25 | Error = { theme.diagnostic.error }, 26 | ErrorMsg = { link = 'Error' }, 27 | WarningMsg = { theme.diagnostic.warn }, 28 | MoreMsg = { theme.ui.comment }, 29 | ModeMsg = { theme.ui.bg2, 'none' }, 30 | 31 | Search = { theme.ui.match, reverse = config.search.reverse }, 32 | IncSearch = { theme.ui.focus, reverse = config.inc_search.reverse }, 33 | 34 | Todo = { theme.ui.bg, theme.ui.purple }, 35 | 36 | -- Completion Menu 37 | Pmenu = { theme.ui.grey1, theme.ui.bg2 }, 38 | PmenuSel = { 39 | theme.ui.bg2, 40 | theme.ui.pmenu_bg, 41 | reverse = config.search.reverse, 42 | }, 43 | PmenuSbar = { 'none', theme.ui.bg2 }, 44 | PmenuThumb = { 'none', theme.ui.grey1 }, 45 | } 46 | 47 | hl_groups['@none'] = { theme.ui.fg } 48 | hl_groups['@none.markdown'] = { 'none', 'none' } 49 | 50 | return hl_groups 51 | end, 52 | apply = function() 53 | ch.log('ch.ui', 'applying theme') 54 | local theme = ch.lib.hl.__value 55 | local config = ch.config.ui.theme_config 56 | local hl_groups = require('ch.ui.theme').create(theme, config) 57 | ch.log('ch.ui', ('applying "%s" hl groups'):format 'main') 58 | hl_groups = vim.iter(hl_groups):map(function(v) 59 | v.default = true 60 | return v 61 | end) 62 | ch.lib.hl.apply(hl_groups) 63 | end, 64 | } 65 | -------------------------------------------------------------------------------- /lua/ch/ui/util.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | local function is_float(value) 4 | local _, p = math.modf(value) 5 | return p ~= 0 6 | end 7 | 8 | local function calc_float(value, max_value) 9 | if value and is_float(value) then 10 | return math.min(max_value, value * max_value) 11 | else 12 | return value 13 | end 14 | end 15 | 16 | local function calc_list(values, max_value, aggregator, limit) 17 | local ret = limit 18 | if type(values) == "table" then 19 | for _, v in ipairs(values) do 20 | ret = aggregator(ret, calc_float(v, max_value)) 21 | end 22 | return ret 23 | else 24 | ret = aggregator(ret, calc_float(values, max_value)) 25 | end 26 | return ret 27 | end 28 | 29 | local function calculate_dim(desired_size, size, min_size, max_size, total_size) 30 | local ret = calc_float(size, total_size) 31 | local min_val = calc_list(min_size, total_size, math.max, 1) 32 | local max_val = calc_list(max_size, total_size, math.min, total_size) 33 | if not ret then 34 | if not desired_size then 35 | ret = (min_val + max_val) / 2 36 | else 37 | ret = calc_float(desired_size, total_size) 38 | end 39 | end 40 | ret = math.min(ret, max_val) 41 | ret = math.max(ret, min_val) 42 | return math.floor(ret) 43 | end 44 | 45 | local function get_max_width(relative, winid) 46 | if relative == "editor" then 47 | return vim.o.columns 48 | else 49 | return vim.api.nvim_win_get_width(winid or 0) 50 | end 51 | end 52 | 53 | local function get_max_height(relative, winid) 54 | if relative == "editor" then 55 | return vim.o.lines - vim.o.cmdheight 56 | else 57 | return vim.api.nvim_win_get_height(winid or 0) 58 | end 59 | end 60 | 61 | M.calculate_col = function(relative, width, winid) 62 | if relative == "cursor" then 63 | return 0 64 | else 65 | return math.floor((get_max_width(relative, winid) - width) / 2) 66 | end 67 | end 68 | 69 | M.calculate_row = function(relative, height, winid) 70 | if relative == "cursor" then 71 | return 0 72 | else 73 | return math.floor((get_max_height(relative, winid) - height) / 2) 74 | end 75 | end 76 | 77 | M.calculate_width = function(relative, desired_width, config, winid) 78 | return calculate_dim( 79 | desired_width, 80 | config.width, 81 | config.min_width, 82 | config.max_width, 83 | get_max_width(relative, winid) 84 | ) 85 | end 86 | 87 | M.calculate_height = function(relative, desired_height, config, winid) 88 | return calculate_dim( 89 | desired_height, 90 | config.height, 91 | config.min_height, 92 | config.max_height, 93 | get_max_height(relative, winid) 94 | ) 95 | end 96 | 97 | local winid_map = {} 98 | M.add_title_to_win = function(winid, title, opts) 99 | opts = opts or {} 100 | opts.align = opts.align or "center" 101 | if not vim.api.nvim_win_is_valid(winid) then 102 | return 103 | end 104 | -- HACK to force the parent window to position itself 105 | -- See https://github.com/neovim/neovim/issues/13403 106 | vim.cmd("redraw") 107 | local width = math.min(vim.api.nvim_win_get_width(winid) - 4, 2 + vim.api.nvim_strwidth(title)) 108 | local title_winid = winid_map[winid] 109 | local bufnr 110 | if title_winid and vim.api.nvim_win_is_valid(title_winid) then 111 | vim.api.nvim_win_set_width(title_winid, width) 112 | bufnr = vim.api.nvim_win_get_buf(title_winid) 113 | else 114 | bufnr = vim.api.nvim_create_buf(false, true) 115 | local col = 1 116 | if opts.align == "center" then 117 | col = math.floor((vim.api.nvim_win_get_width(winid) - width) / 2) 118 | elseif opts.align == "right" then 119 | col = vim.api.nvim_win_get_width(winid) - 1 - width 120 | elseif opts.align ~= "left" then 121 | vim.notify( 122 | string.format("Unknown dressing window title alignment: '%s'", opts.align), 123 | vim.log.levels.ERROR 124 | ) 125 | end 126 | title_winid = vim.api.nvim_open_win(bufnr, false, { 127 | relative = "win", 128 | win = winid, 129 | width = width, 130 | height = 1, 131 | row = -1, 132 | col = col, 133 | focusable = false, 134 | zindex = 151, 135 | style = "minimal", 136 | noautocmd = true, 137 | }) 138 | winid_map[winid] = title_winid 139 | vim.api.nvim_set_option_value( 140 | "winblend", 141 | vim.wo[winid].winblend, 142 | { scope = "local", win = title_winid } 143 | ) 144 | vim.bo[bufnr].bufhidden = "wipe" 145 | vim.cmd(string.format( 146 | [[ 147 | autocmd WinClosed %d ++once lua require('dressing.util')._on_win_closed(%d) 148 | ]], 149 | winid, 150 | winid 151 | )) 152 | end 153 | vim.api.nvim_buf_set_lines(bufnr, 0, -1, true, { " " .. title:gsub("\n", " ") .. " " }) 154 | local ns = vim.api.nvim_create_namespace("DressingWindow") 155 | vim.api.nvim_buf_clear_namespace(bufnr, ns, 0, -1) 156 | vim.api.nvim_buf_add_highlight(bufnr, ns, "FloatTitle", 0, 0, -1) 157 | end 158 | 159 | M._on_win_closed = function(winid) 160 | local title_winid = winid_map[winid] 161 | if title_winid and vim.api.nvim_win_is_valid(title_winid) then 162 | vim.api.nvim_win_close(title_winid, true) 163 | end 164 | winid_map[winid] = nil 165 | end 166 | 167 | M.schedule_wrap_before_vimenter = function(func) 168 | if vim.g.is_test then 169 | return func 170 | end 171 | return function(...) 172 | if vim.v.vim_did_enter == 0 then 173 | return vim.schedule_wrap(func)(...) 174 | else 175 | return func(...) 176 | end 177 | end 178 | end 179 | 180 | return M 181 | -------------------------------------------------------------------------------- /utils/bin/cvim.template: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | export NVIM_APPNAME="${NVIM_APPNAME:-NVIM_APPNAME_VAR}" 4 | 5 | export CV_CONFIG_DIR="${CV_CONFIG_DIR:-CONFIG_DIR_VAR}" 6 | 7 | exec -a "$NVIM_APPNAME" nvim -u "$CV_CONFIG_DIR/init.lua" "$@" 8 | --------------------------------------------------------------------------------