├── .styluaignore ├── .github ├── FUNDING.yml └── workflows │ └── ci.yml ├── Makefile ├── after ├── ftplugin │ ├── proto.vim │ ├── zig.vim │ ├── dockerfile.vim │ ├── go.vim │ ├── python.vim │ ├── rust.vim │ ├── json.vim │ ├── cpp.vim │ ├── c.vim │ └── lua.vim └── queries │ ├── lua │ └── highlights.scm │ ├── c │ └── highlights.scm │ └── cpp │ └── highlights.scm ├── template ├── nvim_temp.lua ├── main_owner.rs ├── package_owner.go ├── main_owner.go ├── main_owner.c └── main_owner.cpp ├── .stylua.toml ├── README.md ├── lsp ├── zls.lua ├── ruff.lua ├── cmake.lua ├── asm.lua ├── tsls.lua ├── luals.lua ├── emmylua_ls.lua ├── basedpyright.lua ├── clangd.lua └── rust_analyzer.lua ├── LICENSE ├── snippets ├── rust.json ├── go.json ├── typescriptreact.json ├── javascriptreact.json ├── cpp.json ├── c.json └── zig.json ├── Dockerfile ├── nvim-pack-lock.json ├── plugin ├── events.lua └── completion.lua ├── lua └── private │ ├── pairs.lua │ ├── jump.lua │ ├── term.lua │ ├── grep.lua │ ├── dashboard.lua │ ├── keymap.lua │ ├── compile.lua │ └── tetris.lua ├── init.lua └── colors ├── monokai.lua └── solarized.lua /.styluaignore: -------------------------------------------------------------------------------- 1 | /template 2 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: glepnir 2 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | clean: 2 | rm -rf $(HOME)/.local/share/nvim/site/pack/ 3 | -------------------------------------------------------------------------------- /after/ftplugin/proto.vim: -------------------------------------------------------------------------------- 1 | setl cindent 2 | setl expandtab 3 | setl sw=2 4 | -------------------------------------------------------------------------------- /after/ftplugin/zig.vim: -------------------------------------------------------------------------------- 1 | set expandtab 2 | set tabstop=8 3 | set softtabstop=4 4 | set shiftwidth=4 5 | -------------------------------------------------------------------------------- /after/ftplugin/dockerfile.vim: -------------------------------------------------------------------------------- 1 | setl nocindent 2 | setl expandtab 3 | setl shiftwidth=2 4 | setl softtabstop=2 5 | setl tabstop=2 6 | -------------------------------------------------------------------------------- /after/queries/lua/highlights.scm: -------------------------------------------------------------------------------- 1 | ; extends 2 | 3 | (table_constructor 4 | [ 5 | "{" 6 | "}" 7 | ] @punctuation.bracket) 8 | 9 | -------------------------------------------------------------------------------- /after/ftplugin/go.vim: -------------------------------------------------------------------------------- 1 | setl commentstring=//%s 2 | setl noexpandtab 3 | setl shiftwidth=4 4 | setl softtabstop=4 5 | setl tabstop=4 6 | -------------------------------------------------------------------------------- /template/nvim_temp.lua: -------------------------------------------------------------------------------- 1 | local api, fn = vim.api, vim.fn 2 | local {{_variable_}} = {} 3 | 4 | {{_cursor_}} 5 | 6 | return {{_variable_}} 7 | -------------------------------------------------------------------------------- /after/ftplugin/python.vim: -------------------------------------------------------------------------------- 1 | setl expandtab 2 | setl autoindent 3 | setl smartindent 4 | setl smarttab 5 | setl sw=4 6 | setl softtabstop=4 7 | setl tabstop=4 8 | -------------------------------------------------------------------------------- /.stylua.toml: -------------------------------------------------------------------------------- 1 | column_width = 100 2 | line_endings = "Unix" 3 | indent_type = "Spaces" 4 | indent_width = 2 5 | quote_style = "AutoPreferSingle" 6 | call_parentheses = "Always" 7 | -------------------------------------------------------------------------------- /after/ftplugin/rust.vim: -------------------------------------------------------------------------------- 1 | setl nocindent 2 | setl expandtab 3 | setl commentstring=//%s 4 | setl shiftwidth=4 5 | setl softtabstop=4 6 | setl smartindent 7 | setl tabstop=4 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Word 2 | 3 | **Text Editor just text editor Please dont't do no more** 4 | 5 | ![Image](https://github.com/user-attachments/assets/148e04c6-46ca-4289-9349-2b72b45534b0) 6 | -------------------------------------------------------------------------------- /after/ftplugin/json.vim: -------------------------------------------------------------------------------- 1 | setl autoindent 2 | setl conceallevel=0 3 | setl expandtab 4 | setl foldmethod=syntax 5 | setl formatoptions=tcq2l 6 | setl shiftwidth=2 7 | setl softtabstop=2 8 | -------------------------------------------------------------------------------- /lsp/zls.lua: -------------------------------------------------------------------------------- 1 | return { 2 | cmd = { 'zls' }, 3 | filetypes = { 'zig', 'zir' }, 4 | root_markers = { 5 | 'build.zig', 6 | 'zls.json', 7 | }, 8 | } --[[@as vim.lsp.Config]] 9 | -------------------------------------------------------------------------------- /after/queries/c/highlights.scm: -------------------------------------------------------------------------------- 1 | ; extends 2 | 3 | ((identifier) @constant 4 | (#lua-match? @constant "^k[A-Z]")) 5 | 6 | ((identifier) @constant 7 | (#match? @constant "^FOR_ALL.*")) 8 | -------------------------------------------------------------------------------- /template/main_owner.rs: -------------------------------------------------------------------------------- 1 | // Copyright {{_date_}} {{_author_}}. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | fn main() {} 6 | -------------------------------------------------------------------------------- /after/queries/cpp/highlights.scm: -------------------------------------------------------------------------------- 1 | ; extends 2 | 3 | ((call_expression 4 | function: (identifier) @function) 5 | (#lua-match? @function "^%u")) 6 | 7 | ("mutable" @keyword 8 | (#has-ancestor? @keyword lambda_expression)) 9 | -------------------------------------------------------------------------------- /template/package_owner.go: -------------------------------------------------------------------------------- 1 | // Copyright {{_date_}} glepnir. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package {{_file_name_}} 6 | 7 | {{_cursor_}} 8 | -------------------------------------------------------------------------------- /lsp/ruff.lua: -------------------------------------------------------------------------------- 1 | --- Refer to the [documentation](https://docs.astral.sh/ruff/editors/) for more details. 2 | return { 3 | cmd = { 'ruff', 'server' }, 4 | filetypes = { 'python' }, 5 | root_markers = { 'pyproject.toml', 'ruff.toml', '.ruff.toml', '.git' }, 6 | } 7 | -------------------------------------------------------------------------------- /template/main_owner.go: -------------------------------------------------------------------------------- 1 | // Copyright {{_date_}} {{_author_}}. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | func main() { 8 | {{_cursor_}} 9 | } 10 | -------------------------------------------------------------------------------- /after/ftplugin/cpp.vim: -------------------------------------------------------------------------------- 1 | setl nocindent 2 | setl expandtab 3 | setl cinkeys-=: 4 | 5 | " set indent to 2 when in std header file 6 | if &readonly 7 | setl sw=2 8 | setl sts=2 9 | setl tabstop=2 10 | else 11 | setl sw=4 12 | setl sts=4 13 | setl tabstop=4 14 | endif 15 | 16 | -------------------------------------------------------------------------------- /template/main_owner.c: -------------------------------------------------------------------------------- 1 | // Copyright {{_date_}} {{_author_}}. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | int main(int argc, char *argv[]) 6 | { 7 | {{_cursor_}} 8 | return 0; 9 | } 10 | -------------------------------------------------------------------------------- /template/main_owner.cpp: -------------------------------------------------------------------------------- 1 | // Copyright {{_date_}} {{_author_}}. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | int main(int argc, char *argv[]) 6 | { 7 | {{_cursor_}} 8 | return 0; 9 | } 10 | -------------------------------------------------------------------------------- /after/ftplugin/c.vim: -------------------------------------------------------------------------------- 1 | let fname = expand('%:p') 2 | 3 | if fname =~# '\v(neovim|nvim)' 4 | setlocal textwidth=120 5 | elseif fname =~# 'vim' 6 | setlocal listchars=tab:\ \ 7 | else 8 | setlocal expandtab 9 | setlocal shiftwidth=4 10 | setlocal softtabstop=4 11 | setlocal tabstop=4 12 | endif 13 | 14 | inoreabbrev #i #include 15 | -------------------------------------------------------------------------------- /lsp/cmake.lua: -------------------------------------------------------------------------------- 1 | --- https://github.com/regen100/cmake-language-server 2 | --- 3 | return { 4 | cmd = { 'cmake-language-server' }, 5 | filetypes = { 'cmake' }, 6 | root_markers = { 'CMakePresets.json', 'CTestConfig.cmake', '.git', 'build', 'cmake' }, 7 | init_options = { 8 | buildDirectory = 'build', 9 | }, 10 | } --[[@as vim.lsp.Config]] 11 | -------------------------------------------------------------------------------- /lsp/asm.lua: -------------------------------------------------------------------------------- 1 | ---@brief 2 | --- 3 | --- https://github.com/bergercookie/asm-lsp 4 | --- 5 | --- Language Server for NASM/GAS/GO Assembly 6 | --- 7 | --- `asm-lsp` can be installed via cargo: 8 | --- cargo install asm-lsp 9 | return { 10 | cmd = { 'asm-lsp' }, 11 | filetypes = { 'asm', 'vmasm' }, 12 | root_markers = { '.asm-lsp.toml', '.git' }, 13 | } 14 | -------------------------------------------------------------------------------- /lsp/tsls.lua: -------------------------------------------------------------------------------- 1 | return { 2 | init_options = { hostInfo = 'neovim' }, 3 | cmd = { 'typescript-language-server', '--stdio' }, 4 | filetypes = { 5 | 'javascript', 6 | 'javascriptreact', 7 | 'javascript.jsx', 8 | 'typescript', 9 | 'typescriptreact', 10 | 'typescript.tsx', 11 | }, 12 | root_markers = { 'tsconfig.json', 'jsconfig.json', 'package.json', '.git' }, 13 | } 14 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | pull_request: 9 | branches: 10 | - main 11 | 12 | jobs: 13 | lint: 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - uses: actions/checkout@v3 18 | 19 | - name: Stylua 20 | uses: JohnnyMorganz/stylua-action@v1.1.2 21 | with: 22 | token: ${{ secrets.GITHUB_TOKEN }} 23 | args: --check . 24 | -------------------------------------------------------------------------------- /after/ftplugin/lua.vim: -------------------------------------------------------------------------------- 1 | " From https://github.com/tpope/tpope/blob/master/.vimrc 2 | setlocal includeexpr=substitute(v:fname,'\\.','/','g').'.lua' 3 | setlocal comments-=:-- comments+=:---,:-- 4 | 5 | inoreabbrev lo local 6 | inoreabbrev lf local function() 7 | inoreabbrev fu function() end 8 | 9 | function! s:RunFunctionalTestOnCtrlG() abort 10 | let l:fullpath = expand('%:p') 11 | 12 | if l:fullpath !~# '/neovim/test/functional/' 13 | return "\" 14 | endif 15 | 16 | let l:file = substitute(l:fullpath, '.*/\(test/functional/.*\)', '\1', '') 17 | let l:cmd = 'TEST_FILE=' . l:file . ' make test' 18 | 19 | call setreg('+', l:cmd) 20 | 21 | return "\" 22 | endfunction 23 | 24 | nnoremap RunFunctionalTestOnCtrlG() 25 | -------------------------------------------------------------------------------- /lsp/luals.lua: -------------------------------------------------------------------------------- 1 | return { 2 | cmd = { 'lua-language-server' }, 3 | filetypes = { 'lua' }, 4 | root_markers = { 5 | '.luarc.json', 6 | '.luarc.jsonc', 7 | '.luacheckrc', 8 | '.stylua.toml', 9 | 'stylua.toml', 10 | 'selene.toml', 11 | 'selene.yml', 12 | '.git', 13 | }, 14 | on_init = function(client) 15 | if client.workspace_folders then 16 | local path = client.workspace_folders[1].name 17 | if 18 | path ~= vim.fn.stdpath('config') 19 | and (vim.uv.fs_stat(path .. '/.luarc.json') or vim.uv.fs_stat(path .. '/.luarc.jsonc')) 20 | then 21 | return 22 | end 23 | end 24 | 25 | client.config.settings.Lua = vim.tbl_deep_extend('force', client.config.settings.Lua, { 26 | runtime = { 27 | version = 'LuaJIT', 28 | }, 29 | workspace = { 30 | checkThirdParty = false, 31 | library = { 32 | vim.env.VIMRUNTIME, 33 | }, 34 | }, 35 | }) 36 | end, 37 | settings = { 38 | Lua = {}, 39 | }, 40 | } 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Raphael 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /snippets/rust.json: -------------------------------------------------------------------------------- 1 | { 2 | "extern crate": { 3 | "prefix": "extern crate", 4 | "body": ["extern crate ${1:name};"], 5 | "description": "Insert extern crate" 6 | }, 7 | "for": { 8 | "prefix": "for", 9 | "body": ["for ${1:elem} in ${2:iter} {", "\t$0", "}"], 10 | "description": "Insert for loop" 11 | }, 12 | "macro_rules": { 13 | "prefix": "macro_rules", 14 | "body": ["macro_rules! $1 {", "\t($2) => {", "\t\t$0", "\t};", "}"], 15 | "description": "Insert macro_rules!" 16 | }, 17 | "if let": { 18 | "prefix": "if let", 19 | "body": ["if let ${1:pattern} = ${2:value} {", "\t$3", "}"], 20 | "description": "Insert if to match a specific pattern, useful for enum variants e.g. `Some(inner)`" 21 | }, 22 | "spawn": { 23 | "prefix": ["thread_spawn", "spawn"], 24 | "body": ["std::thread::spawn(move || {", "\t$1", "})"], 25 | "description": "Wrap code in thread::spawn" 26 | }, 27 | "derive": { 28 | "prefix": "derive", 29 | "body": ["#[derive(${1})]"], 30 | "description": "#[derive(…)]" 31 | }, 32 | "cfg": { 33 | "prefix": "cfg", 34 | "body": ["#[cfg(${1})]"], 35 | "description": "#[cfg(…)]" 36 | }, 37 | "test": { 38 | "prefix": "test", 39 | "body": ["#[test]", "fn ${1:name}() {", " ${2:unimplemented!();}", "}"], 40 | "description": "#[test]" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /lsp/emmylua_ls.lua: -------------------------------------------------------------------------------- 1 | ---@brief 2 | --- 3 | --- https://github.com/EmmyLuaLs/emmylua-analyzer-rust 4 | --- 5 | --- Emmylua Analyzer Rust. Language Server for Lua. 6 | --- 7 | --- `emmylua_ls` can be installed using `cargo` by following the instructions[here] 8 | --- (https://github.com/EmmyLuaLs/emmylua-analyzer-rust?tab=readme-ov-file#install). 9 | --- 10 | --- The default `cmd` assumes that the `emmylua_ls` binary can be found in `$PATH`. 11 | --- It might require you to provide cargo binaries installation path in it. 12 | 13 | ---@type vim.lsp.Config 14 | return { 15 | cmd = { 'emmylua_ls' }, 16 | filetypes = { 'lua' }, 17 | root_markers = { 18 | '.luarc.json', 19 | '.emmyrc.json', 20 | '.luacheckrc', 21 | '.git', 22 | }, 23 | on_init = function(client) 24 | if client.workspace_folders then 25 | local path = client.workspace_folders[1].name 26 | if 27 | path ~= vim.fn.stdpath('config') 28 | and not path:find('neovim') 29 | and (vim.uv.fs_stat(path .. '/.luarc.json') or vim.uv.fs_stat(path .. '/.luarc.jsonc')) 30 | then 31 | return 32 | end 33 | end 34 | 35 | client.config.settings.Lua = vim.tbl_deep_extend('force', client.config.settings.Lua, { 36 | runtime = { 37 | version = 'LuaJIT', 38 | }, 39 | workspace = { 40 | library = { 41 | vim.env.VIMRUNTIME, 42 | }, 43 | }, 44 | }) 45 | end, 46 | settings = { 47 | Lua = { 48 | completion = { 49 | callSnippet = 'Replace', 50 | }, 51 | }, 52 | }, 53 | } 54 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:latest 2 | MAINTAINER RaphaelHuan 3 | 4 | RUN apk add --virtual build-deps --update \ 5 | autoconf \ 6 | automake \ 7 | cmake \ 8 | ncurses ncurses-dev ncurses-libs ncurses-terminfo \ 9 | gcc \ 10 | g++ \ 11 | libtool \ 12 | libuv \ 13 | linux-headers \ 14 | lua5.3-dev \ 15 | m4 \ 16 | unzip \ 17 | make 18 | 19 | 20 | RUN apk add --update \ 21 | curl \ 22 | git \ 23 | python \ 24 | py-pip \ 25 | python-dev \ 26 | python3-dev \ 27 | python3 &&\ 28 | python3 -m ensurepip && \ 29 | rm -r /usr/lib/python*/ensurepip && \ 30 | pip3 install --upgrade pip setuptools && \ 31 | rm -r /root/.cache 32 | 33 | ENV CMAKE_EXTRA_FLAGS=-DENABLE_JEMALLOC=OFF 34 | WORKDIR /tmp 35 | 36 | RUN git clone https://github.com/neovim/libtermkey.git && \ 37 | cd libtermkey && \ 38 | make && \ 39 | make install && \ 40 | cd ../ && rm -rf libtermkey 41 | 42 | RUN git clone https://github.com/neovim/libvterm.git && \ 43 | cd libvterm && \ 44 | make && \ 45 | make install && \ 46 | cd ../ && rm -rf libvterm 47 | 48 | RUN git clone https://github.com/neovim/unibilium.git && \ 49 | cd unibilium && \ 50 | make && \ 51 | make install && \ 52 | cd ../ && rm -rf unibilium 53 | 54 | RUN curl -L https://github.com/neovim/neovim/archive/nightly.tar.gz | tar xz && \ 55 | cd neovim-nightly && \ 56 | make && \ 57 | make install && \ 58 | cd ../ && rm -rf neovim-nightly 59 | 60 | # # Install neovim python support 61 | RUN pip3 install neovim 62 | RUN pip2 install neovim 63 | 64 | RUN apk del build-deps &&\ 65 | rm -rf /var/cache/apk/* 66 | 67 | # # install all plugins 68 | RUN make install 69 | 70 | WORKDIR /root/.config/nvim 71 | 72 | COPY $HOME/.config/nvim /root/.config/nvim 73 | 74 | -------------------------------------------------------------------------------- /lsp/basedpyright.lua: -------------------------------------------------------------------------------- 1 | local function set_python_path(path) 2 | local clients = vim.lsp.get_clients({ 3 | bufnr = vim.api.nvim_get_current_buf(), 4 | name = 'basedpyright', 5 | }) 6 | for _, client in ipairs(clients) do 7 | if client.settings then 8 | client.settings.python = 9 | vim.tbl_deep_extend('force', client.settings.python or {}, { pythonPath = path }) 10 | else 11 | client.config.settings = 12 | vim.tbl_deep_extend('force', client.config.settings, { python = { pythonPath = path } }) 13 | end 14 | client.notify('workspace/didChangeConfiguration', { settings = nil }) 15 | end 16 | end 17 | 18 | return { 19 | cmd = { 'basedpyright-langserver', '--stdio' }, 20 | filetypes = { 'python' }, 21 | root_markers = { 22 | 'pyproject.toml', 23 | 'setup.py', 24 | 'setup.cfg', 25 | 'requirements.txt', 26 | 'Pipfile', 27 | 'pyrightconfig.json', 28 | '.git', 29 | }, 30 | settings = { 31 | basedpyright = { 32 | analysis = { 33 | autoSearchPaths = true, 34 | useLibraryCodeForTypes = true, 35 | diagnosticMode = 'openFilesOnly', 36 | }, 37 | }, 38 | }, 39 | on_attach = function(client, bufnr) 40 | vim.api.nvim_buf_create_user_command(bufnr, 'LspPyrightOrganizeImports', function() 41 | client:exec_cmd({ 42 | command = 'basedpyright.organizeimports', 43 | arguments = { vim.uri_from_bufnr(bufnr) }, 44 | }) 45 | end, { 46 | desc = 'Organize Imports', 47 | }) 48 | 49 | vim.api.nvim_buf_create_user_command(0, 'LspPyrightSetPythonPath', set_python_path, { 50 | desc = 'Reconfigure basedpyright with the provided python path', 51 | nargs = 1, 52 | complete = 'file', 53 | }) 54 | end, 55 | } 56 | -------------------------------------------------------------------------------- /nvim-pack-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": { 3 | "dired.nvim": { 4 | "rev": "f4881fd378d3f958cbab37a71ef7a7c04375080a", 5 | "src": "https://github.com/nvimdev/dired.nvim" 6 | }, 7 | "fzf-lua": { 8 | "rev": "9d579feab4d3035627150e5e9b6e8fbf5e814ef6", 9 | "src": "https://github.com/ibhagwan/fzf-lua" 10 | }, 11 | "gitsigns.nvim": { 12 | "rev": "5813e4878748805f1518cee7abb50fd7205a3a48", 13 | "src": "https://github.com/lewis6991/gitsigns.nvim" 14 | }, 15 | "guard-collection": { 16 | "rev": "6bd5ad7941fc4c182f29c29a743eabc31fdac2e8", 17 | "src": "https://github.com/nvimdev/guard-collection" 18 | }, 19 | "guard.nvim": { 20 | "rev": "c1d2b668e0d1868e9be5757bb9aa278bc1f84c18", 21 | "src": "https://github.com/nvimdev/guard.nvim" 22 | }, 23 | "indentmini.nvim": { 24 | "rev": "e0f1e381a3949ea6757365fa33f8f1722d3eae90", 25 | "src": "https://github.com/nvimdev/indentmini.nvim" 26 | }, 27 | "modeline.nvim": { 28 | "rev": "07bae5b7165325e4e0b41e25618f5240eb37031b", 29 | "src": "https://github.com/nvimdev/modeline.nvim" 30 | }, 31 | "nvim-treesitter": { 32 | "rev": "4fc09bee78e91bf4ba471cdab4bf9dfa37fde51c", 33 | "src": "https://github.com/nvim-treesitter/nvim-treesitter", 34 | "version": "'main'" 35 | }, 36 | "nvim-treesitter-textobjects": { 37 | "rev": "0d7c800fadcfe2d33089f5726cb8907fc846eece", 38 | "src": "https://github.com/nvim-treesitter/nvim-treesitter-textobjects", 39 | "version": "'main'" 40 | }, 41 | "phoenix.nvim": { 42 | "rev": "71ddba235c57fa8beb139ea554e7269900695abf", 43 | "src": "https://github.com/nvimdev/phoenix.nvim" 44 | }, 45 | "visualizer.nvim": { 46 | "rev": "0484787636d0cbe8d827f3343ad4650b17fe08ca", 47 | "src": "https://github.com/nvimdev/visualizer.nvim" 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /lsp/clangd.lua: -------------------------------------------------------------------------------- 1 | --- Implements the off-spec textDocument/switchSourceHeader method. 2 | --- @param buf integer 3 | local function switch_source_header(client, buf) 4 | client:request( 5 | 'textDocument/switchSourceHeader', 6 | vim.lsp.util.make_text_document_params(buf), 7 | function(err, result) 8 | if err then 9 | vim.notify(err.message, vim.log.levels.ERROR) 10 | return 11 | end 12 | if not result then 13 | vim.notify('Corresponding file could not be determined', vim.log.levels.WARN) 14 | return 15 | end 16 | vim.cmd.edit(vim.uri_to_fname(result)) 17 | end 18 | ) 19 | end 20 | 21 | return { 22 | cmd = { 23 | vim.uv.os_uname().sysname:match('Darwin') and '/opt/homebrew/opt/llvm/bin/clangd' or 'clangd', 24 | }, 25 | filetypes = { 'c', 'cpp', 'objc', 'objcpp', 'cuda', 'proto' }, 26 | root_markers = { 27 | '.clangd', 28 | '.clang-tidy', 29 | '.clang-format', 30 | 'compile_commands.json', 31 | 'compile_flags.txt', 32 | 'configure.ac', -- GNU Autotools. 33 | }, 34 | reuse_client = function(client, config) 35 | return client.name == config.name 36 | end, 37 | settings = { 38 | clangd = { 39 | Completion = { 40 | CodePatterns = 'NONE', 41 | }, 42 | }, 43 | }, 44 | capabilities = { 45 | textDocument = { 46 | completion = { 47 | editsNearCursor = true, 48 | completionItem = { 49 | snippetSupport = false, 50 | }, 51 | }, 52 | }, 53 | -- Off-spec, but clangd and vim.lsp support UTF-8, which is more efficient. 54 | offsetEncoding = { 'utf-8', 'utf-16' }, 55 | }, 56 | on_init = function(client, init_result) 57 | if init_result.offsetEncoding then 58 | client.offset_encoding = init_result.offsetEncoding 59 | end 60 | end, 61 | 62 | -- Assumes at most one clangd client is attached to a buffer. 63 | on_attach = function(client, buf) 64 | vim.api.nvim_buf_create_user_command(buf, 'ClangdSwitchSourceHeader', function() 65 | switch_source_header(client, buf) 66 | end, { 67 | bar = true, 68 | desc = 'clangd: Switch Between Source and Header File', 69 | }) 70 | vim.keymap.set('n', 'grs', 'ClangdSwitchSourceHeader', { 71 | buffer = buf, 72 | desc = 'clangd: Switch Between Source and Header File', 73 | }) 74 | 75 | vim.api.nvim_create_autocmd('LspDetach', { 76 | group = vim.api.nvim_create_augroup('conf_lsp_attach_detach', { clear = false }), 77 | buffer = buf, 78 | callback = function(args) 79 | if args.data.client_id == client.id then 80 | vim.keymap.del('n', 'grs', { buffer = buf }) 81 | vim.api.nvim_buf_del_user_command(buf, 'ClangdSwitchSourceHeader') 82 | return true -- Delete this autocmd. 83 | end 84 | end, 85 | }) 86 | end, 87 | } --[[@as vim.lsp.Config]] 88 | -------------------------------------------------------------------------------- /snippets/go.json: -------------------------------------------------------------------------------- 1 | { 2 | "single constant": { 3 | "prefix": "co", 4 | "body": "const ${1:name} = ${2:value}", 5 | "description": "Snippet for a constant" 6 | }, 7 | "variable declaration": { 8 | "prefix": "var", 9 | "body": "var ${1:name} ${2:type}", 10 | "description": "Snippet for a variable" 11 | }, 12 | "type interface declaration": { 13 | "prefix": "tyi", 14 | "body": "type ${1:name} interface {\n\t$0\n}", 15 | "description": "Snippet for a type interface" 16 | }, 17 | "main": { 18 | "prefix": "main", 19 | "body": ["func main() {", "\t$0", "}"], 20 | "description": "main function" 21 | }, 22 | "struct": { 23 | "prefix": "st", 24 | "body": ["type ${1} struct {", "\t${2}", "}"], 25 | "description": "struct" 26 | }, 27 | "func": { 28 | "prefix": "fn", 29 | "body": ["func ${1}() ${2} {", "\t${3}", "}"], 30 | "description": "function" 31 | }, 32 | "method declaration": { 33 | "prefix": "meth", 34 | "body": "func (${1:receiver} ${2:type}) ${3:method}($4) $5 {\n\t$0\n}", 35 | "description": "Snippet for method declaration" 36 | }, 37 | "if statement": { 38 | "prefix": "if", 39 | "body": "if ${1:condition} {\n\t$0\n}", 40 | "description": "Snippet for if statement" 41 | }, 42 | "else branch": { 43 | "prefix": "el", 44 | "body": "else {\n\t$0\n}", 45 | "description": "Snippet for else branch" 46 | }, 47 | "if else statement": { 48 | "prefix": "ie", 49 | "body": "if ${1:condition} {\n\t$2\n} else {\n\t$0\n}", 50 | "description": "Snippet for if else" 51 | }, 52 | "if err != nil": { 53 | "prefix": "iferr", 54 | "body": "if err != nil {\n\t${1:return ${2:nil, }${3:err}}\n}", 55 | "description": "Snippet for if err != nil" 56 | }, 57 | "switch statement": { 58 | "prefix": "switch", 59 | "body": "switch ${1:expression} {\ncase ${2:condition}:\n\t$0\n}", 60 | "description": "Snippet for switch statement" 61 | }, 62 | "select statement": { 63 | "prefix": "sel", 64 | "body": "select {\ncase ${1:condition}:\n\t$0\n}", 65 | "description": "Snippet for select statement" 66 | }, 67 | "case clause": { 68 | "prefix": "cs", 69 | "body": "case ${1:condition}:$0", 70 | "description": "Snippet for case clause" 71 | }, 72 | "for statement": { 73 | "prefix": "for", 74 | "body": "for ${1:i} := 0; $1 < ${2:count}; $1${3:++} {\n\t$0\n}", 75 | "description": "Snippet for a for loop" 76 | }, 77 | "for range statement": { 78 | "prefix": "forr", 79 | "body": "for ${1:_, }${2:var} := range ${3:var} {\n\t$0\n}", 80 | "description": "Snippet for a for range loop" 81 | }, 82 | "map declaration": { 83 | "prefix": "map", 84 | "body": "map[${1:type}]${2:type}", 85 | "description": "Snippet for a map" 86 | }, 87 | "goroutine anonymous function": { 88 | "prefix": "go", 89 | "body": "go func($1) {\n\t$0\n}($2)", 90 | "description": "Snippet for anonymous goroutine declaration" 91 | }, 92 | "goroutine function": { 93 | "prefix": "gf", 94 | "body": "go ${1:func}($0)", 95 | "description": "Snippet for goroutine declaration" 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /snippets/typescriptreact.json: -------------------------------------------------------------------------------- 1 | { 2 | "Import React": { 3 | "prefix": "imr", 4 | "body": ["import React from 'react';"], 5 | "description": "Imports React" 6 | }, 7 | "componentFunctionalTypescript": { 8 | "prefix": "rfc", 9 | "body": [ 10 | "import React from 'react';", 11 | "", 12 | "const ${1:${TM_DIRECTORY/^.*(\\/|\\\\)([^(\\/|\\\\)]+)$/$2/}}: React.FC = () => {", 13 | " return
;", 14 | "}", 15 | "", 16 | "export default ${1:${TM_DIRECTORY/^.*(\\/|\\\\)([^(\\/|\\\\)]+)$/$2/}};" 17 | ], 18 | "description": "Create ReactJS Functional Component Typescript" 19 | }, 20 | "Create useState hook": { 21 | "prefix": "useState", 22 | "body": [ 23 | "const [${1:state}, set${1:State}] = useState(${2:defaultState});" 24 | ], 25 | "description": "Creates React useState hook" 26 | }, 27 | "Create useEffect hook": { 28 | "prefix": "useEffect", 29 | "body": ["useEffect(() => {", "\t", "}, []);"], 30 | "description": "Creates React useEffect hook" 31 | }, 32 | "Create useContext hook": { 33 | "prefix": "useContext", 34 | "body": ["const ${1:value} = useContext(MyContext);"], 35 | "description": "Creates React useContext hook" 36 | }, 37 | "Create useReducer hook": { 38 | "prefix": "useReducer", 39 | "body": [ 40 | "const [${1:state}, dispatch] = useReducer(${2:reducer}, initialState);" 41 | ], 42 | "description": "Creates React useReducer hook" 43 | }, 44 | "Create useCallback hook": { 45 | "prefix": "useCallback", 46 | "body": [ 47 | "const ${1:memoizedCallback} = useCallback(() => {", 48 | "\t", 49 | "}, []);" 50 | ], 51 | "description": "Creates React useCallback hook" 52 | }, 53 | "Create useMemo hook": { 54 | "prefix": "useMemo", 55 | "body": ["const ${1:memoizedValue} = useMemo(() => {", "\t", "}, []);"], 56 | "description": "Creates React useMemo hook" 57 | }, 58 | "Create useRef hook": { 59 | "prefix": "useRef", 60 | "body": ["const ${1:refContainer} = useRef(${2:initialValue});"], 61 | "description": "Creates React useRef hook" 62 | }, 63 | "Create useImperativeHandle hook": { 64 | "prefix": "useImperativeHandle", 65 | "body": ["useImperativeHandle(${1:initialValue}, () => {", "\t", "}, []);"], 66 | "description": "Creates React useImperativeHandle hook" 67 | }, 68 | "Create useLayoutEffect hook": { 69 | "prefix": "useLayoutEffect", 70 | "body": ["useLayoutEffect(() => {", "\t", "}, []);"], 71 | "description": "Creates React useLayoutEffect hook" 72 | }, 73 | "Create useDebugValue hook": { 74 | "prefix": "useDebugValue", 75 | "body": ["useDebugValue(${1:value}"], 76 | "description": "Creates React useDebugValue hook" 77 | }, 78 | "Create useSelector hook": { 79 | "prefix": "useSelector", 80 | "body": [ 81 | "const ${1:selectedData} = useSelector(state => state.${2:YourObject});" 82 | ], 83 | "description": "Creates Redux useSelector hook" 84 | }, 85 | "Create useDispatch hook": { 86 | "prefix": "useDispatch", 87 | "body": ["const dispatch = useDispatch();"], 88 | "description": "Creates Redux useDispatch hook" 89 | }, 90 | "Create useStore hook": { 91 | "prefix": "useStore", 92 | "body": ["const store = useStore();"], 93 | "description": "Creates Redux useStore hook" 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /plugin/events.lua: -------------------------------------------------------------------------------- 1 | local api = vim.api 2 | local au = api.nvim_create_autocmd 3 | local group = api.nvim_create_augroup('GlepnirGroup', {}) 4 | 5 | au('TextYankPost', { 6 | group = group, 7 | callback = function() 8 | vim.hl.on_yank({ higroup = 'IncSearch', timeout = 400 }) 9 | end, 10 | }) 11 | 12 | au('TermOpen', { 13 | group = group, 14 | command = 'setl stc= nonumber | startinsert!', 15 | }) 16 | 17 | au('LspAttach', { 18 | group = group, 19 | callback = function(args) 20 | local client = vim.lsp.get_client_by_id(args.data.client_id) 21 | if client and client.server_capabilities then 22 | client.server_capabilities.semanticTokensProvider = nil 23 | end 24 | end, 25 | }) 26 | 27 | au('InsertLeave', { 28 | group = group, 29 | callback = function() 30 | if vim.fn.executable('iswitch') == 0 then 31 | return 32 | end 33 | 34 | vim.system({ 'iswitch', '-s', 'com.apple.keylayout.ABC' }, nil, function(proc) 35 | if proc.code ~= 0 then 36 | vim.notify('Failed to switch input source: ' .. proc.stderr, vim.log.levels.WARN) 37 | end 38 | end) 39 | end, 40 | desc = 'auto switch to abc input', 41 | }) 42 | 43 | au('InsertEnter', { 44 | group = group, 45 | once = true, 46 | callback = function() 47 | require('private.pairs') 48 | end, 49 | desc = 'auto pairs', 50 | }) 51 | 52 | au('CmdlineEnter', { 53 | group = group, 54 | once = true, 55 | callback = function() 56 | if vim.version().minor >= 12 then 57 | require('vim._extui').enable({}) 58 | end 59 | end, 60 | }) 61 | 62 | local function startuptime() 63 | if vim.g.strive_startup_time ~= nil then 64 | return 65 | end 66 | vim.g.strive_startup_time = 0 67 | local usage = vim.uv.getrusage() 68 | if usage then 69 | -- Calculate time in milliseconds (user + system time) 70 | local user_time = (usage.utime.sec * 1000) + (usage.utime.usec / 1000) 71 | local sys_time = (usage.stime.sec * 1000) + (usage.stime.usec / 1000) 72 | vim.g.nvim_startup_time = user_time + sys_time 73 | end 74 | end 75 | 76 | vim.lsp.enable({ 77 | 'luals', 78 | -- 'emmylua_ls', 79 | 'clangd', 80 | 'rust_analyzer', 81 | 'basedpyright', 82 | 'ruff', 83 | 'zls', 84 | 'cmake', 85 | 'tsls', 86 | }) 87 | 88 | au('UIEnter', { 89 | group = group, 90 | once = true, 91 | callback = function() 92 | startuptime() 93 | vim.schedule(function() 94 | require('private.dashboard').show() 95 | require('private.keymap') 96 | 97 | vim.lsp.log.set_level(vim.log.levels.OFF) 98 | vim.diagnostic.config({ 99 | float = true, 100 | -- i don't like CursorMoved so... 101 | virtual_text = { 102 | -- current_line = true, 103 | prefix = function(d, _, _) 104 | return ' ■', 'DiagnosticPrefix' .. vim.diagnostic.severity[d.severity] 105 | end, 106 | virt_text_pos = 'eol_right_align', 107 | }, 108 | signs = { 109 | text = { '●', '●', '●', '●' }, 110 | numhl = { 111 | 'DiagnosticError', 112 | 'DiagnosticWarn', 113 | 'DiagnosticInfo', 114 | 'DiagnosticHint', 115 | }, 116 | }, 117 | severity_sort = true, 118 | }) 119 | 120 | api.nvim_create_user_command('LspLog', function() 121 | vim.cmd(string.format('tabnew %s', vim.lsp.log.get_filename())) 122 | end, { 123 | desc = 'Opens the Nvim LSP client log.', 124 | }) 125 | 126 | api.nvim_create_user_command('LspDebug', function() 127 | vim.lsp.log.set_level(vim.log.levels.WARN) 128 | end, { desc = 'enable lsp log' }) 129 | 130 | if vim.version().minor >= 12 then 131 | require('private.grep') 132 | end 133 | require('private.compile') 134 | 135 | vim.cmd.packadd('nohlsearch') 136 | vim.cmd.packadd('nvim.undotree') 137 | end) 138 | end, 139 | desc = 'Initializer', 140 | }) 141 | 142 | au('FileType', { 143 | pattern = vim.g._lang, 144 | group = group, 145 | callback = function(opts) 146 | local lang = vim.treesitter.language.get_lang(vim.bo[opts.buf].filetype) 147 | if not lang then 148 | return 149 | end 150 | if vim.treesitter.language.add(lang) then 151 | vim.treesitter.start(opts.buf, lang) 152 | vim.wo[0][0].foldmethod = 'expr' 153 | vim.wo[0][0].foldexpr = 'v:lua.vim.treesitter.foldexpr()' 154 | end 155 | end, 156 | desc = 'try start treesitter highlight', 157 | }) 158 | -------------------------------------------------------------------------------- /plugin/completion.lua: -------------------------------------------------------------------------------- 1 | local api = vim.api 2 | local au = api.nvim_create_autocmd 3 | local g = api.nvim_create_augroup('glepnir.completion', { clear = true }) 4 | local phoenix_id = 0 5 | 6 | au('LspAttach', { 7 | group = g, 8 | callback = function(args) 9 | local lsp = vim.lsp 10 | local completion = lsp.completion 11 | local ms = lsp.protocol.Methods 12 | local CompletionItemKind = lsp.protocol.CompletionItemKind 13 | 14 | local bufnr = args.buf 15 | local client = lsp.get_client_by_id(args.data.client_id) 16 | if not client or not client:supports_method(ms.textDocument_completion) then 17 | return 18 | end 19 | if client.name == 'phoenix' then 20 | phoenix_id = client.id 21 | end 22 | 23 | if not vim.env.DEBUG_COMPLETION then 24 | local chars = client.server_capabilities.completionProvider.triggerCharacters 25 | if chars then 26 | for i = string.byte('a'), string.byte('z') do 27 | if not vim.list_contains(chars, string.char(i)) then 28 | table.insert(chars, string.char(i)) 29 | end 30 | end 31 | 32 | for i = string.byte('A'), string.byte('Z') do 33 | if not vim.list_contains(chars, string.char(i)) then 34 | table.insert(chars, string.char(i)) 35 | end 36 | end 37 | end 38 | end 39 | 40 | completion.enable(true, client.id, bufnr, { 41 | -- autotrigger = not vim.env.DEBUG_COMPLETION and true or { any = true }, 42 | autotrigger = true, 43 | convert = function(item) 44 | local kind = lsp.protocol.CompletionItemKind[item.kind] or 'u' 45 | local cpp = vim.bo.filetype == 'c' or vim.bo.filetype == 'cpp' 46 | local res = { 47 | kind = kind:sub(1, 1):lower(), 48 | menu = '', 49 | } 50 | -- hack for c/cpp 51 | if 52 | (item.kind == CompletionItemKind.Function or item.kind == CompletionItemKind.Method) 53 | and cpp 54 | and not item.textEdit.newText:find('%)$') 55 | then 56 | res.word = item.insertText .. '()' 57 | end 58 | return res 59 | end, 60 | cmp = function(a, b) 61 | local item_a = a.user_data.nvim.lsp.completion_item 62 | local item_b = b.user_data.nvim.lsp.completion_item 63 | 64 | local is_snip_a = item_a.kind == lsp.protocol.CompletionItemKind.Snippet 65 | local is_snip_b = item_b.kind == lsp.protocol.CompletionItemKind.Snippet 66 | 67 | if is_snip_a ~= is_snip_b then 68 | return is_snip_a 69 | end 70 | 71 | local client_a = a.user_data.nvim.lsp.client_id 72 | local client_b = b.user_data.nvim.lsp.client_id 73 | local prio_a = client_a == phoenix_id and 999 or 1 74 | local prio_b = client_b == phoenix_id and 999 or 1 75 | if prio_a ~= prio_b then 76 | return prio_a < prio_b 77 | end 78 | 79 | return (item_a.sortText or item_a.label) < (item_b.sortText or item_b.label) 80 | end, 81 | }) 82 | 83 | if 84 | #api.nvim_get_autocmds({ 85 | buffer = bufnr, 86 | event = { 'CompleteChanged' }, 87 | group = g, 88 | }) == 0 89 | then 90 | au('CompleteChanged', { 91 | buffer = bufnr, 92 | group = g, 93 | callback = function() 94 | local info = vim.fn.complete_info({ 'selected' }) 95 | if info.preview_bufnr and vim.bo[info.preview_bufnr].filetype == '' then 96 | vim.bo[info.preview_bufnr].filetype = 'markdown' 97 | vim.wo[info.preview_winid].conceallevel = 2 98 | vim.wo[info.preview_winid].concealcursor = 'niv' 99 | end 100 | end, 101 | }) 102 | 103 | au('CompleteDone', { 104 | buffer = bufnr, 105 | group = g, 106 | callback = function() 107 | local item = 108 | vim.tbl_get(vim.v.completed_item, 'user_data', 'nvim', 'lsp', 'completion_item') 109 | if not item then 110 | return 111 | end 112 | 113 | -- hack for c/cpp 114 | local cpp = vim.bo.filetype == 'c' or vim.bo.filetype == 'cpp' 115 | local has_params = false 116 | if item.label then 117 | local params = item.label:match('%b()') 118 | if params and not params:match('^%(%s*%)$') then 119 | has_params = true 120 | end 121 | end 122 | 123 | if 124 | (item.kind == CompletionItemKind.Function or item.kind == CompletionItemKind.Method) 125 | and cpp 126 | and has_params 127 | then 128 | api.nvim_feedkeys(api.nvim_replace_termcodes('', true, false, true), 'n', false) 129 | end 130 | 131 | if 132 | item.kind == 3 133 | and item.insertTextFormat == lsp.protocol.InsertTextFormat.Snippet 134 | and (item.textEdit ~= nil or item.insertText ~= nil) 135 | then 136 | vim.schedule(function() 137 | if api.nvim_get_mode().mode == 's' then 138 | lsp.buf.signature_help() 139 | end 140 | end) 141 | end 142 | end, 143 | desc = 'Auto show signature help when compeltion done', 144 | }) 145 | end 146 | end, 147 | }) 148 | -------------------------------------------------------------------------------- /lsp/rust_analyzer.lua: -------------------------------------------------------------------------------- 1 | ---@brief 2 | --- 3 | --- https://github.com/rust-lang/rust-analyzer 4 | --- 5 | --- rust-analyzer (aka rls 2.0), a language server for Rust 6 | --- 7 | --- 8 | --- See [docs](https://rust-analyzer.github.io/book/configuration.html) for extra settings. The settings can be used like this: 9 | --- ```lua 10 | --- vim.lsp.config('rust_analyzer', { 11 | --- settings = { 12 | --- ['rust-analyzer'] = { 13 | --- diagnostics = { 14 | --- enable = false; 15 | --- } 16 | --- } 17 | --- } 18 | --- }) 19 | --- ``` 20 | --- 21 | --- Note: do not set `init_options` for this LS config, it will be automatically populated by the contents of settings["rust-analyzer"] per 22 | --- https://github.com/rust-lang/rust-analyzer/blob/eb5da56d839ae0a9e9f50774fa3eb78eb0964550/docs/dev/lsp-extensions.md?plain=1#L26. 23 | 24 | local function is_descendant(root, path) 25 | if not path or not root then 26 | return false 27 | end 28 | 29 | local ok, relpath = pcall(vim.fs.relpath, path, root) 30 | if not ok or not relpath then 31 | return false 32 | end 33 | 34 | return relpath == '.' or not relpath:match('^%.%./') 35 | end 36 | 37 | local function escape_wildcards(path) 38 | return path:gsub('([%[%]%?%*])', '\\%1') 39 | end 40 | 41 | local function root_pattern(...) 42 | local patterns = vim.iter({ ... }):flatten(math.huge):totable() 43 | 44 | return function(startpath) 45 | startpath = startpath or vim.fn.expand('%:p:h') 46 | 47 | for _, pattern in ipairs(patterns) do 48 | for path in vim.fs.parents(startpath) do 49 | local matches = vim.fn.glob(escape_wildcards(path) .. '/' .. pattern, true, true) 50 | 51 | for _, p in ipairs(matches) do 52 | if vim.uv.fs_stat(p) then 53 | return path 54 | end 55 | end 56 | end 57 | 58 | local matches = vim.fn.glob(escape_wildcards(startpath) .. '/' .. pattern, true, true) 59 | for _, p in ipairs(matches) do 60 | if vim.uv.fs_stat(p) then 61 | return startpath 62 | end 63 | end 64 | end 65 | 66 | return nil 67 | end 68 | end 69 | 70 | local function reload_workspace(bufnr) 71 | bufnr = bufnr == 0 and vim.api.nvim_get_current_buf() or bufnr 72 | local clients = vim.lsp.get_clients({ bufnr = bufnr, name = 'rust_analyzer' }) 73 | for _, client in ipairs(clients) do 74 | vim.notify('Reloading Cargo Workspace') 75 | client.request('rust-analyzer/reloadWorkspace', nil, function(err) 76 | if err then 77 | error(tostring(err)) 78 | end 79 | vim.notify('Cargo workspace reloaded') 80 | end, 0) 81 | end 82 | end 83 | 84 | local function is_library(fname) 85 | local user_home = vim.fs.normalize(vim.env.HOME) 86 | local cargo_home = os.getenv('CARGO_HOME') or user_home .. '/.cargo' 87 | local registry = cargo_home .. '/registry/src' 88 | local git_registry = cargo_home .. '/git/checkouts' 89 | 90 | local rustup_home = os.getenv('RUSTUP_HOME') or user_home .. '/.rustup' 91 | local toolchains = rustup_home .. '/toolchains' 92 | 93 | for _, item in ipairs({ toolchains, registry, git_registry }) do 94 | if is_descendant(item, fname) then 95 | local clients = vim.lsp.get_clients({ name = 'rust_analyzer' }) 96 | return #clients > 0 and clients[#clients].config.root_dir or nil 97 | end 98 | end 99 | end 100 | 101 | return { 102 | cmd = { 'rust-analyzer' }, 103 | filetypes = { 'rust' }, 104 | root_dir = function(bufnr, on_dir) 105 | local fname = vim.api.nvim_buf_get_name(bufnr) 106 | local reused_dir = is_library(fname) 107 | if reused_dir then 108 | on_dir(reused_dir) 109 | return 110 | end 111 | 112 | local cargo_crate_dir = root_pattern('Cargo.toml')(fname) 113 | local cargo_workspace_root 114 | 115 | if cargo_crate_dir == nil then 116 | on_dir( 117 | root_pattern('rust-project.json')(fname) 118 | or vim.fs.dirname(vim.fs.find('.git', { path = fname, upward = true })[1]) 119 | ) 120 | return 121 | end 122 | 123 | local cmd = { 124 | 'cargo', 125 | 'metadata', 126 | '--no-deps', 127 | '--format-version', 128 | '1', 129 | '--manifest-path', 130 | cargo_crate_dir .. '/Cargo.toml', 131 | } 132 | 133 | vim.system(cmd, { text = true }, function(output) 134 | if output.code == 0 then 135 | if output.stdout then 136 | local result = vim.json.decode(output.stdout) 137 | if result['workspace_root'] then 138 | cargo_workspace_root = vim.fs.normalize(result['workspace_root']) 139 | end 140 | end 141 | 142 | on_dir(cargo_workspace_root or cargo_crate_dir) 143 | else 144 | vim.schedule(function() 145 | vim.notify( 146 | ('[rust_analyzer] cmd failed with code %d: %s\n%s'):format( 147 | output.code, 148 | cmd, 149 | output.stderr 150 | ) 151 | ) 152 | end) 153 | end 154 | end) 155 | end, 156 | capabilities = { 157 | experimental = { 158 | serverStatusNotification = true, 159 | }, 160 | }, 161 | before_init = function(init_params, config) 162 | -- See https://github.com/rust-lang/rust-analyzer/blob/eb5da56d839ae0a9e9f50774fa3eb78eb0964550/docs/dev/lsp-extensions.md?plain=1#L26 163 | if config.settings and config.settings['rust-analyzer'] then 164 | init_params.initializationOptions = config.settings['rust-analyzer'] 165 | end 166 | end, 167 | on_attach = function() 168 | vim.api.nvim_buf_create_user_command(0, 'LspCargoReload', function() 169 | reload_workspace(0) 170 | end, { desc = 'Reload current cargo workspace' }) 171 | end, 172 | } 173 | -------------------------------------------------------------------------------- /lua/private/pairs.lua: -------------------------------------------------------------------------------- 1 | local api = vim.api 2 | 3 | -- Class for managing bracket pairs 4 | local BracketPair = {} 5 | BracketPair.__index = BracketPair 6 | 7 | function BracketPair.new() 8 | local self = setmetatable({}, BracketPair) 9 | self.pairs = { 10 | ['('] = ')', 11 | ['['] = ']', 12 | ['{'] = '}', 13 | ['"'] = '"', 14 | ["'"] = "'", 15 | ['`'] = '`', 16 | } 17 | -- Extend with any user-defined pairs 18 | self.pairs = vim.tbl_extend('keep', self.pairs, vim.g.fnpairs or {}) 19 | return self 20 | end 21 | 22 | function BracketPair:get_closing(opening) 23 | return self.pairs[opening] 24 | end 25 | 26 | function BracketPair:is_opening(char) 27 | return self.pairs[char] ~= nil 28 | end 29 | 30 | function BracketPair:is_balanced(str, opening_char) 31 | local stack = {} 32 | for i = 1, #str do 33 | local c = str:sub(i, i) 34 | if self.pairs[c] then 35 | table.insert(stack, c) 36 | elseif c == self.pairs[opening_char] then 37 | if #stack == 0 or stack[#stack] ~= opening_char then 38 | return false 39 | end 40 | table.remove(stack) 41 | end 42 | end 43 | return true 44 | end 45 | 46 | -- Class for managing editor state 47 | local State = {} 48 | State.__index = State 49 | 50 | function State.new() 51 | local self = setmetatable({}, State) 52 | self.line = api.nvim_get_current_line() 53 | self.cursor = api.nvim_win_get_cursor(0) 54 | self.mode = api.nvim_get_mode().mode 55 | return self 56 | end 57 | 58 | function State:get_word_before() 59 | local col = self.cursor[2] 60 | if col == 0 then 61 | return '' 62 | end 63 | 64 | local start_pos = col 65 | while start_pos > 0 do 66 | local char = self.line:sub(start_pos, start_pos) 67 | if char:match('%s') then 68 | start_pos = start_pos + 1 69 | break 70 | end 71 | start_pos = start_pos - 1 72 | end 73 | 74 | if start_pos == 0 then 75 | start_pos = 1 76 | end 77 | 78 | return self.line:sub(start_pos, col) 79 | end 80 | 81 | function State:get_char_before() 82 | local pos = self.cursor[2] 83 | if pos > 0 then 84 | return self.line:sub(pos, pos) 85 | end 86 | return nil 87 | end 88 | 89 | function State:get_char_after() 90 | local pos = self.cursor[2] + 1 91 | if pos <= #self.line then 92 | return self.line:sub(pos, pos) 93 | end 94 | return nil 95 | end 96 | 97 | -- Action classes 98 | local ActionType = { 99 | SKIP = 'skip', 100 | INSERT = 'insert', 101 | DELETE = 'delete', 102 | NOTHING = 'nothing', 103 | } 104 | 105 | local Action = {} 106 | Action.__index = Action 107 | 108 | function Action.skip() 109 | return setmetatable({ 110 | type = ActionType.SKIP, 111 | }, Action) 112 | end 113 | 114 | function Action.insert(opening, closing) 115 | return setmetatable({ 116 | type = ActionType.INSERT, 117 | opening = opening, 118 | closing = closing, 119 | }, Action) 120 | end 121 | 122 | function Action.delete() 123 | return setmetatable({ 124 | type = ActionType.DELETE, 125 | }, Action) 126 | end 127 | 128 | function Action.nothing(char) 129 | return setmetatable({ 130 | type = ActionType.NOTHING, 131 | char = char, 132 | }, Action) 133 | end 134 | 135 | -- Action handler 136 | local ActionHandler = {} 137 | 138 | function ActionHandler.handle(action, state, bracket_pairs) 139 | if action.type == ActionType.SKIP then 140 | return '' 141 | elseif action.type == ActionType.INSERT then 142 | return action.opening .. action.closing .. '' 143 | elseif action.type == ActionType.DELETE then 144 | local before = state:get_char_before() 145 | local after = state:get_char_after() 146 | if before and after and bracket_pairs:get_closing(before) == after then 147 | return '' 148 | end 149 | return '' 150 | else -- NOTHING insert char self 151 | return action.char or '' 152 | end 153 | end 154 | 155 | -- Main plugin class 156 | local Pairs = {} 157 | Pairs.__index = Pairs 158 | 159 | function Pairs.new() 160 | local self = setmetatable({}, Pairs) 161 | self.bracket_pairs = BracketPair.new() 162 | return self 163 | end 164 | 165 | function Pairs:determine_action(char, state) 166 | -- Handle visual mode 167 | if state.mode == 'v' or state.mode == 'V' then 168 | return Action.insert(char, self.bracket_pairs:get_closing(char)) 169 | end 170 | 171 | -- Check ' used in binary number 172 | if char == "'" then 173 | local word_before = state:get_word_before() 174 | if word_before and word_before:match('0b[01]*$') then 175 | return Action.nothing(char) 176 | end 177 | end 178 | 179 | -- Check if we should skip closing bracket 180 | local next_char = state:get_char_after() 181 | if next_char and next_char == self.bracket_pairs:get_closing(char) then 182 | -- Check bracket balance 183 | local substr = state.line:sub(state.cursor[2] + 1) 184 | if self.bracket_pairs:is_balanced(substr, char) then 185 | return Action.skip() 186 | end 187 | end 188 | 189 | return Action.insert(char, self.bracket_pairs:get_closing(char)) 190 | end 191 | 192 | function Pairs:handle_char(char) 193 | local state = State.new() 194 | local action = self:determine_action(char, state) 195 | return ActionHandler.handle(action, state, self.bracket_pairs) 196 | end 197 | 198 | function Pairs:handle_backspace() 199 | local state = State.new() 200 | return ActionHandler.handle(Action.delete(), state, self.bracket_pairs) 201 | end 202 | 203 | function Pairs:setup() 204 | -- Store reference to self to avoid closure issues 205 | local plugin = self 206 | 207 | -- Setup bracket pairs 208 | for opening, _ in pairs(self.bracket_pairs.pairs) do 209 | vim.keymap.set('i', opening, function() 210 | return plugin:handle_char(opening) 211 | end, { expr = true }) 212 | end 213 | 214 | -- Setup backspace handling 215 | vim.keymap.set('i', '', function() 216 | return plugin:handle_backspace() 217 | end, { expr = true }) 218 | end 219 | 220 | Pairs.new():setup() 221 | -------------------------------------------------------------------------------- /init.lua: -------------------------------------------------------------------------------- 1 | local g = vim.g 2 | vim.loader.enable() 3 | g.mapleader = vim.keycode('') 4 | 5 | g.loaded_gzip = 1 6 | g.loaded_tar = 1 7 | g.loaded_tarPlugin = 1 8 | g.loaded_zip = 1 9 | g.loaded_zipPlugin = 1 10 | g.loaded_getscript = 1 11 | g.loaded_getscriptPlugin = 1 12 | g.loaded_vimball = 1 13 | g.loaded_vimballPlugin = 1 14 | g.loaded_matchit = 1 15 | g.loaded_2html_plugin = 1 16 | g.loaded_rrhelper = 1 17 | g.loaded_netrwPlugin = 1 18 | g.loaded_matchparen = 1 19 | 20 | local o = vim.o 21 | o.hidden = true 22 | o.magic = true 23 | o.virtualedit = 'block' 24 | o.clipboard = 'unnamedplus' 25 | o.wildignorecase = true 26 | o.swapfile = false 27 | o.timeout = true 28 | o.ttimeout = true 29 | o.timeoutlen = 500 30 | o.ttimeoutlen = 10 31 | o.updatetime = 500 32 | o.ignorecase = true 33 | o.smartcase = true 34 | o.cursorline = true 35 | o.showmode = false 36 | o.shortmess = 'aoOTIcF' 37 | o.scrolloff = 2 38 | o.sidescrolloff = 5 39 | o.ruler = false 40 | o.showtabline = 0 41 | o.showcmd = false 42 | o.pumheight = 15 43 | o.pummaxwidth = 30 44 | o.list = true 45 | --eol:¬ 46 | o.listchars = 'tab:» ,nbsp:+,trail:·,extends:→,precedes:←,' 47 | o.fillchars = 'trunc:…' 48 | o.foldtext = '' 49 | o.foldlevelstart = 99 50 | o.undofile = true 51 | o.linebreak = true 52 | o.smoothscroll = true 53 | o.smarttab = true 54 | o.expandtab = true 55 | o.autoindent = true 56 | o.tabstop = 2 57 | o.sw = 2 58 | o.wrap = false 59 | o.number = true 60 | o.signcolumn = 'yes' 61 | o.textwidth = 80 62 | o.colorcolumn = '+0' 63 | o.winborder = 'rounded' 64 | o.splitright = true 65 | -- reset to 2 in dashboard.lua lnum 273 66 | o.laststatus = 0 67 | o.cot = 'menu,menuone,noinsert,fuzzy,nosort,popup' 68 | o.cia = 'kind,abbr,menu' 69 | vim.opt.guicursor:remove({ 't:block-blinkon500-blinkoff500-TermCursor' }) 70 | 71 | vim.cmd.colorscheme('solarized') 72 | g.health = { style = 'float' } 73 | g.editorconfig = false 74 | g._lang = { 75 | 'c', 76 | 'cpp', 77 | 'rust', 78 | 'zig', 79 | 'lua', 80 | 'python', 81 | 'proto', 82 | 'typescript', 83 | 'javascript', 84 | 'tsx', 85 | 'css', 86 | 'scss', 87 | 'diff', 88 | 'dockerfile', 89 | 'graphql', 90 | 'html', 91 | 'sql', 92 | 'markdown', 93 | 'markdown_inline', 94 | 'vimdoc', 95 | 'vim', 96 | 'cmake', 97 | } 98 | 99 | vim.api.nvim_create_autocmd('PackChanged', { 100 | callback = function(ev) 101 | local name, active = ev.data.spec.name, ev.data.active 102 | if name == 'nvim-treesitter' then 103 | if not active then 104 | vim.cmd.packadd('nvim-treesitter') 105 | end 106 | local nts = require('nvim-treesitter') 107 | nts.install(g.lang, { summary = true }) 108 | nts.update(nil, { summary = true }) 109 | end 110 | end, 111 | }) 112 | 113 | g.phoenix = { 114 | snippet = vim.fn.stdpath('config') .. '/snippets', 115 | } 116 | 117 | local P = {} 118 | 119 | function P:add(specs, opts) 120 | if type(specs) == 'string' or (type(specs) == 'table' and specs.src) then 121 | specs = { specs } 122 | end 123 | opts = opts or {} 124 | opts.confirm = false 125 | vim.pack.add(specs, opts) 126 | return self 127 | end 128 | 129 | P:add('https://github.com/nvimdev/modeline.nvim') 130 | :add({ 131 | 'https://github.com/lewis6991/gitsigns.nvim', 132 | 'https://github.com/nvimdev/visualizer.nvim', 133 | 'https://github.com/nvimdev/phoenix.nvim', 134 | { src = 'https://github.com/nvim-treesitter/nvim-treesitter', version = 'main' }, 135 | { src = 'https://github.com/nvim-treesitter/nvim-treesitter-textobjects', version = 'main' }, 136 | }, { 137 | load = false, 138 | }) 139 | :add('https://github.com/nvimdev/dired.nvim', { 140 | load = function() 141 | vim.api.nvim_create_user_command('Dired', function(data) 142 | vim.api.nvim_del_user_command('Dired') 143 | vim.cmd.packadd('dired.nvim') 144 | vim.cmd('Dired ' .. data.args) 145 | end, { 146 | nargs = '?', 147 | }) 148 | end, 149 | }) 150 | :add('https://github.com/ibhagwan/fzf-lua', { 151 | load = function() 152 | vim.api.nvim_create_user_command('FzfLua', function(data) 153 | vim.api.nvim_del_user_command('FzfLua') 154 | vim.cmd.packadd('fzf-lua') 155 | require('fzf-lua').setup({ 156 | 'max-perf', 157 | lsp = { symbols = { symbol_style = 3 } }, 158 | }) 159 | vim.cmd('FzfLua ' .. data.args) 160 | end, { 161 | nargs = '?', 162 | }) 163 | end, 164 | }) 165 | :add('https://github.com/nvimdev/indentmini.nvim', { 166 | load = function() 167 | vim.api.nvim_create_autocmd({ 'BufEnter', 'BufNewFile' }, { 168 | callback = function() 169 | vim.cmd.packadd('indentmini.nvim') 170 | require('indentmini').setup({ 171 | only_current = true, 172 | }) 173 | end, 174 | }) 175 | end, 176 | }) 177 | :add({ 178 | 'https://github.com/nvimdev/guard.nvim', 179 | 'https://github.com/nvimdev/guard-collection', 180 | }, { 181 | load = function() 182 | vim.api.nvim_create_autocmd('BufReadPost', { 183 | once = true, 184 | callback = function() 185 | vim.cmd.packadd('guard.nvim') 186 | vim.cmd.packadd('guard-collection') 187 | local ft = require('guard.filetype') 188 | ft('c,cpp'):fmt({ 189 | cmd = 'clang-format', 190 | args = function(bufnr) 191 | local f = vim.bo[bufnr].filetype == 'cpp' and '.cc-format' or '.c-format' 192 | return { ('--style=file:%s/%s'):format(vim.env.HOME, f) } 193 | end, 194 | stdin = true, 195 | ignore_patterns = { 'neovim', 'vim' }, 196 | }) 197 | 198 | ft('lua'):fmt({ 199 | cmd = 'stylua', 200 | args = { '-' }, 201 | stdin = true, 202 | ignore_patterns = 'function.*_spec%.lua', 203 | find = '.stylua.toml', 204 | }) 205 | ft('rust'):fmt('rustfmt') 206 | ft('typescript', 'javascript', 'typescriptreact', 'javascriptreact'):fmt('prettier') 207 | end, 208 | }) 209 | end, 210 | }) 211 | -------------------------------------------------------------------------------- /lua/private/jump.lua: -------------------------------------------------------------------------------- 1 | -- Minimal asynchronous fast jump based on rg 2 | 3 | local M = {} 4 | local api, FORWARD, BACKWARD = vim.api, 1, -1 5 | 6 | local state = { 7 | active = false, 8 | mode = nil, 9 | ns_id = nil, 10 | key_map = {}, 11 | on_key_func = nil, 12 | max_targets = 60, -- keys count 13 | } 14 | 15 | local function cleanup() 16 | if state.active then 17 | if state.ns_id then 18 | api.nvim_buf_clear_namespace(0, state.ns_id, 0, -1) 19 | end 20 | if state.on_key_func then 21 | vim.on_key(nil, state.ns_id) 22 | state.on_key_func = nil 23 | end 24 | 25 | state.active = false 26 | state.mode = nil 27 | state.key_map = {} 28 | 29 | if state.id then 30 | api.nvim_del_autocmd(state.id) 31 | end 32 | end 33 | end 34 | 35 | local function generate_keys(count) 36 | local keys = 'asdghjklzxcvbnmqwertyuiopASDGHJLZXCVBNMQWERTYUIOP1234567890' 37 | local key_len = #keys 38 | local result = {} 39 | 40 | for i = 1, math.min(count, key_len) do 41 | table.insert(result, string.sub(keys, i, i)) 42 | end 43 | 44 | return result 45 | end 46 | 47 | local function dim_buffer() 48 | if not state.ns_id then 49 | state.ns_id = api.nvim_create_namespace('jumpmotion') 50 | end 51 | 52 | -- Get visible lines range 53 | local first_line = vim.fn.line('w0') - 1 54 | local last_line = vim.fn.line('w$') - 1 55 | 56 | -- Apply dimming to all visible lines 57 | for line_num = first_line, last_line do 58 | local line_text = api.nvim_buf_get_lines(0, line_num, line_num + 1, false)[1] 59 | if line_text and line_text ~= '' then 60 | api.nvim_buf_set_extmark(0, state.ns_id, line_num, 0, { 61 | end_col = #line_text, 62 | hl_group = 'JumpMotionDim', 63 | priority = 200, 64 | }) 65 | end 66 | end 67 | end 68 | 69 | local function mark_targets(targets) 70 | if not state.ns_id then 71 | state.ns_id = api.nvim_create_namespace('jumpmotion') 72 | end 73 | 74 | api.nvim_buf_clear_namespace(0, state.ns_id, 0, -1) 75 | 76 | -- Dim the entire buffer first 77 | dim_buffer() 78 | 79 | local keys = generate_keys(#targets) 80 | state.key_map = {} 81 | 82 | for i, target in ipairs(targets) do 83 | if i <= #keys then 84 | local key = keys[i] 85 | state.key_map[key] = target 86 | 87 | -- Clear the dimming at target position and add the jump key 88 | api.nvim_buf_set_extmark(0, state.ns_id, target.row, target.col, { 89 | end_col = target.col + 1, 90 | hl_group = 'JumpMotionTargetBg', 91 | priority = 250, 92 | }) 93 | 94 | api.nvim_buf_set_extmark(0, state.ns_id, target.row, target.col, { 95 | virt_text = { { key, 'JumpMotionTarget' } }, 96 | virt_text_pos = 'overlay', 97 | priority = 300, 98 | }) 99 | end 100 | end 101 | 102 | state.on_key_func = function(char, typed) 103 | if not state.active then 104 | return 105 | end 106 | 107 | if char == '\27' then 108 | cleanup() 109 | return 110 | end 111 | 112 | local target = state.key_map[typed] 113 | if target then 114 | vim.schedule(function() 115 | api.nvim_win_set_cursor(0, { target.row + 1, target.col }) 116 | end) 117 | cleanup() 118 | return '' 119 | end 120 | end 121 | 122 | vim.on_key(state.on_key_func, state.ns_id) 123 | 124 | state.id = api.nvim_create_autocmd({ 'CursorMoved', 'InsertEnter', 'BufLeave', 'WinLeave' }, { 125 | once = true, 126 | callback = function() 127 | if state.active then 128 | cleanup() 129 | end 130 | end, 131 | }) 132 | end 133 | 134 | function M.char(direction) 135 | if vim.fn.executable('rg') == 0 or vim.fn.line2byte(vim.fn.line('$') + 1) == -1 then 136 | return 137 | end 138 | 139 | return function() 140 | vim.schedule(function() 141 | if state.active then 142 | cleanup() 143 | end 144 | 145 | local ok, char = pcall(function() 146 | return vim.fn.nr2char(vim.fn.getchar()) 147 | end) 148 | if not ok or not char or char == '' or char == vim.fn.nr2char(27) then 149 | return 150 | end 151 | local char_input = char 152 | 153 | state.active = true 154 | state.mode = 'char' 155 | 156 | local first_line = vim.fn.line('w0') - 1 157 | local curow = api.nvim_win_get_cursor(0)[1] - 1 158 | if curow == 0 and direction == BACKWARD then 159 | return 160 | end 161 | local last_line = vim.fn.line('w$') 162 | 163 | local lines 164 | local base_row 165 | 166 | if direction == FORWARD then 167 | base_row = curow + 1 168 | lines = api.nvim_buf_get_lines(0, curow + 1, last_line, false) 169 | else 170 | base_row = first_line 171 | lines = api.nvim_buf_get_lines(0, first_line, curow, false) 172 | local reversed_lines = {} 173 | for i = #lines, 1, -1 do 174 | table.insert(reversed_lines, lines[i]) 175 | end 176 | lines = reversed_lines 177 | end 178 | 179 | local visible_text = table.concat(lines, '\n') 180 | 181 | local cmd = { 182 | 'rg', 183 | '--json', 184 | '--fixed-strings', 185 | char_input, 186 | } 187 | 188 | vim.system(cmd, { 189 | stdin = visible_text, 190 | }, function(result) 191 | vim.schedule(function() 192 | local targets = {} 193 | local count = 0 194 | 195 | if result.stdout then 196 | for line in string.gmatch(result.stdout, '[^\r\n]+') do 197 | if line:find('"type":"match"') then 198 | local ok_json, json = pcall(vim.json.decode, line) 199 | if ok_json and json and json.type == 'match' and json.data then 200 | local row = json.data.line_number - 1 201 | 202 | if json.data.submatches and #json.data.submatches > 0 then 203 | for _, submatch in ipairs(json.data.submatches) do 204 | local col = submatch.start 205 | 206 | local actual_row 207 | if direction == FORWARD then 208 | actual_row = base_row + row 209 | else 210 | actual_row = curow - 1 - row 211 | end 212 | 213 | table.insert(targets, { 214 | row = actual_row, 215 | col = col, 216 | }) 217 | 218 | count = count + 1 219 | if count >= state.max_targets then 220 | break 221 | end 222 | end 223 | end 224 | 225 | if count >= state.max_targets then 226 | break 227 | end 228 | end 229 | end 230 | end 231 | end 232 | 233 | if direction == BACKWARD then 234 | table.sort(targets, function(a, b) 235 | return a.row < b.row 236 | end) 237 | end 238 | 239 | if #targets == 0 then 240 | cleanup() 241 | return 242 | end 243 | 244 | mark_targets(targets) 245 | end) 246 | end) 247 | end) 248 | end 249 | end 250 | 251 | api.nvim_set_hl(0, 'JumpMotionTarget', { 252 | fg = '#ff4800', 253 | bold = true, 254 | }) 255 | 256 | api.nvim_set_hl(0, 'JumpMotionDim', { 257 | fg = '#555555', 258 | }) 259 | 260 | return { charForward = M.char(FORWARD), charBackward = M.char(BACKWARD) } 261 | -------------------------------------------------------------------------------- /lua/private/term.lua: -------------------------------------------------------------------------------- 1 | local api = vim.api 2 | local fn = vim.fn 3 | 4 | local M = {} 5 | 6 | local terminals = {} 7 | local current_term_id = nil 8 | 9 | local default_config = { 10 | size_presets = { 11 | small = { width = 0.5, height = 0.4 }, 12 | medium = { width = 0.7, height = 0.6 }, 13 | large = { width = 0.9, height = 0.8 }, 14 | full = { width = 0.95, height = 0.95 }, 15 | }, 16 | default_size = 'medium', 17 | title = ' Terminal ', 18 | title_pos = 'center', 19 | show_statusline = false, 20 | auto_insert = true, 21 | auto_close = true, 22 | } 23 | 24 | local config = vim.deepcopy(default_config) 25 | 26 | local term_id_counter = 0 27 | local function gen_term_id() 28 | term_id_counter = term_id_counter + 1 29 | return term_id_counter 30 | end 31 | 32 | local function calc_win_config(size_preset) 33 | local size = config.size_presets[size_preset or config.default_size] 34 | 35 | local width = math.floor(vim.o.columns * size.width) 36 | local height = math.floor(vim.o.lines * size.height) 37 | local col = math.floor((vim.o.columns - width) / 2) 38 | local row = math.floor((vim.o.lines - height) * 0.4) 39 | 40 | return { 41 | relative = 'editor', 42 | width = width, 43 | height = height, 44 | col = col, 45 | row = row, 46 | style = 'minimal', 47 | zindex = 50, 48 | focusable = true, 49 | } 50 | end 51 | 52 | local function setup_keymaps(term) 53 | local opts = { buffer = term.bufnr, noremap = true, silent = true } 54 | 55 | vim.keymap.set('t', '', '', opts) 56 | vim.keymap.set('t', 'h', 'h', opts) 57 | vim.keymap.set('t', 'j', 'j', opts) 58 | vim.keymap.set('t', 'k', 'k', opts) 59 | vim.keymap.set('t', 'l', 'l', opts) 60 | vim.keymap.set('t', 'q', ':close', opts) 61 | 62 | vim.keymap.set('n', 'q', function() 63 | M.close(term.id) 64 | end, opts) 65 | 66 | local function resize(direction, amount) 67 | if not api.nvim_win_is_valid(term.winid) then 68 | return 69 | end 70 | local win_config = api.nvim_win_get_config(term.winid) 71 | 72 | if direction == 'width' then 73 | win_config.width = math.max(20, win_config.width + amount) 74 | elseif direction == 'height' then 75 | win_config.height = math.max(5, win_config.height + amount) 76 | end 77 | 78 | api.nvim_win_set_config(term.winid, win_config) 79 | end 80 | 81 | vim.keymap.set('n', '', function() 82 | resize('width', -5) 83 | end, opts) 84 | vim.keymap.set('n', '', function() 85 | resize('width', 5) 86 | end, opts) 87 | vim.keymap.set('n', '', function() 88 | resize('height', -2) 89 | end, opts) 90 | vim.keymap.set('n', '', function() 91 | resize('height', 2) 92 | end, opts) 93 | end 94 | 95 | function M.toggle(opts) 96 | opts = opts or {} 97 | 98 | if current_term_id then 99 | local term = terminals[current_term_id] 100 | if term and term.winid and api.nvim_win_is_valid(term.winid) then 101 | local cur_win = api.nvim_get_current_win() 102 | if cur_win == term.winid then 103 | M.close(current_term_id) 104 | return 105 | end 106 | end 107 | end 108 | 109 | M.open(opts) 110 | end 111 | 112 | function M.open(opts) 113 | opts = opts or {} 114 | local term_id = opts.term_id or current_term_id 115 | local term = term_id and terminals[term_id] ---@type table? 116 | 117 | if term and term.winid and api.nvim_win_is_valid(term.winid) then 118 | api.nvim_set_current_win(term.winid) 119 | if config.auto_insert then 120 | vim.cmd('startinsert!') 121 | end 122 | return term 123 | end 124 | local spawn_new = not term 125 | if spawn_new then 126 | term_id = gen_term_id() 127 | local cmd = opts.cmd or (fn.has('win32') == 1 and 'cmd.exe' or os.getenv('SHELL')) 128 | local cwd = opts.cwd or fn.getcwd() 129 | local name = opts.name or ('Term #' .. term_id) 130 | 131 | term = { 132 | id = term_id, 133 | bufnr = nil, 134 | winid = nil, 135 | name = name, 136 | cmd = cmd, 137 | cwd = cwd, 138 | size_preset = opts.size or config.default_size, 139 | job_id = nil, 140 | created_at = os.time(), 141 | } 142 | 143 | terminals[term_id] = term 144 | end 145 | if not term then 146 | return 147 | end 148 | 149 | local win_config = calc_win_config(term.size_preset) 150 | if spawn_new then 151 | term.bufnr = api.nvim_create_buf(false, true) 152 | else 153 | win_config.noautocmd = true 154 | end 155 | 156 | term.winid = api.nvim_open_win(term.bufnr, true, win_config) 157 | vim.bo[term.bufnr].bufhidden = 'hide' 158 | vim.bo[term.bufnr].filetype = 'floatterm' 159 | vim.bo[term.bufnr].buflisted = false 160 | 161 | if spawn_new then 162 | term.job_id = fn.jobstart(term.cmd, { 163 | term = true, 164 | cwd = term.cwd, 165 | on_exit = function(_, _) 166 | if term.winid and api.nvim_win_is_valid(term.winid) then 167 | if config.auto_close then 168 | api.nvim_win_close(term.winid, true) 169 | end 170 | end 171 | 172 | terminals[term.id] = nil 173 | if current_term_id == term.id then 174 | current_term_id = nil 175 | end 176 | end, 177 | }) 178 | 179 | setup_keymaps(term) 180 | end 181 | 182 | if config.auto_insert then 183 | vim.cmd('startinsert!') 184 | end 185 | 186 | api.nvim_create_autocmd('WinClosed', { 187 | pattern = tostring(term.winid), 188 | once = true, 189 | callback = function() 190 | term.winid = nil 191 | end, 192 | }) 193 | 194 | current_term_id = term.id 195 | return term 196 | end 197 | 198 | function M.close(term_id) 199 | local term = term_id and terminals[term_id] or terminals[current_term_id] 200 | if not term or not term.winid or not api.nvim_win_is_valid(term.winid) then 201 | return 202 | end 203 | 204 | api.nvim_win_close(term.winid, true) 205 | term.winid = nil 206 | end 207 | 208 | function M.send(cmd, term_id) 209 | local term = term_id and terminals[term_id] or terminals[current_term_id] 210 | if not term or not term.job_id then 211 | vim.notify('Terminal not found', vim.log.levels.WARN) 212 | return 213 | end 214 | 215 | api.nvim_chan_send(term.job_id, cmd .. '\n') 216 | end 217 | 218 | function M.exec(cmd, opts) 219 | opts = opts or {} 220 | local term = M.open(opts) 221 | vim.defer_fn(function() 222 | if term and term.job_id then 223 | M.send(cmd, term.id) 224 | end 225 | end, 100) 226 | end 227 | 228 | function M.list() 229 | return vim.tbl_values(terminals) 230 | end 231 | 232 | function M.select() 233 | local term_list = M.list() 234 | 235 | if #term_list == 0 then 236 | vim.notify('No terminals', vim.log.levels.INFO) 237 | return 238 | end 239 | 240 | local items = vim.tbl_map(function(t) 241 | local status = t.winid and api.nvim_win_is_valid(t.winid) and '[Open]' or '[Hidden]' 242 | return string.format('%d: %s %s', t.id, t.name, status) 243 | end, term_list) 244 | 245 | vim.ui.select(items, { 246 | prompt = 'Select terminal:', 247 | }, function(_, idx) 248 | if idx then 249 | M.open({ term_id = term_list[idx].id }) 250 | end 251 | end) 252 | end 253 | 254 | function M.kill(term_id) 255 | local term = term_id and terminals[term_id] or terminals[current_term_id] 256 | if not term then 257 | return 258 | end 259 | 260 | if term.job_id then 261 | fn.jobstop(term.job_id) 262 | end 263 | 264 | if term.bufnr and api.nvim_buf_is_valid(term.bufnr) then 265 | api.nvim_buf_delete(term.bufnr, { force = true }) 266 | end 267 | 268 | terminals[term.id] = nil 269 | if current_term_id == term.id then 270 | current_term_id = nil 271 | end 272 | end 273 | 274 | return M 275 | -------------------------------------------------------------------------------- /snippets/javascriptreact.json: -------------------------------------------------------------------------------- 1 | { 2 | "Import React": { 3 | "prefix": "imr", 4 | "body": ["import * as React from 'react';\n"], 5 | "description": "Import React" 6 | }, 7 | 8 | "Import React and Component": { 9 | "prefix": "imrc", 10 | "body": [ 11 | "import * as React from 'react';", 12 | "import { Component } from 'react';\n" 13 | ], 14 | "description": "Import React, { Component }" 15 | }, 16 | 17 | "Import ReactDOM": { 18 | "prefix": "imrd", 19 | "body": ["import ReactDOM from 'react-dom';"], 20 | "description": "Import ReactDOM" 21 | }, 22 | 23 | "Import React, { useState }": { 24 | "prefix": "imrs", 25 | "body": [ 26 | "import { useState } from 'react';\n" 27 | ], 28 | "description": "Import React, { useState }" 29 | }, 30 | 31 | "Import React, { useState, useEffect }": { 32 | "prefix": "imrse", 33 | "body": [ 34 | "import * as React from 'react';", 35 | "import { useState, useEffect } from 'react';\n" 36 | ], 37 | "description": "Import React, { useState, useEffect }" 38 | }, 39 | 40 | "Import Pure Component": { 41 | "prefix": "impc", 42 | "body": ["import React, { PureComponent } from 'react';\n"], 43 | "description": "Import React, { PureComponent }" 44 | }, 45 | 46 | "Class Component": { 47 | "prefix": "cc", 48 | "body": [ 49 | "interface $1Props {", 50 | "\t$2", 51 | "}", 52 | " ", 53 | "interface $1State {", 54 | "\t$3", 55 | "}", 56 | " ", 57 | "class $1 extends React.Component<$1Props, $1State> {", 58 | "\tstate = { $4: $5 }", 59 | "\trender() { ", 60 | "\t\treturn ( $0 );", 61 | "\t}", 62 | "}", 63 | " ", 64 | "export default $1;" 65 | ], 66 | "description": "Class Component" 67 | }, 68 | 69 | "Class Pure Component": { 70 | "prefix": "cpc", 71 | "body": [ 72 | "export interface $1Props {", 73 | "\t$2", 74 | "}", 75 | " ", 76 | "export interface $1State {", 77 | "\t$3", 78 | "}", 79 | " ", 80 | "class $1 extends React.PureComponent<$1Props, $1State> {", 81 | "\tstate = { $4: $5 }", 82 | "\trender() { ", 83 | "\t\treturn ( $0 );", 84 | "\t}", 85 | "}", 86 | " ", 87 | "export default $1;" 88 | ], 89 | "description": "Class Pure Component" 90 | }, 91 | 92 | "Class Component Constructor": { 93 | "prefix": "ccc", 94 | "body": [ 95 | "interface $1Props {", 96 | "\t$2", 97 | "}", 98 | " ", 99 | "interface $1State {", 100 | "\t$3", 101 | "}", 102 | " ", 103 | "class $1 extends React.Component<$1Props, $1State> {", 104 | "\tconstructor(props: $1Props) {", 105 | "\t\tsuper(props);", 106 | "\t\tthis.state = { $4: $5 };", 107 | "\t}", 108 | "\trender() { ", 109 | "\t\treturn ( $0 );", 110 | "\t}", 111 | "}", 112 | " ", 113 | "export default $1;" 114 | ], 115 | "description": "Class Component with Constructor" 116 | }, 117 | 118 | "Function Component": { 119 | "prefix": "fc", 120 | "body": [ 121 | "interface $1Props {", 122 | "\t$2", 123 | "}", 124 | " ", 125 | "const $1: FunctionComponent<$1Props> = ($3) => {", 126 | "\treturn ( $0 );", 127 | "}", 128 | " ", 129 | "export default $1;" 130 | ], 131 | "description": "Function Component" 132 | }, 133 | 134 | "Function Syntax Component": { 135 | "prefix": "ffc", 136 | "body": [ 137 | "function $1($2) {", 138 | "\treturn ( $0 );", 139 | "}", 140 | "", 141 | "export default $1;" 142 | ], 143 | "description": "Function Syntax Component" 144 | }, 145 | 146 | "Stateless Function Component": { 147 | "prefix": "sfc", 148 | "body": [ 149 | "const $1 = ($2) => {", 150 | "\treturn ( $0 );", 151 | "}", 152 | " ", 153 | "export default $1;" 154 | ], 155 | "description": "Stateless Function Component" 156 | }, 157 | 158 | "componentDidMount": { 159 | "prefix": "cdm", 160 | "body": ["componentDidMount() {", "\t$0", "}"], 161 | "description": "componentDidMount" 162 | }, 163 | 164 | "useEffect": { 165 | "prefix": "uef", 166 | "body": [ 167 | "useEffect(() => {", 168 | "\t$1", 169 | "}, []);" 170 | ], 171 | "description": "useEffect Hook" 172 | }, 173 | 174 | "componentWillMount": { 175 | "prefix": "cwm", 176 | "body": ["//WARNING! To be deprecated in React v17. Use componentDidMount instead.", "componentWillMount() {", "\t$0", "}"], 177 | "description": "componentWillMount" 178 | }, 179 | 180 | "componentWillReceiveProps": { 181 | "prefix": "cwrp", 182 | "body": ["//WARNING! To be deprecated in React v17. Use new lifecycle static getDerivedStateFromProps instead.", "componentWillReceiveProps(nextProps: $1Props) {", "\t$0", "}"], 183 | "description": "componentWillReceiveProps" 184 | }, 185 | 186 | "getDerivedStateFromProps": { 187 | "prefix": "gds", 188 | "body": ["static getDerivedStateFromProps(nextProps: $1Props, prevState: $1State) {", "\t$0", "}"], 189 | "description": "getDerivedStateFromProps" 190 | }, 191 | 192 | "shouldComponentUpdate": { 193 | "prefix": "scu", 194 | "body": ["shouldComponentUpdate(nextProps: $1Props, nextState: $1State) {", "\t$0", "}"], 195 | "description": "shouldComponentUpdate" 196 | }, 197 | 198 | "componentWillUpdate": { 199 | "prefix": "cwu", 200 | "body": ["//WARNING! To be deprecated in React v17. Use componentDidUpdate instead.", "componentWillUpdate(nextProps: $1Props, nextState: $1State) {", "\t$0", "}"], 201 | "description": "componentWillUpdate" 202 | }, 203 | 204 | "componentDidUpdate": { 205 | "prefix": "cdu", 206 | "body": ["componentDidUpdate(prevProps: $1Props, prevState: $1State) {", "\t$0", "}"], 207 | "description": "componentDidUpdate" 208 | }, 209 | 210 | "componentWillUnmount": { 211 | "prefix": "cwun", 212 | "body": ["componentWillUnmount() {", "\t$0", "}"], 213 | "description": "componentWillUnmount" 214 | }, 215 | 216 | "componentDidCatch": { 217 | "prefix": "cdc", 218 | "body": ["componentDidCatch(error, info) {", "\t$0", "}"], 219 | "description": "componentDidCatch" 220 | }, 221 | 222 | "getSnapshotBeforeUpdate": { 223 | "prefix": "gsbu", 224 | "body": ["getSnapshotBeforeUpdate(prevProps: $1Props, prevState: $1State) {", "\t$0", "}"], 225 | "description": "getSnapshotBeforeUpdate" 226 | }, 227 | 228 | "setState": { 229 | "prefix": "ss", 230 | "body": ["this.setState({ $1: $2 });"], 231 | "description": "setState" 232 | }, 233 | 234 | "Functional setState": { 235 | "prefix": "ssf", 236 | "body": ["this.setState(prevState => {", "\treturn { $1: prevState.$1 };", "});"], 237 | "description": "Functional setState" 238 | }, 239 | 240 | "Declare a new state variable using State Hook": { 241 | "prefix": "usf", 242 | "body": [ 243 | "const [${1}, set${1/(.*)/${1:/capitalize}/}] = useState($2);" 244 | ], 245 | "description": "Declare a new state Variable using the State Hook. Hit Tab to apply CamelCase to function" 246 | }, 247 | 248 | "render": { 249 | "prefix": "ren", 250 | "body": ["render() {", "\treturn (", "\t\t $0", "\t);", "}"], 251 | "description": "render" 252 | }, 253 | 254 | "Render Prop": { 255 | "prefix": "rprop", 256 | "body": [ 257 | "interface $1Props {", 258 | "\trender: (state: $1State) => JSX.Element", 259 | "}", 260 | " ", 261 | "interface $1State {", 262 | "\t$2", 263 | "}", 264 | " ", 265 | "class $1 extends React.Component<$1Props, $1State> {", 266 | "\tstate = { $3: $4 }", 267 | "\trender() { ", 268 | "\t\treturn this.props.render(this.state);", 269 | "\t}", 270 | "}", 271 | " ", 272 | "export default $1;" 273 | ], 274 | "description": "Render Prop" 275 | } 276 | } 277 | -------------------------------------------------------------------------------- /snippets/cpp.json: -------------------------------------------------------------------------------- 1 | { 2 | "beginend": { 3 | "prefix": "beginend", 4 | "body": "${1:container}.begin(), ${1:container}.end()", 5 | "description": "Snippet for begin/end iterator pair" 6 | }, 7 | "for_i": { 8 | "prefix": "fori", 9 | "body": [ 10 | "for (int ${1:i} = 0; ${1:i} < ${2:n}; ${1:i}++) {", 11 | "\t$0", 12 | "}" 13 | ], 14 | "description": "Integer for loop" 15 | }, 16 | "for_range": { 17 | "prefix": "for", 18 | "body": [ 19 | "for (const auto& ${1:item} : ${2:container}) {", 20 | "\t$0", 21 | "}" 22 | ], 23 | "description": "Range-based for loop (const ref)" 24 | }, 25 | "for_range_mut": { 26 | "prefix": "form", 27 | "body": [ 28 | "for (auto& ${1:item} : ${2:container}) {", 29 | "\t$0", 30 | "}" 31 | ], 32 | "description": "Range-based for loop (mutable)" 33 | }, 34 | "for_iter": { 35 | "prefix": "forit", 36 | "body": [ 37 | "for (auto ${1:it} = ${2:vec}.begin(); ${1:it} != ${2:vec}.end(); ++${1:it}) {", 38 | "\t$0", 39 | "}" 40 | ], 41 | "description": "Iterator for loop" 42 | }, 43 | "while": { 44 | "prefix": "while", 45 | "body": [ 46 | "while (${1:condition}) {", 47 | "\t$0", 48 | "}" 49 | ], 50 | "description": "While loop" 51 | }, 52 | "do_while": { 53 | "prefix": "do", 54 | "body": [ 55 | "do {", 56 | "\t$0", 57 | "} while (${1:condition});" 58 | ], 59 | "description": "Do-while loop" 60 | }, 61 | "if": { 62 | "prefix": "if", 63 | "body": [ 64 | "if (${1:condition}) {", 65 | "\t$0", 66 | "}" 67 | ], 68 | "description": "If statement" 69 | }, 70 | "if_else": { 71 | "prefix": "ife", 72 | "body": [ 73 | "if (${1:condition}) {", 74 | "\t$2", 75 | "} else {", 76 | "\t$0", 77 | "}" 78 | ], 79 | "description": "If-else statement" 80 | }, 81 | "elif": { 82 | "prefix": "elif", 83 | "body": [ 84 | "else if (${1:condition}) {", 85 | "\t$0", 86 | "}" 87 | ], 88 | "description": "Else-if statement" 89 | }, 90 | "switch": { 91 | "prefix": "switch", 92 | "body": [ 93 | "switch (${1:expr}) {", 94 | "\tcase ${2:value}:", 95 | "\t\t$0", 96 | "\t\tbreak;", 97 | "\tdefault:", 98 | "\t\tbreak;", 99 | "}" 100 | ], 101 | "description": "Switch statement" 102 | }, 103 | "class": { 104 | "prefix": "class", 105 | "body": [ 106 | "class ${1:ClassName} {", 107 | "public:", 108 | "\t${1:ClassName}() = default;", 109 | "\t~${1:ClassName}() = default;", 110 | "\t", 111 | "private:", 112 | "\t$0", 113 | "};" 114 | ], 115 | "description": "Class definition" 116 | }, 117 | "struct": { 118 | "prefix": "struct", 119 | "body": [ 120 | "struct ${1:Name} {", 121 | "\t$0", 122 | "};" 123 | ], 124 | "description": "Struct definition" 125 | }, 126 | "enum_class": { 127 | "prefix": "enum", 128 | "body": [ 129 | "enum class ${1:Name} {", 130 | "\t$0", 131 | "};" 132 | ], 133 | "description": "Enum class (scoped)" 134 | }, 135 | "namespace": { 136 | "prefix": "ns", 137 | "body": [ 138 | "namespace ${1:name} {", 139 | "", 140 | "$0", 141 | "", 142 | "} // namespace ${1:name}" 143 | ], 144 | "description": "Namespace" 145 | }, 146 | "function": { 147 | "prefix": "fn", 148 | "body": [ 149 | "${1:void} ${2:name}(${3:}) {", 150 | "\t$0", 151 | "}" 152 | ], 153 | "description": "Function definition" 154 | }, 155 | "main": { 156 | "prefix": "main", 157 | "body": [ 158 | "int main() {", 159 | "\t$0", 160 | "\treturn 0;", 161 | "}" 162 | ], 163 | "description": "Main function" 164 | }, 165 | "lambda": { 166 | "prefix": "lam", 167 | "body": "[${1:}](${2:}) { $0 }", 168 | "description": "Lambda expression" 169 | }, 170 | "template": { 171 | "prefix": "temp", 172 | "body": [ 173 | "template ", 174 | "$0" 175 | ], 176 | "description": "Template" 177 | }, 178 | "template_class": { 179 | "prefix": "tempc", 180 | "body": [ 181 | "template ", 182 | "class ${2:Name} {", 183 | "public:", 184 | "\t$0", 185 | "};" 186 | ], 187 | "description": "Template class" 188 | }, 189 | "unique_ptr": { 190 | "prefix": "uptr", 191 | "body": "auto ${1:ptr} = std::make_unique<${2:Type}>(${3:});$0", 192 | "description": "Unique pointer" 193 | }, 194 | "shared_ptr": { 195 | "prefix": "sptr", 196 | "body": "auto ${1:ptr} = std::make_shared<${2:Type}>(${3:});$0", 197 | "description": "Shared pointer" 198 | }, 199 | "vector": { 200 | "prefix": "vec", 201 | "body": "std::vector<${1:int}> ${2:v}${3:;}$0", 202 | "description": "Vector declaration" 203 | }, 204 | "map": { 205 | "prefix": "map", 206 | "body": "std::map<${1:KeyType}, ${2:ValueType}> ${3:m}${4:;}$0", 207 | "description": "Map declaration" 208 | }, 209 | "unordered_map": { 210 | "prefix": "umap", 211 | "body": "std::unordered_map<${1:KeyType}, ${2:ValueType}> ${3:m}${4:;}$0", 212 | "description": "Unordered map declaration" 213 | }, 214 | "set": { 215 | "prefix": "set", 216 | "body": "std::set<${1:Type}> ${2:s}${3:;}$0", 217 | "description": "Set declaration" 218 | }, 219 | "sort": { 220 | "prefix": "sort", 221 | "body": "std::sort(${1:vec}.begin(), ${1:vec}.end());$0", 222 | "description": "Sort container" 223 | }, 224 | "find": { 225 | "prefix": "find", 226 | "body": "auto ${1:it} = std::find(${2:vec}.begin(), ${2:vec}.end(), ${3:val});$0", 227 | "description": "Find in container" 228 | }, 229 | "find_if": { 230 | "prefix": "findif", 231 | "body": [ 232 | "auto ${1:it} = std::find_if(${2:vec}.begin(), ${2:vec}.end(),", 233 | "\t[](const auto& ${3:x}) { return ${0:condition}; });" 234 | ], 235 | "description": "Find if in container" 236 | }, 237 | "transform": { 238 | "prefix": "transform", 239 | "body": [ 240 | "std::transform(${1:in}.begin(), ${1:in}.end(), ${2:out}.begin(),", 241 | "\t[](const auto& ${3:x}) { return ${0:expr}; });" 242 | ], 243 | "description": "Transform algorithm" 244 | }, 245 | "include": { 246 | "prefix": "#inc", 247 | "body": "#include <${1:iostream}>", 248 | "description": "Include header" 249 | }, 250 | "include_local": { 251 | "prefix": "#incl", 252 | "body": "#include \"${1:header.h}\"", 253 | "description": "Include local header" 254 | }, 255 | "pragma_once": { 256 | "prefix": "#pragma", 257 | "body": "#pragma once", 258 | "description": "Pragma once" 259 | }, 260 | "ifndef": { 261 | "prefix": "#guard", 262 | "body": [ 263 | "#ifndef ${1:HEADER}_H", 264 | "#define ${1:HEADER}_H", 265 | "", 266 | "$0", 267 | "", 268 | "#endif // ${1:HEADER}_H" 269 | ], 270 | "description": "Header guard" 271 | }, 272 | "cout": { 273 | "prefix": "cout", 274 | "body": "std::cout << ${1:} << '\\n';$0", 275 | "description": "Console output" 276 | }, 277 | "cerr": { 278 | "prefix": "cerr", 279 | "body": "std::cerr << ${1:} << '\\n';$0", 280 | "description": "Error output" 281 | }, 282 | "cin": { 283 | "prefix": "cin", 284 | "body": "std::cin >> ${1:var};$0", 285 | "description": "Console input" 286 | }, 287 | "try_catch": { 288 | "prefix": "try", 289 | "body": [ 290 | "try {", 291 | "\t$0", 292 | "} catch (const std::exception& ${1:e}) {", 293 | "\t${2:// handle}", 294 | "}" 295 | ], 296 | "description": "Try-catch block" 297 | }, 298 | "auto": { 299 | "prefix": "auto", 300 | "body": "auto ${1:var} = ${2:value};$0", 301 | "description": "Auto variable" 302 | }, 303 | "constexpr": { 304 | "prefix": "constexpr", 305 | "body": "constexpr ${1:auto} ${2:var} = ${3:value};$0", 306 | "description": "Constexpr variable" 307 | }, 308 | "nullptr": { 309 | "prefix": "nullptr", 310 | "body": "nullptr", 311 | "description": "Null pointer" 312 | }, 313 | "static_cast": { 314 | "prefix": "scast", 315 | "body": "static_cast<${1:Type}>(${2:expr})$0", 316 | "description": "Static cast" 317 | }, 318 | "dynamic_cast": { 319 | "prefix": "dcast", 320 | "body": "dynamic_cast<${1:Type}>(${2:expr})$0", 321 | "description": "Dynamic cast" 322 | }, 323 | "structured_binding": { 324 | "prefix": "auto[]", 325 | "body": "auto [${1:a}, ${2:b}] = ${3:pair};$0", 326 | "description": "Structured binding" 327 | } 328 | } 329 | -------------------------------------------------------------------------------- /lua/private/grep.lua: -------------------------------------------------------------------------------- 1 | local api, QUICK, LOCAL, FORWARD, BACKWARD, mapset = vim.api, 1, 2, 1, 2, vim.keymap.set 2 | local treesitter, fn = vim.treesitter, vim.fn 3 | 4 | local async = require('vim._async') 5 | 6 | local state = { 7 | preview = { 8 | win = nil, 9 | enabled = false, 10 | }, 11 | count = 0, 12 | match_ids = {}, 13 | done = false, 14 | win = nil, 15 | buf = nil, 16 | } 17 | 18 | local function create_preview_window(bufnr) 19 | local preview = state.preview 20 | local qf_win = api.nvim_get_current_win() 21 | local qf_width = api.nvim_win_get_width(qf_win) 22 | local qf_position = api.nvim_win_get_position(qf_win) 23 | 24 | local preview_height = math.floor(vim.o.lines * 0.6) 25 | local preview_row = qf_position[1] - preview_height - 3 26 | 27 | if preview_row < 0 then 28 | preview_row = 0 29 | preview_height = qf_position[1] - 1 30 | end 31 | 32 | local win_opts = { 33 | style = 'minimal', 34 | relative = 'editor', 35 | row = preview_row, 36 | col = qf_position[2], 37 | width = qf_width, 38 | height = preview_height, 39 | focusable = false, 40 | } 41 | 42 | preview.win = api.nvim_open_win(bufnr, false, win_opts) 43 | 44 | mapset('n', '', function() 45 | if preview.win and api.nvim_win_is_valid(preview.win) then 46 | api.nvim_win_close(preview.win, true) 47 | preview.win = nil 48 | end 49 | end, { buffer = preview.buf }) 50 | 51 | api.nvim_create_autocmd('WinClosed', { 52 | buffer = bufnr, 53 | once = true, 54 | callback = function() 55 | api.nvim_win_close(preview.win, true) 56 | state.count = 0 57 | state.preview.win = nil 58 | state.preview.enabled = false 59 | end, 60 | }) 61 | end 62 | 63 | local function update_preview() 64 | local preview = state.preview 65 | if not preview.enabled then 66 | return 67 | end 68 | 69 | local win_id = api.nvim_get_current_win() 70 | local win_info = fn.getwininfo(win_id)[1] 71 | 72 | local is_loclist = win_info.loclist == 1 73 | 74 | local idx = fn.line('.') 75 | local list = is_loclist and fn.getloclist(0) or fn.getqflist() 76 | 77 | if idx > 0 and idx <= #list then 78 | local item = list[idx] 79 | if not item.bufnr then 80 | return 81 | end 82 | 83 | if not preview.win or not api.nvim_win_is_valid(preview.win) then 84 | create_preview_window(item.bufnr) 85 | end 86 | 87 | local ft = vim.filetype.match({ buf = item.bufnr }) 88 | if ft then 89 | local lang = treesitter.language.get_lang(ft) 90 | local ok = pcall(treesitter.get_parser, item.bufnr, lang) 91 | if ok then 92 | vim.treesitter.start(item.bufnr, lang) 93 | end 94 | end 95 | 96 | if preview.win and api.nvim_win_is_valid(preview.win) then 97 | api.nvim_win_set_buf(preview.win, item.bufnr) 98 | api.nvim_win_set_cursor(preview.win, { item.lnum, item.col }) 99 | api.nvim_win_call(preview.win, function() 100 | vim.cmd('normal! zz') 101 | end) 102 | end 103 | end 104 | end 105 | 106 | local function toggle_preview(buf) 107 | local preview = state.preview 108 | preview.enabled = not preview.enabled 109 | if preview.enabled then 110 | update_preview() 111 | api.nvim_create_autocmd('CursorMoved', { 112 | buffer = buf, 113 | callback = update_preview, 114 | }) 115 | elseif preview.win and api.nvim_win_is_valid(preview.win) then 116 | api.nvim_win_close(preview.win, true) 117 | preview.win = nil 118 | end 119 | end 120 | 121 | local function setup_init(buf, is_quick) 122 | vim.opt_local.wrap = false 123 | vim.opt_local.number = false 124 | vim.opt_local.relativenumber = false 125 | vim.opt_local.signcolumn = 'no' 126 | 127 | local win = api.nvim_get_current_win() 128 | fn.clearmatches(win) 129 | 130 | fn.matchadd('qfFileName', '^▸ \\zs.*', 12, -1, { window = win }) 131 | fn.matchadd('qfLineNr', '^\\s\\+\\d\\+:\\d\\+', 20, -1, { window = win }) 132 | fn.matchadd('qfSeparator', '│', 15, -1, { window = win }) 133 | fn.matchadd('qfText', '│ \\zs.*', 10, -1, { window = win }) 134 | 135 | local move = function(dir) 136 | return function() 137 | api.nvim_buf_call(buf, function() 138 | pcall( 139 | vim.cmd, 140 | dir == FORWARD and (is_quick and 'cnext' or 'lnext') or (is_quick and 'cprev' or 'lprev') 141 | ) 142 | update_preview() 143 | end) 144 | end 145 | end 146 | 147 | local preview = state.preview 148 | mapset('n', 'q', function() 149 | if preview.win and api.nvim_win_is_valid(preview.win) then 150 | api.nvim_win_close(preview.win, true) 151 | preview.win = nil 152 | end 153 | vim.cmd('cclose') 154 | end, { buffer = buf }) 155 | 156 | mapset('n', '', move(FORWARD), { buffer = buf }) 157 | mapset('n', '', move(BACKWARD), { buffer = buf }) 158 | 159 | mapset('n', 'p', function() 160 | toggle_preview(buf) 161 | end, { buffer = buf }) 162 | end 163 | 164 | local function update_title() 165 | local width = 15 166 | local bar = '' 167 | 168 | if not state.done then 169 | local anim_pos = state.count % width 170 | for i = 1, width do 171 | bar = bar .. (i == anim_pos and '●' or '○') 172 | end 173 | else 174 | bar = string.rep('●', width) 175 | end 176 | 177 | vim.wo[state.win].stl = 178 | string.format(' %s [%s] %d matches', state.done and 'Done' or 'Searching', bar, state.count) 179 | api.nvim__redraw({ 180 | win = state.win, 181 | buf = state.buf, 182 | statusline = true, 183 | }) 184 | end 185 | 186 | local function grep(t, ...) 187 | local args = { ... } 188 | 189 | -- Use async.run to run the async function 190 | async.run(function() 191 | local grepprg = vim.o.grepprg 192 | local cmd = vim.split(grepprg, '%s+', { trimempty = true }) 193 | local qf_fn = t == QUICK and function(...) 194 | fn.setqflist(...) 195 | end or function(...) 196 | fn.setloclist(0, ...) 197 | end 198 | 199 | for _, arg in ipairs(args) do 200 | table.insert(cmd, arg) 201 | end 202 | table.insert(cmd, '--fixed-strings') 203 | 204 | local opened = false 205 | local id = nil 206 | local batch_size = 200 207 | local chunk = {} 208 | local seen_files = {} 209 | 210 | local result = async.await(3, vim.system, cmd, { 211 | text = true, 212 | stdout = function(err, data) 213 | assert(not err) 214 | state.done = not data 215 | local process = {} 216 | if data then 217 | local lines = vim.split(data, '\n', { trimempty = true }) 218 | if #lines > 0 then 219 | for _, line in ipairs(lines) do 220 | -- parse format: filename:lnum:col:text 221 | local filename, lnum, col, text = line:match('^([^:]+):(%d+):(%d+):(.*)$') 222 | if filename and lnum and col and text then 223 | -- check is new file 224 | if not seen_files[filename] then 225 | -- insert header for it 226 | table.insert(chunk, string.format('%s:0:0:', filename)) 227 | seen_files[filename] = true 228 | end 229 | end 230 | table.insert(chunk, line) 231 | end 232 | end 233 | end 234 | 235 | if #chunk >= batch_size then 236 | -- Fix the unpack issue by using loops instead 237 | process = {} 238 | for i = 1, batch_size do 239 | process[i] = chunk[i] 240 | end 241 | 242 | local new_chunk = {} 243 | for i = batch_size + 1, #chunk do 244 | table.insert(new_chunk, chunk[i]) 245 | end 246 | chunk = new_chunk 247 | end 248 | 249 | vim.schedule(function() 250 | if #process > 0 or data ~= nil then 251 | qf_fn({}, 'a', { 252 | lines = not data and chunk or process, 253 | id = id, 254 | efm = vim.o.errorformat, 255 | title = 'Grep', 256 | quickfixtextfunc = function(info) 257 | local lines = {} 258 | local list = info.quickfix == 1 and vim.fn.getqflist() 259 | or vim.fn.getloclist(info.winid) 260 | local last_bufnr = nil 261 | 262 | for i = info.start_idx, info.end_idx do 263 | local item = list[i] 264 | if item and item.valid == 1 and item.lnum > 0 and item.text ~= '' then 265 | local filename = item.bufnr > 0 and vim.fn.bufname(item.bufnr) or '' 266 | local text = item.text or '' 267 | 268 | if item.bufnr ~= last_bufnr then 269 | table.insert(lines, string.format('▸ %s', filename)) 270 | last_bufnr = item.bufnr 271 | end 272 | 273 | table.insert( 274 | lines, 275 | string.format(' %4d:%-3d │ %s', item.lnum, item.col, text) 276 | ) 277 | end 278 | end 279 | return lines 280 | end, 281 | }) 282 | 283 | if not opened then 284 | vim.cmd(t == QUICK and 'copen' or 'lopen') 285 | local buf = api.nvim_get_current_buf() 286 | state.win = api.nvim_get_current_win() 287 | setup_init(buf, t == QUICK) 288 | opened = true 289 | end 290 | 291 | state.count = state.count + (not data and #chunk or #process) 292 | update_title() 293 | end 294 | end) 295 | end, 296 | }) 297 | 298 | if result.code ~= 0 then 299 | vim.notify('Grep failed with exit code: ' .. result.code, vim.log.levels.ERROR) 300 | end 301 | end) 302 | end 303 | 304 | api.nvim_create_user_command('Grep', function(opts) 305 | grep(LOCAL, opts.args) 306 | end, { nargs = '+', complete = 'file_in_path', desc = 'Search using location list' }) 307 | 308 | api.nvim_create_user_command('GREP', function(opts) 309 | grep(QUICK, opts.args) 310 | end, { nargs = '+', complete = 'file_in_path', desc = 'Search using quickfix list' }) 311 | 312 | api.nvim_create_autocmd('CmdlineEnter', { 313 | pattern = ':', 314 | callback = function() 315 | vim.cmd( 316 | [[cnoreabbrev grep (getcmdtype() ==# ':' && getcmdline() ==# 'grep') ? 'Grep' : 'grep']] 317 | ) 318 | end, 319 | }) 320 | -------------------------------------------------------------------------------- /colors/monokai.lua: -------------------------------------------------------------------------------- 1 | local colors = { 2 | -- Monokai base colors 3 | bg = '#272822', -- Main background 4 | bg_dark = '#1e1f1c', -- Darker background 5 | bg_light = '#3e3d32', -- Lighter background 6 | bg_visual = '#49483e', -- Visual selection 7 | bg_search = '#4e4a3e', -- Search highlight 8 | fg = '#f8f8f2', -- Main foreground 9 | fg_dark = '#75715e', -- Comments and secondary text 10 | fg_light = '#f8f8f0', -- Bright text 11 | 12 | -- Monokai accent colors 13 | red = '#f92672', -- Keywords 14 | green = '#a6e22e', -- Strings, functions 15 | yellow = '#e6db74', -- Strings, numbers 16 | blue = '#66d9ef', -- Types, constants 17 | purple = '#ae81ff', -- Numbers, constants 18 | cyan = '#a1efe4', -- Special chars 19 | orange = '#fd971f', -- Numbers, constants 20 | pink = '#f92672', -- Keywords 21 | 22 | -- UI colors 23 | cursor_line = '#3c3d37', 24 | line_number = '#90908a', 25 | selection = '#49483e', 26 | comment = '#75715e', 27 | error = '#f92672', 28 | warning = '#fd971f', 29 | info = '#66d9ef', 30 | hint = '#a6e22e', 31 | } 32 | 33 | vim.g.colors_name = 'monokai' 34 | 35 | local function shl(group, properties) 36 | vim.api.nvim_set_hl(0, group, properties) 37 | end 38 | 39 | local function load_monokai() 40 | -- General editor highlights 41 | shl('Normal', { fg = colors.fg, bg = colors.bg }) 42 | shl('EndOfBuffer', { fg = colors.bg }) 43 | shl('CursorLine', { bg = colors.cursor_line }) 44 | shl('CursorLineNr', { fg = colors.yellow, bg = colors.cursor_line, bold = true }) 45 | shl('LineNr', { fg = colors.line_number }) 46 | shl('Comment', { fg = colors.comment, italic = true }) 47 | shl('String', { fg = colors.yellow }) 48 | shl('Function', { fg = colors.green }) 49 | shl('Keyword', { fg = colors.red, bold = true }) 50 | shl('Constant', { fg = colors.purple }) 51 | shl('Identifier', { fg = colors.fg }) 52 | shl('Statement', { fg = colors.red }) 53 | shl('Number', { fg = colors.purple }) 54 | shl('PreProc', { fg = colors.red }) 55 | shl('Type', { fg = colors.blue }) 56 | shl('Special', { fg = colors.orange }) 57 | shl('Operator', { fg = colors.fg }) 58 | shl('Underlined', { fg = colors.blue, underline = true }) 59 | shl('Todo', { fg = colors.bg, bg = colors.orange, bold = true }) 60 | shl('Error', { fg = colors.error, bg = colors.bg, bold = true }) 61 | shl('WarningMsg', { fg = colors.warning }) 62 | shl('IncSearch', { fg = colors.bg, bg = colors.orange }) 63 | shl('Search', { fg = colors.bg, bg = colors.yellow }) 64 | shl('Visual', { bg = colors.selection }) 65 | shl('Pmenu', { fg = colors.fg, bg = colors.bg_dark }) 66 | shl('PmenuMatch', { fg = colors.green, bg = colors.bg_dark, bold = true }) 67 | shl('PmenuMatchSel', { fg = colors.green, bg = colors.bg_light, bold = true }) 68 | shl('PmenuSel', { fg = colors.fg_light, bg = colors.bg_light }) 69 | shl('PmenuSbar', { bg = colors.bg_light }) 70 | shl('PmenuThumb', { bg = colors.fg_dark }) 71 | shl('MatchParen', { bg = colors.bg_light, bold = true }) 72 | shl('WinBar', { bg = colors.bg_light }) 73 | shl('NormalFloat', { bg = colors.bg_dark }) 74 | shl('FloatBorder', { fg = colors.blue }) 75 | shl('Title', { fg = colors.yellow, bold = true }) 76 | shl('WinSeparator', { fg = colors.fg_dark }) 77 | shl('StatusLine', { bg = colors.bg_light, fg = colors.fg }) 78 | shl('StatusLineNC', { bg = colors.bg_dark, fg = colors.fg_dark }) 79 | shl('ModeMsg', { fg = colors.cyan }) 80 | shl('ColorColumn', { bg = colors.cursor_line }) 81 | shl('WildMenu', { fg = colors.bg, bg = colors.yellow }) 82 | shl('Folded', { bg = colors.bg_light, fg = colors.fg_dark }) 83 | shl('ErrorMsg', { fg = colors.error }) 84 | shl('ComplMatchIns', { fg = colors.comment }) 85 | shl('Directory', { fg = colors.blue }) 86 | shl('QuickFixLine', { bold = true }) 87 | shl('qfFileName', { fg = colors.blue }) 88 | shl('qfSeparator', { fg = colors.comment }) 89 | shl('qfLineNr', { link = 'LineNr' }) 90 | shl('qfText', { link = 'Normal' }) 91 | 92 | -- Treesitter highlights 93 | shl('@function', { fg = colors.green }) 94 | shl('@function.builtin', { fg = colors.blue }) 95 | shl('@function.call', { fg = colors.green }) 96 | shl('@function.macro', { fg = colors.orange }) 97 | shl('@variable', { fg = colors.fg }) 98 | shl('@variable.builtin', { fg = colors.blue }) 99 | shl('@variable.parameter', { fg = colors.orange, italic = true }) 100 | shl('@variable.member', { fg = colors.fg }) 101 | shl('@keyword', { fg = colors.red, bold = true }) 102 | shl('@keyword.function', { fg = colors.red }) 103 | shl('@keyword.operator', { fg = colors.red }) 104 | shl('@keyword.import', { fg = colors.red }) 105 | shl('@keyword.type', { fg = colors.blue }) 106 | shl('@keyword.modifier', { fg = colors.red }) 107 | shl('@keyword.repeat', { fg = colors.red }) 108 | shl('@keyword.return', { fg = colors.red }) 109 | shl('@keyword.debug', { fg = colors.red }) 110 | shl('@keyword.exception', { fg = colors.red }) 111 | shl('@keyword.conditional', { fg = colors.red }) 112 | shl('@keyword.conditional.ternary', { fg = colors.red }) 113 | shl('@keyword.directive', { fg = colors.red }) 114 | shl('@keyword.directive.define', { fg = colors.red }) 115 | shl('@string', { fg = colors.yellow }) 116 | shl('@string.documentation', { fg = colors.yellow }) 117 | shl('@string.regexp', { fg = colors.cyan }) 118 | shl('@string.escape', { fg = colors.orange }) 119 | shl('@string.special', { fg = colors.orange }) 120 | shl('@string.special.symbol', { fg = colors.orange }) 121 | shl('@string.special.url', { fg = colors.cyan, underline = true }) 122 | shl('@comment', { fg = colors.comment, italic = true }) 123 | shl('@comment.documentation', { fg = colors.comment, italic = true }) 124 | shl('@comment.error', { fg = colors.error }) 125 | shl('@comment.warning', { fg = colors.warning }) 126 | shl('@comment.note', { fg = colors.info }) 127 | shl('@comment.todo', { fg = colors.bg, bg = colors.orange, bold = true }) 128 | shl('@type', { fg = colors.blue }) 129 | shl('@constant', { fg = colors.purple }) 130 | shl('@constant.builtin', { fg = colors.purple }) 131 | shl('@constant.macro', { fg = colors.purple }) 132 | shl('@constructor', { fg = colors.green }) 133 | shl('@parameter', { fg = colors.orange, italic = true }) 134 | shl('@class', { fg = colors.blue }) 135 | shl('@method', { fg = colors.green }) 136 | shl('@method.call', { fg = colors.green }) 137 | shl('@property', { fg = colors.fg }) 138 | shl('@field', { fg = colors.fg }) 139 | shl('@interface', { fg = colors.blue }) 140 | shl('@namespace', { fg = colors.blue }) 141 | shl('@module', { fg = colors.blue }) 142 | shl('@punctuation', { fg = colors.fg }) 143 | shl('@punctuation.bracket', { fg = colors.fg }) 144 | shl('@punctuation.delimiter', { fg = colors.fg }) 145 | shl('@punctuation.special', { fg = colors.orange }) 146 | shl('@operator', { link = 'Operator' }) 147 | shl('@attribute', { fg = colors.orange }) 148 | shl('@boolean', { fg = colors.purple }) 149 | shl('@number', { fg = colors.purple }) 150 | shl('@number.float', { fg = colors.purple }) 151 | shl('@tag', { fg = colors.red }) 152 | shl('@tag.attribute', { fg = colors.green }) 153 | shl('@tag.delimiter', { fg = colors.fg }) 154 | shl('@markup', { fg = colors.fg }) 155 | shl('@markup.strong', { fg = colors.fg, bold = true }) 156 | shl('@markup.italic', { fg = colors.fg, italic = true }) 157 | shl('@markup.strikethrough', { fg = colors.fg, strikethrough = true }) 158 | shl('@markup.underline', { fg = colors.fg, underline = true }) 159 | shl('@markup.heading', { fg = colors.yellow, bold = true }) 160 | shl('@markup.quote', { fg = colors.comment, italic = true }) 161 | shl('@markup.math', { fg = colors.cyan }) 162 | shl('@markup.environment', { fg = colors.orange }) 163 | shl('@markup.link', { fg = colors.cyan }) 164 | shl('@markup.link.label', { fg = colors.cyan }) 165 | shl('@markup.link.url', { fg = colors.cyan, underline = true }) 166 | shl('@markup.raw', { fg = colors.yellow }) 167 | shl('@markup.raw.block', { fg = colors.yellow }) 168 | shl('@markup.list', { fg = colors.red }) 169 | shl('@markup.list.checked', { fg = colors.green }) 170 | shl('@markup.list.unchecked', { fg = colors.comment }) 171 | shl('@character', { fg = colors.yellow }) 172 | 173 | -- Diagnostics 174 | shl('DiagnosticError', { fg = colors.error }) 175 | shl('DiagnosticWarn', { fg = colors.warning }) 176 | shl('DiagnosticInfo', { fg = colors.info }) 177 | shl('DiagnosticHint', { fg = colors.hint }) 178 | shl('DiagnosticOk', { fg = colors.green }) 179 | shl('DiagnosticUnderlineError', { undercurl = true, sp = colors.error }) 180 | shl('DiagnosticUnderlineWarn', { undercurl = true, sp = colors.warning }) 181 | shl('DiagnosticUnderlineInfo', { undercurl = true, sp = colors.info }) 182 | shl('DiagnosticUnderlineHint', { undercurl = true, sp = colors.hint }) 183 | shl('DiagnosticUnderlineOk', { undercurl = true, sp = colors.green }) 184 | 185 | -- LSP 186 | shl('LspReferenceText', { bg = colors.bg_light }) 187 | shl('LspReferenceRead', { bg = colors.bg_light }) 188 | shl('LspReferenceWrite', { bg = colors.bg_light }) 189 | shl('LspSignatureActiveParameter', { fg = colors.orange, bold = true }) 190 | shl('LspCodeLens', { fg = colors.comment, italic = true }) 191 | shl('LspCodeLensSeparator', { fg = colors.comment }) 192 | 193 | -- Semantic tokens 194 | shl('@lsp.type.class', { fg = colors.blue }) 195 | shl('@lsp.type.decorator', { fg = colors.orange }) 196 | shl('@lsp.type.enum', { fg = colors.blue }) 197 | shl('@lsp.type.enumMember', { fg = colors.purple }) 198 | shl('@lsp.type.function', { fg = colors.green }) 199 | shl('@lsp.type.interface', { fg = colors.blue }) 200 | shl('@lsp.type.macro', { fg = colors.orange }) 201 | shl('@lsp.type.method', { fg = colors.green }) 202 | shl('@lsp.type.namespace', { fg = colors.blue }) 203 | shl('@lsp.type.parameter', { fg = colors.orange, italic = true }) 204 | shl('@lsp.type.property', { fg = colors.fg }) 205 | shl('@lsp.type.struct', { fg = colors.blue }) 206 | shl('@lsp.type.type', { fg = colors.blue }) 207 | shl('@lsp.type.typeParameter', { fg = colors.blue, italic = true }) 208 | shl('@lsp.type.variable', { fg = colors.fg }) 209 | 210 | shl('IndentLine', { fg = colors.bg_light }) 211 | shl('IndentLineCurrent', { fg = colors.fg_dark }) 212 | 213 | -- GitSigns 214 | shl('GitSignsAdd', { fg = colors.green, bg = colors.bg }) 215 | shl('GitSignsChange', { fg = colors.yellow, bg = colors.bg }) 216 | shl('GitSignsDelete', { fg = colors.red, bg = colors.bg }) 217 | shl('GitSignsAddNr', { fg = colors.green }) 218 | shl('GitSignsChangeNr', { fg = colors.yellow }) 219 | shl('GitSignsDeleteNr', { fg = colors.red }) 220 | shl('GitSignsAddLn', { bg = '#2a3f2a' }) 221 | shl('GitSignsChangeLn', { bg = '#3f3a2a' }) 222 | shl('GitSignsDeleteLn', { bg = '#3f2a2a' }) 223 | 224 | -- Mode line 225 | shl('ModeLineMode', { fg = colors.bg, bg = colors.red, bold = true }) 226 | shl('ModeLineFileinfo', { fg = colors.fg, bold = true }) 227 | shl('ModeLineGit', { fg = colors.green }) 228 | shl('ModeLineDiagnostic', { fg = colors.warning }) 229 | end 230 | 231 | load_monokai() 232 | -------------------------------------------------------------------------------- /lua/private/dashboard.lua: -------------------------------------------------------------------------------- 1 | local group = vim.api.nvim_create_augroup('Dashboard', { clear = true }) 2 | 3 | local M = {} 4 | 5 | local config = { 6 | lambda_art = { 7 | '⠀⠀⠀⢀⣠⣴⣶⣤⣄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀', 8 | '⠀⠀⣴⣿⣿⣿⣿⣿⣿⣿⣆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀', 9 | '⠀⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣧⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀', 10 | '⠘⣿⣿⣿⣿⡟⠉⢿⣿⣿⣿⣿⣆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀', 11 | '⠀⠈⠛⠛⠋⠀⠀⠈⣿⣿⣿⣿⣿⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀', 12 | '⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⣿⣿⣿⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀', 13 | '⠀⠀⠀⠀⠀⠀⠀⠀⣸⣿⣿⣿⣿⣿⣧⠀⠀⠀⠀⠀⠀⠀⠀⠀', 14 | '⠀⠀⠀⠀⠀⠀⠀⢠⣿⣿⣿⣿⣿⣿⣿⡄⠀⠀⠀⠀⠀⠀⠀⠀', 15 | '⠀⠀⠀⠀⠀⠀⣰⣿⣿⣿⣿⣿⣿⣿⣿⣷⠀⠀⠀⠀⠀⠀⠀⠀', 16 | '⠀⠀⠀⠀⢀⣼⣿⣿⣿⣿⡿⢿⣿⣿⣿⣿⡆⠀⠀⠀⠀⠀⠀⠀', 17 | '⠀⠀⠀⢠⣾⣿⣿⣿⣿⡟⠁⠘⣿⣿⣿⣿⣷⠀⠀⠀⣀⡀⠀⠀', 18 | '⠀⠀⣠⣿⣿⣿⣿⣿⠏⠀⠀⠀⢻⣿⣿⣿⣿⡆⣰⣿⣿⣿⣷⡀', 19 | '⠀⣴⣿⣿⣿⣿⣿⠋⠀⠀⠀⠀⠘⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠁', 20 | '⠰⣿⣿⣿⣿⡿⠁⠀⠀⠀⠀⠀⠀⠘⢿⣿⣿⣿⣿⣿⣿⡟⠁⠀', 21 | '⠀⠙⠻⠿⠛⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠛⠿⠟⠛⠁⠀⠀⠀', 22 | }, 23 | 24 | shortcuts = { 25 | { key = 'f', desc = 'Open File', action = 'FzfLua files' }, 26 | { key = 'o', desc = 'Recent Files', action = 'FzfLua oldfiles' }, 27 | { key = 'd', desc = 'Dotfiles', action = 'FzfLua files cwd=$HOME/.config' }, 28 | { key = 'e', desc = 'New File', action = 'enew' }, 29 | { 30 | key = 'u', 31 | desc = 'Update Plugins', 32 | action = 'lua vim.pack.update(nil, { force = true})', 33 | }, 34 | { key = 'q', desc = 'Quit', action = 'qa' }, 35 | }, 36 | 37 | highlights = { 38 | lambda = 'DashboardLambda', 39 | key = 'DashboardKey', 40 | desc = 'DashboardDesc', 41 | date = 'DashboardDate', 42 | footer = 'DashboardFooter', 43 | }, 44 | 45 | layout = { 46 | top_offset = 8, 47 | date_top_offset = 3, 48 | plugin_info_offset = 5, 49 | shortcuts_top_offset = 3, 50 | }, 51 | } 52 | 53 | local function calculate_positions() 54 | local screen_width = vim.o.columns 55 | 56 | local lambda_display_width = 0 57 | for _, line in ipairs(config.lambda_art) do 58 | lambda_display_width = math.max(lambda_display_width, vim.fn.strdisplaywidth(line)) 59 | end 60 | 61 | local max_right_display_width = 0 62 | 63 | local sample_plugin = 'load 999/999 plugins in 9999.999ms' 64 | max_right_display_width = math.max(max_right_display_width, vim.fn.strdisplaywidth(sample_plugin)) 65 | 66 | local gap = 2 67 | 68 | local total_display_width = lambda_display_width + gap + max_right_display_width 69 | 70 | local start_pos = math.max(1, math.floor((screen_width - total_display_width) / 2)) 71 | 72 | return { 73 | lambda_left_margin = start_pos, 74 | right_section_left = start_pos + lambda_display_width + gap, 75 | } 76 | end 77 | 78 | local function setup_highlights() 79 | local highlights = { 80 | DashboardLambda = { fg = '#7aa2f7', bold = true }, 81 | DashboardKey = { fg = '#f7768e', bold = true }, 82 | DashboardDesc = { fg = '#9ece6a' }, 83 | DashboardDate = { fg = '#e0af68', bold = true }, 84 | DashboardFooter = { fg = '#565f89', italic = true }, 85 | } 86 | 87 | for g, opts in pairs(highlights) do 88 | vim.api.nvim_set_hl(0, g, opts) 89 | end 90 | end 91 | 92 | local function get_datetime() 93 | local datetime = os.date('*t') 94 | local weekdays = { 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday' } 95 | local months = 96 | { 'jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec' } 97 | 98 | local weekday = weekdays[datetime.wday] 99 | local year = datetime.year 100 | local month = months[datetime.month] 101 | local day = datetime.day 102 | local hour = string.format('%02d', datetime.hour) 103 | local min = string.format('%02d', datetime.min) 104 | 105 | return string.format('%s %d %s %d %s:%s', weekday, year, month, day, hour, min) 106 | end 107 | 108 | local function create_dashboard_buffer() 109 | local buf = vim.api.nvim_get_current_buf() 110 | vim.bo[buf].bufhidden = 'wipe' 111 | vim.bo[buf].buftype = 'nofile' 112 | vim.bo[buf].buflisted = false 113 | vim.bo[buf].modifiable = false 114 | return buf 115 | end 116 | 117 | local function render_dashboard(buf) 118 | local lines = {} 119 | local highlights_to_apply = {} 120 | 121 | local pos = calculate_positions() 122 | 123 | for _ = 1, config.layout.top_offset do 124 | table.insert(lines, '') 125 | end 126 | 127 | local lambda_lines = #config.lambda_art 128 | local date_line_idx = config.layout.top_offset + config.layout.date_top_offset 129 | local plugin_info_line_idx = config.layout.top_offset + config.layout.plugin_info_offset 130 | local shortcuts_start_idx = plugin_info_line_idx + config.layout.shortcuts_top_offset 131 | 132 | local total_lines = math.max( 133 | config.layout.top_offset + lambda_lines, 134 | shortcuts_start_idx + #config.shortcuts, 135 | plugin_info_line_idx + 1 136 | ) 137 | 138 | for _ = #lines + 1, total_lines do 139 | table.insert(lines, '') 140 | end 141 | 142 | for i, lambda_line in ipairs(config.lambda_art) do 143 | local line_idx = config.layout.top_offset + i 144 | if line_idx <= #lines then 145 | local new_line = string.rep(' ', pos.lambda_left_margin - 1) .. lambda_line 146 | lines[line_idx] = new_line 147 | 148 | local lambda_byte_start = pos.lambda_left_margin - 1 149 | local lambda_byte_end = lambda_byte_start + #lambda_line 150 | 151 | table.insert(highlights_to_apply, { 152 | line = line_idx - 1, 153 | col_start = lambda_byte_start, 154 | col_end = lambda_byte_end, 155 | hl_group = config.highlights.lambda, 156 | }) 157 | end 158 | end 159 | 160 | local datetime_str = get_datetime() 161 | if date_line_idx <= #lines then 162 | local current_line = lines[date_line_idx] or '' 163 | local needed_spaces = math.max(0, pos.right_section_left - 1 - #current_line) 164 | local new_line = current_line .. string.rep(' ', needed_spaces) .. datetime_str 165 | lines[date_line_idx] = new_line 166 | 167 | local date_byte_start = #current_line + needed_spaces 168 | local date_byte_end = date_byte_start + #datetime_str 169 | 170 | table.insert(highlights_to_apply, { 171 | line = date_line_idx - 1, 172 | col_start = date_byte_start, 173 | col_end = date_byte_end, 174 | hl_group = config.highlights.date, 175 | }) 176 | end 177 | 178 | local plugins = vim.pack.get() 179 | local loaded = vim 180 | .iter(plugins) 181 | :map(function(p) 182 | return p.active 183 | end) 184 | :totable() 185 | local startup_time = vim.g.nvim_startup_time or '0' 186 | local plugin_info_str = 187 | string.format('load %d/%d plugins in %sms', #loaded or 0, #plugins or 0, startup_time) 188 | 189 | if plugin_info_line_idx <= #lines then 190 | local current_line = lines[plugin_info_line_idx] or '' 191 | local needed_spaces = math.max(0, pos.right_section_left - 1 - #current_line) 192 | local new_line = current_line .. string.rep(' ', needed_spaces) .. plugin_info_str 193 | lines[plugin_info_line_idx] = new_line 194 | 195 | local plugin_byte_start = #current_line + needed_spaces 196 | local plugin_byte_end = plugin_byte_start + #plugin_info_str 197 | 198 | table.insert(highlights_to_apply, { 199 | line = plugin_info_line_idx - 1, 200 | col_start = plugin_byte_start, 201 | col_end = plugin_byte_end, 202 | hl_group = config.highlights.footer, 203 | }) 204 | end 205 | 206 | local cursor = {} 207 | 208 | local shortcuts = config.shortcuts 209 | for i, shortcut in ipairs(shortcuts) do 210 | local row_idx = shortcuts_start_idx + i - 1 211 | if row_idx <= #lines then 212 | local shortcut_text = string.format('[%s] %s', shortcut.key, shortcut.desc) 213 | 214 | local current_line = lines[row_idx] or '' 215 | local needed_spaces = math.max(2, pos.right_section_left - 1 - #current_line) 216 | local new_line = current_line .. string.rep(' ', needed_spaces) .. shortcut_text 217 | if i == 1 then 218 | cursor[1] = row_idx 219 | cursor[2] = #new_line - #shortcut_text + 5 220 | end 221 | lines[row_idx] = new_line 222 | 223 | local shortcut_byte_start = #current_line + needed_spaces 224 | 225 | table.insert(highlights_to_apply, { 226 | line = row_idx - 1, 227 | col_start = shortcut_byte_start + 1, 228 | col_end = shortcut_byte_start + 2, 229 | hl_group = config.highlights.key, 230 | }) 231 | 232 | table.insert(highlights_to_apply, { 233 | line = row_idx - 1, 234 | col_start = shortcut_byte_start + 3, 235 | col_end = shortcut_byte_start + #shortcut_text, 236 | hl_group = config.highlights.desc, 237 | }) 238 | end 239 | end 240 | 241 | vim.bo[buf].modifiable = true 242 | vim.api.nvim_buf_set_lines(buf, 0, -1, false, lines) 243 | vim.bo[buf].modifiable = false 244 | vim.api.nvim_win_set_cursor(0, cursor) 245 | 246 | local ns_id = vim.api.nvim_create_namespace('dashboard') 247 | for _, hl in ipairs(highlights_to_apply) do 248 | vim.hl.range(buf, ns_id, hl.hl_group, { hl.line, hl.col_start }, { hl.line, hl.col_end }) 249 | end 250 | end 251 | 252 | local function setup_keymaps(buf) 253 | local opts = { noremap = true, silent = true, buffer = buf } 254 | 255 | for _, shortcut in ipairs(config.shortcuts) do 256 | vim.keymap.set('n', shortcut.key, shortcut.action, opts) 257 | end 258 | 259 | vim.keymap.set('n', '', ':q', opts) 260 | vim.keymap.set('n', 'q', ':q', opts) 261 | end 262 | 263 | local function opt_handler() 264 | local save_opts = {} 265 | 266 | save_opts.number = vim.wo.number 267 | save_opts.relativenumber = vim.wo.relativenumber 268 | save_opts.cursorline = vim.wo.cursorline 269 | save_opts.cursorcolumn = vim.wo.cursorcolumn 270 | save_opts.colorcolumn = vim.wo.colorcolumn 271 | save_opts.signcolumn = vim.wo.signcolumn 272 | save_opts.wrap = vim.wo.wrap 273 | save_opts.laststatus = 2 274 | save_opts.showtabline = vim.o.showtabline 275 | save_opts.listchars = vim.o.listchars 276 | 277 | return function() 278 | vim.wo.number = save_opts.number 279 | vim.wo.relativenumber = save_opts.relativenumber 280 | vim.wo.cursorline = save_opts.cursorline 281 | vim.wo.cursorcolumn = save_opts.cursorcolumn 282 | vim.wo.colorcolumn = save_opts.colorcolumn 283 | vim.wo.signcolumn = save_opts.signcolumn 284 | vim.wo.wrap = save_opts.wrap 285 | vim.o.laststatus = save_opts.laststatus 286 | vim.o.showtabline = save_opts.showtabline 287 | vim.o.listchars = save_opts.listchars 288 | end 289 | end 290 | 291 | function M.show() 292 | if vim.fn.argc() > 0 or vim.fn.line2byte('$') ~= -1 then 293 | vim.o.laststatus = 2 294 | return 295 | end 296 | 297 | local buf = create_dashboard_buffer() 298 | vim.api.nvim_set_current_buf(buf) 299 | render_dashboard(buf) 300 | setup_highlights() 301 | setup_keymaps(buf) 302 | 303 | local restore_opt = opt_handler() 304 | 305 | vim.wo.number = false 306 | vim.wo.relativenumber = false 307 | vim.wo.cursorline = false 308 | vim.wo.cursorcolumn = false 309 | vim.wo.colorcolumn = '0' 310 | vim.wo.signcolumn = 'no' 311 | vim.wo.wrap = false 312 | vim.wo.listchars = 'precedes: ' 313 | 314 | vim.o.laststatus = 0 315 | vim.o.showtabline = 0 316 | 317 | vim.api.nvim_create_autocmd('VimResized', { 318 | buffer = buf, 319 | group = group, 320 | callback = function() 321 | if vim.bo.buftype == 'nofile' and vim.bo.filetype == '' then 322 | render_dashboard(buf) 323 | end 324 | end, 325 | }) 326 | 327 | vim.api.nvim_create_autocmd('BufLeave', { 328 | buffer = buf, 329 | group = group, 330 | callback = function() 331 | restore_opt() 332 | end, 333 | }) 334 | end 335 | 336 | vim.api.nvim_create_autocmd('VimEnter', { 337 | group = group, 338 | callback = function() 339 | if vim.fn.argc() == 0 and vim.fn.line2byte('$') == -1 then 340 | M.show() 341 | end 342 | vim.o.laststatus = 2 343 | end, 344 | }) 345 | 346 | vim.api.nvim_create_user_command('Dashboard', function() 347 | M.show() 348 | end, {}) 349 | 350 | return M 351 | -------------------------------------------------------------------------------- /snippets/c.json: -------------------------------------------------------------------------------- 1 | { 2 | "for_i": { 3 | "prefix": "fori", 4 | "body": [ 5 | "for (int ${1:i} = 0; ${1:i} < ${2:n}; ${1:i}++) {", 6 | "\t$0", 7 | "}" 8 | ], 9 | "description": "Integer for loop (C99)" 10 | }, 11 | "for_size_t": { 12 | "prefix": "forz", 13 | "body": [ 14 | "for (size_t ${1:i} = 0; ${1:i} < ${2:n}; ${1:i}++) {", 15 | "\t$0", 16 | "}" 17 | ], 18 | "description": "Size_t for loop" 19 | }, 20 | "while": { 21 | "prefix": "while", 22 | "body": [ 23 | "while (${1:condition}) {", 24 | "\t$0", 25 | "}" 26 | ], 27 | "description": "While loop" 28 | }, 29 | "do_while": { 30 | "prefix": "do", 31 | "body": [ 32 | "do {", 33 | "\t$0", 34 | "} while (${1:condition});" 35 | ], 36 | "description": "Do-while loop" 37 | }, 38 | "if": { 39 | "prefix": "if", 40 | "body": [ 41 | "if (${1:condition}) {", 42 | "\t$0", 43 | "}" 44 | ], 45 | "description": "If statement" 46 | }, 47 | "if_else": { 48 | "prefix": "ife", 49 | "body": [ 50 | "if (${1:condition}) {", 51 | "\t$2", 52 | "} else {", 53 | "\t$0", 54 | "}" 55 | ], 56 | "description": "If-else statement" 57 | }, 58 | "elif": { 59 | "prefix": "elif", 60 | "body": [ 61 | "else if (${1:condition}) {", 62 | "\t$0", 63 | "}" 64 | ], 65 | "description": "Else-if statement" 66 | }, 67 | "switch": { 68 | "prefix": "switch", 69 | "body": [ 70 | "switch (${1:expr}) {", 71 | "\tcase ${2:value}:", 72 | "\t\t$0", 73 | "\t\tbreak;", 74 | "\tdefault:", 75 | "\t\tbreak;", 76 | "}" 77 | ], 78 | "description": "Switch statement" 79 | }, 80 | "ternary": { 81 | "prefix": "?", 82 | "body": "${1:condition} ? ${2:true_val} : ${3:false_val}", 83 | "description": "Ternary operator" 84 | }, 85 | "struct": { 86 | "prefix": "struct", 87 | "body": [ 88 | "typedef struct {", 89 | "\t$0", 90 | "} ${1:Name};" 91 | ], 92 | "description": "Struct with typedef" 93 | }, 94 | "struct_named": { 95 | "prefix": "structs", 96 | "body": [ 97 | "struct ${1:name} {", 98 | "\t$0", 99 | "};" 100 | ], 101 | "description": "Named struct" 102 | }, 103 | "enum": { 104 | "prefix": "enum", 105 | "body": [ 106 | "typedef enum {", 107 | "\t$0", 108 | "} ${1:Name};" 109 | ], 110 | "description": "Enum with typedef" 111 | }, 112 | "union": { 113 | "prefix": "union", 114 | "body": [ 115 | "typedef union {", 116 | "\t$0", 117 | "} ${1:Name};" 118 | ], 119 | "description": "Union with typedef" 120 | }, 121 | "function": { 122 | "prefix": "fn", 123 | "body": [ 124 | "${1:void} ${2:name}(${3:void}) {", 125 | "\t$0", 126 | "}" 127 | ], 128 | "description": "Function definition" 129 | }, 130 | "static_function": { 131 | "prefix": "sfn", 132 | "body": [ 133 | "static ${1:void} ${2:name}(${3:void}) {", 134 | "\t$0", 135 | "}" 136 | ], 137 | "description": "Static function" 138 | }, 139 | "inline_function": { 140 | "prefix": "ifn", 141 | "body": [ 142 | "static inline ${1:void} ${2:name}(${3:void}) {", 143 | "\t$0", 144 | "}" 145 | ], 146 | "description": "Inline function (C99)" 147 | }, 148 | "main": { 149 | "prefix": "main", 150 | "body": [ 151 | "int main(int argc, char *argv[]) {", 152 | "\t$0", 153 | "\treturn 0;", 154 | "}" 155 | ], 156 | "description": "Main function" 157 | }, 158 | "main_simple": { 159 | "prefix": "mains", 160 | "body": [ 161 | "int main(void) {", 162 | "\t$0", 163 | "\treturn 0;", 164 | "}" 165 | ], 166 | "description": "Simple main function" 167 | }, 168 | "malloc": { 169 | "prefix": "malloc", 170 | "body": [ 171 | "${1:Type} *${2:ptr} = malloc(${3:n} * sizeof(${1:Type}));", 172 | "if (${2:ptr} == NULL) {", 173 | "\t${0:// handle error}", 174 | "}" 175 | ], 176 | "description": "Malloc with error check" 177 | }, 178 | "calloc": { 179 | "prefix": "calloc", 180 | "body": [ 181 | "${1:Type} *${2:ptr} = calloc(${3:n}, sizeof(${1:Type}));", 182 | "if (${2:ptr} == NULL) {", 183 | "\t${0:// handle error}", 184 | "}" 185 | ], 186 | "description": "Calloc with error check" 187 | }, 188 | "realloc": { 189 | "prefix": "realloc", 190 | "body": [ 191 | "${1:Type} *${2:tmp} = realloc(${3:ptr}, ${4:new_size} * sizeof(${1:Type}));", 192 | "if (${2:tmp} == NULL) {", 193 | "\t${0:// handle error}", 194 | "} else {", 195 | "\t${3:ptr} = ${2:tmp};", 196 | "}" 197 | ], 198 | "description": "Realloc with error check" 199 | }, 200 | "free": { 201 | "prefix": "free", 202 | "body": [ 203 | "free(${1:ptr});", 204 | "${1:ptr} = NULL;$0" 205 | ], 206 | "description": "Free and nullify pointer" 207 | }, 208 | "null_check": { 209 | "prefix": "ifnull", 210 | "body": [ 211 | "if (${1:ptr} == NULL) {", 212 | "\t$0", 213 | "}" 214 | ], 215 | "description": "Null pointer check" 216 | }, 217 | "not_null_check": { 218 | "prefix": "ifnn", 219 | "body": [ 220 | "if (${1:ptr} != NULL) {", 221 | "\t$0", 222 | "}" 223 | ], 224 | "description": "Not null pointer check" 225 | }, 226 | "strlen": { 227 | "prefix": "strlen", 228 | "body": "size_t ${1:len} = strlen(${2:str});$0", 229 | "description": "String length" 230 | }, 231 | "strcpy": { 232 | "prefix": "strcpy", 233 | "body": "strcpy(${1:dest}, ${2:src});$0", 234 | "description": "String copy" 235 | }, 236 | "strncpy": { 237 | "prefix": "strncpy", 238 | "body": [ 239 | "strncpy(${1:dest}, ${2:src}, ${3:n});", 240 | "${1:dest}[${3:n} - 1] = '\\0';$0" 241 | ], 242 | "description": "Safe string copy" 243 | }, 244 | "strcmp": { 245 | "prefix": "strcmp", 246 | "body": "strcmp(${1:s1}, ${2:s2}) == 0", 247 | "description": "String comparison" 248 | }, 249 | "strdup": { 250 | "prefix": "strdup", 251 | "body": [ 252 | "char *${1:copy} = strdup(${2:str});", 253 | "if (${1:copy} == NULL) {", 254 | "\t${0:// handle error}", 255 | "}" 256 | ], 257 | "description": "String duplicate" 258 | }, 259 | "fopen": { 260 | "prefix": "fopen", 261 | "body": [ 262 | "FILE *${1:fp} = fopen(\"${2:filename}\", \"${3:r}\");", 263 | "if (${1:fp} == NULL) {", 264 | "\t${0:// handle error}", 265 | "}" 266 | ], 267 | "description": "File open with error check" 268 | }, 269 | "fclose": { 270 | "prefix": "fclose", 271 | "body": [ 272 | "if (${1:fp} != NULL) {", 273 | "\tfclose(${1:fp});", 274 | "\t${1:fp} = NULL;", 275 | "}$0" 276 | ], 277 | "description": "Safe file close" 278 | }, 279 | "fprintf": { 280 | "prefix": "fprintf", 281 | "body": "fprintf(${1:fp}, \"${2:%s}\\n\", ${3:arg});$0", 282 | "description": "Formatted file write" 283 | }, 284 | "fscanf": { 285 | "prefix": "fscanf", 286 | "body": "fscanf(${1:fp}, \"${2:%d}\", &${3:var});$0", 287 | "description": "Formatted file read" 288 | }, 289 | "fread": { 290 | "prefix": "fread", 291 | "body": "fread(${1:ptr}, sizeof(${2:Type}), ${3:count}, ${4:fp});$0", 292 | "description": "Binary file read" 293 | }, 294 | "fwrite": { 295 | "prefix": "fwrite", 296 | "body": "fwrite(${1:ptr}, sizeof(${2:Type}), ${3:count}, ${4:fp});$0", 297 | "description": "Binary file write" 298 | }, 299 | "printf": { 300 | "prefix": "printf", 301 | "body": "printf(\"${1:%d}\\n\", ${2:arg});$0", 302 | "description": "Printf" 303 | }, 304 | "scanf": { 305 | "prefix": "scanf", 306 | "body": "scanf(\"${1:%d}\", &${2:var});$0", 307 | "description": "Scanf" 308 | }, 309 | "puts": { 310 | "prefix": "puts", 311 | "body": "puts(${1:str});$0", 312 | "description": "Puts" 313 | }, 314 | "getchar": { 315 | "prefix": "getchar", 316 | "body": "int ${1:ch} = getchar();$0", 317 | "description": "Get character" 318 | }, 319 | "include": { 320 | "prefix": "#inc", 321 | "body": "#include <${1:stdio.h}>", 322 | "description": "Include header" 323 | }, 324 | "include_local": { 325 | "prefix": "#incl", 326 | "body": "#include \"${1:header.h}\"", 327 | "description": "Include local header" 328 | }, 329 | "define": { 330 | "prefix": "#def", 331 | "body": "#define ${1:MACRO} ${2:value}", 332 | "description": "Define macro" 333 | }, 334 | "define_function": { 335 | "prefix": "#deff", 336 | "body": "#define ${1:MACRO}(${2:x}) ${3:(x)}", 337 | "description": "Define function macro" 338 | }, 339 | "ifndef": { 340 | "prefix": "#guard", 341 | "body": [ 342 | "#ifndef ${1:HEADER}_H", 343 | "#define ${1:HEADER}_H", 344 | "", 345 | "$0", 346 | "", 347 | "#endif /* ${1:HEADER}_H */" 348 | ], 349 | "description": "Header guard" 350 | }, 351 | "ifdef": { 352 | "prefix": "#ifdef", 353 | "body": [ 354 | "#ifdef ${1:MACRO}", 355 | "$0", 356 | "#endif /* ${1:MACRO} */" 357 | ], 358 | "description": "Ifdef block" 359 | }, 360 | "ifndef_block": { 361 | "prefix": "#ifndef", 362 | "body": [ 363 | "#ifndef ${1:MACRO}", 364 | "$0", 365 | "#endif /* !${1:MACRO} */" 366 | ], 367 | "description": "Ifndef block" 368 | }, 369 | "perror": { 370 | "prefix": "perror", 371 | "body": "perror(\"${1:error}\");$0", 372 | "description": "Print error" 373 | }, 374 | "errno": { 375 | "prefix": "errno", 376 | "body": [ 377 | "if (${1:result} == -1) {", 378 | "\tperror(\"${2:operation}\");", 379 | "\treturn ${3:EXIT_FAILURE};", 380 | "}$0" 381 | ], 382 | "description": "Errno check" 383 | }, 384 | "assert": { 385 | "prefix": "assert", 386 | "body": "assert(${1:condition});$0", 387 | "description": "Assert" 388 | }, 389 | "bool": { 390 | "prefix": "bool", 391 | "body": [ 392 | "#include ", 393 | "", 394 | "bool ${1:var} = ${2:true};$0" 395 | ], 396 | "description": "Boolean type (C99)" 397 | }, 398 | "restrict": { 399 | "prefix": "restrict", 400 | "body": "${1:Type} *restrict ${2:ptr}$0", 401 | "description": "Restrict pointer (C99)" 402 | }, 403 | "inline": { 404 | "prefix": "inline", 405 | "body": "inline ${1:Type} ${2:name}(${3:}) { $0 }", 406 | "description": "Inline function (C99)" 407 | }, 408 | "designated_init": { 409 | "prefix": ".=", 410 | "body": ".${1:field} = ${2:value}", 411 | "description": "Designated initializer (C99)" 412 | }, 413 | "compound_literal": { 414 | "prefix": "(type)", 415 | "body": "(${1:Type}){ ${2:values} }", 416 | "description": "Compound literal (C99)" 417 | }, 418 | "vla": { 419 | "prefix": "vla", 420 | "body": "${1:Type} ${2:array}[${3:n}];$0", 421 | "description": "Variable length array (C99)" 422 | }, 423 | "array_loop": { 424 | "prefix": "fora", 425 | "body": [ 426 | "for (size_t ${1:i} = 0; ${1:i} < ${2:arr}_len; ${1:i}++) {", 427 | "\t${3:// process} ${2:arr}[${1:i}]$0", 428 | "}" 429 | ], 430 | "description": "Array iteration" 431 | }, 432 | "linked_list_traverse": { 433 | "prefix": "forll", 434 | "body": [ 435 | "for (${1:Node} *${2:curr} = ${3:head}; ${2:curr} != NULL; ${2:curr} = ${2:curr}->next) {", 436 | "\t$0", 437 | "}" 438 | ], 439 | "description": "Linked list traversal" 440 | }, 441 | "swap": { 442 | "prefix": "swap", 443 | "body": [ 444 | "${1:Type} ${2:tmp} = ${3:a};", 445 | "${3:a} = ${4:b};", 446 | "${4:b} = ${2:tmp};$0" 447 | ], 448 | "description": "Swap values" 449 | }, 450 | "min_max": { 451 | "prefix": "min", 452 | "body": "#define MIN(a, b) ((a) < (b) ? (a) : (b))", 453 | "description": "Min macro" 454 | } 455 | } 456 | -------------------------------------------------------------------------------- /lua/private/keymap.lua: -------------------------------------------------------------------------------- 1 | local api = vim.api 2 | 3 | -- Create a smart keymap wrapper using metatables 4 | local keymap = {} 5 | 6 | -- Valid vim modes 7 | local valid_modes = 8 | { n = true, i = true, v = true, x = true, s = true, o = true, c = true, t = true } 9 | 10 | -- Store mode combinations we've created 11 | local mode_cache = {} 12 | 13 | -- Function that performs the actual mapping 14 | local function perform_mapping(modes, lhs, rhs, opts) 15 | opts = opts or {} 16 | local mapset = vim.keymap.set 17 | 18 | if type(lhs) == 'table' then 19 | -- Handle table of mappings 20 | for key, action in pairs(lhs) do 21 | mapset(modes, key, action, opts) 22 | end 23 | else 24 | -- Handle single mapping 25 | mapset(modes, lhs, rhs, opts) 26 | end 27 | 28 | return keymap -- Return keymap for chaining 29 | end 30 | 31 | -- Parse a mode string into an array of mode characters 32 | local function parse_modes(mode_str) 33 | local modes = {} 34 | for i = 1, #mode_str do 35 | local char = mode_str:sub(i, i) 36 | if valid_modes[char] then 37 | table.insert(modes, char) 38 | end 39 | end 40 | return modes 41 | end 42 | 43 | -- Create the metatable that powers the dynamic mode access 44 | local mt = { 45 | __index = function(_, key) 46 | -- If this mode combination is already cached, return it 47 | if mode_cache[key] then 48 | return mode_cache[key] 49 | end 50 | 51 | -- Check if this is a valid mode string 52 | local modes = parse_modes(key) 53 | if #modes > 0 then 54 | -- Create and cache a function for this mode combination 55 | local mode_fn = function(lhs, rhs, opts) 56 | return perform_mapping(modes, lhs, rhs, opts) 57 | end 58 | 59 | mode_cache[key] = mode_fn 60 | return mode_fn 61 | end 62 | 63 | return nil -- Not a valid mode key 64 | end, 65 | 66 | -- Make the keymap table directly callable 67 | __call = function(_, modes, lhs, rhs, opts) 68 | return perform_mapping(type(modes) == 'string' and parse_modes(modes) or modes, lhs, rhs, opts) 69 | end, 70 | } 71 | 72 | local map = setmetatable(keymap, mt) 73 | 74 | -- Helper function for command mappings 75 | local cmd = function(command) 76 | return ('%s'):format(command) 77 | end 78 | 79 | map.n({ 80 | ['j'] = 'gj', 81 | ['k'] = 'gk', 82 | [''] = cmd('write'), 83 | -- ['k'] = cmd(vim.bo.buftype == 'terminal' and 'q!' or 'BufKeepDelete'), 84 | [''] = cmd('bn'), 85 | [''] = cmd('bp'), 86 | [''] = cmd('qa!'), 87 | --window 88 | [''] = 'h', 89 | [''] = 'l', 90 | [''] = 'j', 91 | [''] = 'k', 92 | [''] = cmd('vertical resize -5'), 93 | [''] = cmd('vertical resize +5'), 94 | ['[t'] = cmd('vs | vertical resize -5 | terminal'), 95 | [']t'] = cmd('set splitbelow | sp | set nosplitbelow | resize -5 | terminal'), 96 | ['t'] = cmd('tabnew | terminal'), 97 | ['gV'] = '`[v`]', 98 | ['c'] = cmd('Compile'), 99 | ['r'] = cmd('Recompile'), 100 | ['['] = cmd('vertical wincmd ]'), 101 | }) 102 | 103 | map.i({ 104 | [''] = 'diw', 105 | [''] = '', 106 | [''] = '', 107 | [''] = '^i', 108 | [''] = '', 109 | [''] = '', 110 | --down/up 111 | [''] = 'o', 112 | [''] = 'O', 113 | --@see https://github.com/neovim/neovim/issues/16416 114 | [''] = '', 115 | --@see https://vim.fandom.com/wiki/Moving_lines_up_or_down 116 | [''] = ':m .+1==gi', 117 | }) 118 | 119 | map.i('', function() 120 | local pos = vim.api.nvim_win_get_cursor(0) 121 | local row = pos[1] 122 | local col = pos[2] 123 | local line = vim.api.nvim_get_current_line() 124 | local total_lines = vim.api.nvim_buf_line_count(0) 125 | local trimmed_line = line:gsub('%s+$', '') 126 | local killed_text = '' 127 | 128 | if col == 0 then 129 | if trimmed_line == '' then 130 | if row < total_lines then 131 | killed_text = '\n' 132 | local next_line = api.nvim_buf_get_lines(0, row, row + 1, false)[1] or '' 133 | api.nvim_buf_set_lines(0, row - 1, row + 1, false, { next_line }) 134 | end 135 | else 136 | killed_text = line 137 | api.nvim_set_current_line('') 138 | end 139 | else 140 | if col < #trimmed_line then 141 | killed_text = line:sub(col + 1) 142 | api.nvim_set_current_line(line:sub(1, col)) 143 | else 144 | if row < total_lines then 145 | killed_text = '\n' 146 | local next_line = api.nvim_buf_get_lines(0, row, row + 1, false)[1] or '' 147 | api.nvim_buf_set_lines(0, row - 1, row + 1, false, { line .. next_line }) 148 | end 149 | end 150 | end 151 | 152 | if killed_text ~= '' then 153 | vim.fn.setreg('+', killed_text, 'v') 154 | end 155 | end) 156 | 157 | map.c({ 158 | [''] = '', 159 | [''] = '', 160 | [''] = '', 161 | [''] = '', 162 | [''] = '', 163 | [''] = '', 164 | }) 165 | 166 | map.t({ 167 | [''] = [[]], 168 | ['k'] = cmd('quit'), 169 | }) 170 | 171 | -- insert cut text to paste 172 | map.i('', function() 173 | local mark = api.nvim_buf_get_mark(0, 'a') 174 | local lnum, col = unpack(api.nvim_win_get_cursor(0)) 175 | if mark[1] == 0 then 176 | api.nvim_buf_set_mark(0, 'a', lnum, col, {}) 177 | else 178 | local keys = 'd`aa' 179 | api.nvim_feedkeys(api.nvim_replace_termcodes(keys, true, true, true), 'n', false) 180 | vim.schedule(function() 181 | api.nvim_buf_del_mark(0, 'a') 182 | end) 183 | end 184 | end) 185 | 186 | -- Ctrl-y works like emacs 187 | map.i('', function() 188 | if tonumber(vim.fn.pumvisible()) == 1 or vim.fn.getreg('"+'):find('%w') == nil then 189 | return '' 190 | end 191 | return 'p==a' 192 | end, { expr = true }) 193 | 194 | -- move line down 195 | map.i('', function() 196 | local lnum = api.nvim_win_get_cursor(0)[1] 197 | local line = api.nvim_buf_get_lines(0, lnum - 3, lnum - 2, false)[1] 198 | return #line > 0 and ':m .-2==gi' or 'kkddj:m .-2==gi' 199 | end, { expr = true }) 200 | 201 | map.i('', function() 202 | if tonumber(vim.fn.pumvisible()) == 1 then 203 | return '' 204 | elseif vim.snippet.active({ direction = 1 }) then 205 | return 'lua vim.snippet.jump(1)' 206 | else 207 | return '' 208 | end 209 | end, { expr = true }) 210 | 211 | map.i('', function() 212 | if vim.fn.pumvisible() == 1 then 213 | return '' 214 | elseif vim.snippet.active({ direction = -1 }) then 215 | return 'lua vim.snippet.jump(-1)' 216 | else 217 | return '' 218 | end 219 | end, { expr = true }) 220 | 221 | map.i('', function() 222 | if tonumber(vim.fn.pumvisible()) == 1 then 223 | return '' 224 | end 225 | local line = api.nvim_get_current_line() 226 | local col = api.nvim_win_get_cursor(0)[2] 227 | local before = line:sub(col, col) 228 | local after = line:sub(col + 1, col + 1) 229 | local t = { 230 | ['('] = ')', 231 | ['['] = ']', 232 | ['{'] = '}', 233 | } 234 | if t[before] and t[before] == after then 235 | return 'O' 236 | end 237 | return '' 238 | end, { expr = true }) 239 | 240 | map.i('', function() 241 | return vim.fn.pumvisible() == 1 and '' or '' 242 | end, { expr = true }) 243 | 244 | local ns_id, mark_id = vim.api.nvim_create_namespace('my_marks'), nil 245 | 246 | map.i('', function() 247 | if not mark_id then 248 | local row, col = unpack(api.nvim_win_get_cursor(0)) 249 | mark_id = api.nvim_buf_set_extmark(0, ns_id, row - 1, col, { 250 | virt_text = { { '⚑', 'DiagnosticError' } }, 251 | hl_group = 'Search', 252 | virt_text_pos = 'inline', 253 | }) 254 | return 255 | end 256 | local mark = api.nvim_buf_get_extmark_by_id(0, ns_id, mark_id, {}) 257 | if not mark or #mark == 0 then 258 | return 259 | end 260 | pcall(api.nvim_win_set_cursor, 0, { mark[1] + 1, mark[2] }) 261 | api.nvim_buf_del_extmark(0, ns_id, mark_id) 262 | mark_id = nil 263 | end) 264 | 265 | -- gX: Web search 266 | map.n('gX', function() 267 | vim.ui.open(('https://google.com/search?q=%s'):format(vim.fn.expand(''))) 268 | end) 269 | 270 | map.x('gX', function() 271 | local lines = vim.fn.getregion(vim.fn.getpos('.'), vim.fn.getpos('v'), { type = vim.fn.mode() }) 272 | vim.ui.open(('https://google.com/search?q=%s'):format(vim.trim(table.concat(lines, ' ')))) 273 | api.nvim_input('') 274 | end) 275 | 276 | map.n('gs', function() 277 | local bufnr = api.nvim_create_buf(false, false) 278 | vim.bo[bufnr].buftype = 'prompt' 279 | vim.fn.prompt_setprompt(bufnr, ' ') 280 | api.nvim_buf_set_extmark(bufnr, api.nvim_create_namespace('WebSearch'), 0, 0, { 281 | line_hl_group = 'String', 282 | }) 283 | local width = math.floor(vim.o.columns * 0.5) 284 | local winid = api.nvim_open_win(bufnr, true, { 285 | relative = 'editor', 286 | row = 5, 287 | width = width, 288 | height = 5, 289 | col = math.floor(vim.o.columns / 2) - math.floor(width / 2), 290 | border = 'rounded', 291 | title = 'Google Search', 292 | title_pos = 'center', 293 | }) 294 | vim.cmd.startinsert() 295 | vim.wo[winid].number = false 296 | vim.wo[winid].stc = '' 297 | vim.wo[winid].lcs = 'trail: ' 298 | vim.wo[winid].wrap = true 299 | vim.wo[winid].signcolumn = 'no' 300 | vim.fn.prompt_setcallback(bufnr, function(text) 301 | vim.ui.open(('https://google.com/search?q=%s'):format(vim.trim(text))) 302 | api.nvim_win_close(winid, true) 303 | end) 304 | vim.keymap.set({ 'n', 'i' }, '', function() 305 | pcall(api.nvim_win_close, winid, true) 306 | end, { buffer = bufnr }) 307 | end) 308 | 309 | map.n({ 310 | -- Lspsaga 311 | -- ['[d'] = cmd('Lspsaga diagnostic_jump_next'), 312 | -- [']d'] = cmd('Lspsaga diagnostic_jump_prev'), 313 | -- ['ga'] = cmd('Lspsaga code_action'), 314 | -- ['gr'] = cmd('Lspsaga rename'), 315 | -- ['gd'] = cmd('Lspsaga peek_definition'), 316 | -- ['gp'] = cmd('Lspsaga goto_definition'), 317 | -- ['gh'] = cmd('Lspsaga finder'), 318 | -- ['gl'] = cmd('Visualizer full'), 319 | -- FzfLua 320 | ['d'] = cmd('FzfLua diagnostic_document'), 321 | ['D'] = cmd('FzfLua diagnostic_workspace'), 322 | ['b'] = cmd('FzfLua buffers'), 323 | ['fa'] = cmd('FzfLua live_grep_native'), 324 | ['fs'] = cmd('FzfLua grep_cword'), 325 | ['ff'] = cmd('FzfLua files'), 326 | ['fh'] = cmd('FzfLua helptags'), 327 | ['fo'] = cmd('FzfLua oldfiles'), 328 | ['fg'] = cmd('FzfLua git_files'), 329 | ['gc'] = cmd('FzfLua git_commits'), 330 | ['gb'] = cmd('FzfLua git_bcommits'), 331 | ['o'] = cmd('FzfLua lsp_document_symbols'), 332 | ['fc'] = cmd('FzfLua files cwd=$HOME/.config'), 333 | --gitsign 334 | [']g'] = cmd('lua require"gitsigns".next_hunk()'), 335 | ['[g'] = cmd('lua require"gitsigns".prev_hunk()'), 336 | }) 337 | 338 | map.n('', cmd('Dired')) 339 | 340 | map.nt('', function() 341 | require('private.term').toggle() 342 | end) 343 | -- map.nx('ga', cmd('Lspsaga code_action')) 344 | 345 | map.n('f', function() 346 | require('private.jump').charForward() 347 | end) 348 | 349 | map.n('F', function() 350 | require('private.jump').charBackward() 351 | end) 352 | 353 | vim.cmd([[ 354 | iabbrev ,d strftime('%Y-%m-%d') 355 | iabbrev ,t strftime('%Y-%m-%d %H:%M') 356 | ]]) 357 | 358 | map.xo('af', function() 359 | require('nvim-treesitter-textobjects.select').select_textobject('@function.outer', 'textobjects') 360 | end) 361 | map.xo('if', function() 362 | require('nvim-treesitter-textobjects.select').select_textobject('@function.inner', 'textobjects') 363 | end) 364 | map.xo('ac', function() 365 | require('nvim-treesitter-textobjects.select').select_textobject('@class.outer', 'textobjects') 366 | end) 367 | map.xo('ic', function() 368 | require('nvim-treesitter-textobjects.select').select_textobject('@class.inner', 'textobjects') 369 | end) 370 | map.xo('as', function() 371 | require('nvim-treesitter-textobjects.select').select_textobject('@local.scope', 'locals') 372 | end) 373 | -------------------------------------------------------------------------------- /lua/private/compile.lua: -------------------------------------------------------------------------------- 1 | local api = vim.api 2 | local last_cmd = nil 3 | local qf_id = nil 4 | local ansi_ns = nil 5 | local job = nil ---@type vim.SystemObj? 6 | 7 | local function parse_err(stderr, save_item) 8 | local list = {} 9 | local lines = vim.split(stderr, '\n', { trimempty = true }) 10 | 11 | local i = 1 12 | local prev_item = {} 13 | while i <= #lines do 14 | local line = lines[i] 15 | 16 | local filename, lnum, col, type_str, msg = line:match('^([^:]+):(%d+):(%d+):%s*(%w+):%s*(.*)$') 17 | local bufnr = vim.fn.bufadd(filename) 18 | 19 | if filename and lnum and col then 20 | prev_item = { 21 | filename = filename, 22 | lnum = tonumber(lnum), 23 | col = tonumber(col), 24 | type = type_str:sub(1, 1):upper(), 25 | text = msg, 26 | bufnr = bufnr, 27 | } 28 | table.insert(list, prev_item) 29 | save_item.lnum = prev_item.lnum 30 | save_item.col = prev_item.col 31 | save_item.bufnr = prev_item.bufnr 32 | 33 | local j = i + 1 34 | 35 | while j <= #lines do 36 | local next_line = lines[j] 37 | 38 | if 39 | next_line:match('^%s*%d+%s*|') 40 | or next_line:match('^%s*|') 41 | or next_line:match('^%s*%^') 42 | or next_line:match('generated') 43 | then 44 | table.insert(list, { 45 | filename = prev_item.filename, 46 | bufnr = prev_item.bufnr, 47 | text = next_line, 48 | }) 49 | j = j + 1 50 | else 51 | break 52 | end 53 | end 54 | 55 | i = j 56 | else 57 | if save_item then 58 | table.insert(list, { 59 | filename = save_item.filename, 60 | bufnr = save_item.bufnr, 61 | lnum = save_item.lnum, 62 | col = save_item.col, 63 | text = line, 64 | user_data = 'compile_info', 65 | }) 66 | end 67 | i = i + 1 68 | end 69 | end 70 | 71 | return list 72 | end 73 | 74 | local function apply_qf_syntax() 75 | vim.cmd([[ 76 | syntax clear 77 | syntax match QfFileName /^▸ \zs[^ ]*/ 78 | syntax match QfLineCol / \d\+:\d\+/ 79 | syntax match QfErrorMsg /use.*$/ 80 | syntax match QfContext /^ .*/ 81 | syntax match QfFinish /\/ 82 | syntax match QfExit /\/ 83 | syntax match QfCode /\vcode\s+\zs\d+/ 84 | 85 | highlight QfFileName guifg=#992c3d ctermfg=Red gui=bold,underline 86 | highlight QfLineCol guifg=#c7c938 ctermfg=Yellow 87 | highlight QfErrorMsg guifg=#abb2bf ctermfg=White 88 | highlight QfContext guifg=#abb2bf ctermfg=White 89 | highlight QfFinish guifg=#62c92a ctermfg=Green 90 | highlight QfExit guifg=#992c3d ctermfg=Red gui=bold 91 | highlight QfCode guifg=#992c3d ctermfg=Red gui=bold 92 | ]]) 93 | end 94 | 95 | local info_list = { 96 | start = { 97 | user_data = 'compile_info', 98 | }, 99 | fill = { 100 | user_data = 'compile_info', 101 | text = ' ', 102 | }, 103 | cmd = { 104 | user_data = 'compile_info', 105 | }, 106 | } 107 | 108 | local function update_qf(qf_list, over) 109 | local line_colors = {} 110 | local ansi_colors = { 111 | ['30'] = 'Black', 112 | ['31'] = 'Red', 113 | ['32'] = 'Green', 114 | ['33'] = 'Yellow', 115 | ['34'] = 'Blue', 116 | ['35'] = 'Magenta', 117 | ['36'] = 'Cyan', 118 | ['37'] = 'White', 119 | } 120 | 121 | vim.fn.setqflist({}, 'a', { 122 | items = qf_list, 123 | title = over and 'Compilation' or 'Compiling', 124 | quickfixtextfunc = function(info) 125 | local lines = {} 126 | qf_id = info.id 127 | 128 | local res = vim.fn.getqflist({ id = info.id, items = 1, winid = 0 }) 129 | local items = res.items 130 | local last_bufnr = -1 131 | 132 | local lpeg = vim.lpeg 133 | local P, R, C, Ct = lpeg.P, lpeg.R, lpeg.C, lpeg.Ct 134 | 135 | -- ESC [digits m 136 | local esc = P('\27') 137 | local digits = R('09') ^ 0 138 | local code = esc 139 | * '[' 140 | * C(digits) 141 | * 'm' 142 | / function(d) 143 | return { type = 'code', value = d } 144 | end 145 | 146 | local text = C((1 - esc) ^ 1) 147 | / function(t) 148 | return { type = 'text', value = t } 149 | end 150 | 151 | local grammar = Ct((code + text) ^ 0) 152 | 153 | for i = info.start_idx, info.end_idx do 154 | local item = items[i] 155 | 156 | if item.user_data == 'compile_info' then 157 | local segs = grammar:match(item.text) 158 | local plain = {} 159 | local active = nil 160 | 161 | for _, seg in ipairs(segs) do 162 | if seg.type == 'code' then 163 | local c = seg.value 164 | if c ~= '' and c ~= '0' and ansi_colors[c] then 165 | if active then 166 | active._end = #table.concat(plain) 167 | end 168 | active = { 169 | lnum = i, 170 | start = #table.concat(plain), 171 | color = ansi_colors[c], 172 | code = tonumber(c), 173 | } 174 | table.insert(line_colors, active) 175 | else 176 | if active then 177 | active._end = #table.concat(plain) 178 | active = nil 179 | end 180 | end 181 | else 182 | table.insert(plain, seg.value) 183 | end 184 | end 185 | 186 | if active then 187 | active._end = #table.concat(plain) 188 | end 189 | table.insert(lines, table.concat(plain)) 190 | elseif item.bufnr ~= last_bufnr then 191 | table.insert( 192 | lines, 193 | string.format( 194 | '▸ %s %d:%d %s', 195 | vim.fn.bufname(item.bufnr), 196 | item.lnum, 197 | item.col, 198 | item.text 199 | ) 200 | ) 201 | last_bufnr = item.bufnr 202 | else 203 | table.insert(lines, ' ' .. item.text) 204 | end 205 | end 206 | 207 | local buf = vim.api.nvim_win_get_buf(res.winid) 208 | 209 | if #line_colors > 0 then 210 | vim.schedule(function() 211 | for _, c in ipairs(line_colors) do 212 | vim.api.nvim_set_hl(ansi_ns, 'ANSI' .. c.color, { ctermfg = c.code, fg = c.color }) 213 | vim.api.nvim_buf_set_extmark(buf, ansi_ns, c.lnum - 1, c.start, { 214 | end_col = c._end, 215 | hl_group = 'ANSI' .. c.color, 216 | }) 217 | end 218 | end) 219 | end 220 | 221 | return lines 222 | end, 223 | }) 224 | 225 | local qf_win = vim.fn.getqflist({ winid = 0 }).winid 226 | local curwin 227 | if qf_win == 0 then 228 | curwin = api.nvim_get_current_win() 229 | vim.cmd.copen() 230 | qf_win = api.nvim_get_current_win() 231 | api.nvim_win_set_hl_ns(qf_win, ansi_ns) 232 | vim.opt_local.number = false 233 | vim.opt_local.signcolumn = 'no' 234 | vim.opt_local.list = false 235 | vim.bo.textwidth = 0 236 | end 237 | 238 | if curwin and api.nvim_win_is_valid(curwin) then 239 | api.nvim_set_current_win(curwin) 240 | end 241 | 242 | api.nvim_win_call(qf_win, function() 243 | local count = api.nvim_buf_line_count(0) 244 | local height = api.nvim_win_get_height(qf_win) 245 | if count > height then 246 | api.nvim_win_set_cursor(qf_win, { count, 0 }) 247 | end 248 | apply_qf_syntax() 249 | end) 250 | end 251 | 252 | local function compiler(compile_cmd, bufname) 253 | if compile_cmd:find('%%s') then 254 | local cwd = vim.uv.cwd() 255 | if bufname:find(cwd) then 256 | bufname = bufname:sub(#cwd + 2) 257 | end 258 | compile_cmd = compile_cmd:gsub('%%s', bufname) 259 | end 260 | last_cmd = compile_cmd 261 | 262 | local start_time = vim.uv.hrtime() 263 | 264 | local stdout_buffer = '' 265 | local stderr_buffer = '' 266 | local save_item = {} 267 | 268 | info_list.cmd.text = last_cmd 269 | info_list.start.text = ('Compilation started at %s'):format(os.date('%a %b %H:%M:%S')) 270 | vim.schedule(function() 271 | local action = 'a' 272 | local qf_win 273 | if qf_id then 274 | qf_win = vim.fn.getqflist({ id = qf_id, winid = true }).winid 275 | if api.nvim_win_is_valid(qf_win) then 276 | action = 'r' 277 | end 278 | end 279 | vim.fn.setqflist({}, action, { 280 | title = 'Compiling', 281 | id = qf_id, 282 | items = { info_list.start, info_list.fill, info_list.cmd }, 283 | }) 284 | 285 | if action == 'r' then 286 | api.nvim_win_call(qf_win, function() 287 | apply_qf_syntax() 288 | end) 289 | end 290 | end) 291 | 292 | job = vim.system({ 'sh', '-c', compile_cmd }, { 293 | text = true, 294 | stdout = function(err, data) 295 | if err or not data then 296 | return 297 | end 298 | 299 | vim.schedule(function() 300 | stdout_buffer = stdout_buffer .. data 301 | local lines = vim.split(stdout_buffer, '\n', { plain = true }) 302 | if not data:match('\n$') then 303 | stdout_buffer = lines[#lines] 304 | table.remove(lines, #lines) 305 | else 306 | stdout_buffer = '' 307 | end 308 | 309 | local list = {} 310 | for _, line in ipairs(lines) do 311 | if line ~= '' then 312 | table.insert(list, { 313 | text = line, 314 | user_data = 'compile_info', 315 | }) 316 | end 317 | end 318 | 319 | update_qf(list) 320 | end) 321 | end, 322 | 323 | stderr = function(err, data) 324 | if err or not data then 325 | return 326 | end 327 | 328 | vim.schedule(function() 329 | stderr_buffer = stderr_buffer .. data 330 | local lines = vim.split(stderr_buffer, '\n', { plain = true }) 331 | if not data:match('\n$') then 332 | stderr_buffer = lines[#lines] 333 | table.remove(lines, #lines) 334 | else 335 | stderr_buffer = '' 336 | end 337 | 338 | local list = {} 339 | local err_text = table.concat(lines, '\n') 340 | if err_text ~= '' then 341 | local err_list = parse_err(err_text, save_item) 342 | vim.list_extend(list, err_list) 343 | update_qf(list) 344 | end 345 | end) 346 | end, 347 | }, function(out) 348 | local duration = (vim.uv.hrtime() - start_time) / 1e9 349 | vim.schedule(function() 350 | local list = {} 351 | if stdout_buffer ~= '' then 352 | table.insert(list, { 353 | text = stdout_buffer, 354 | user_data = 'compile_info', 355 | }) 356 | end 357 | 358 | if stderr_buffer ~= '' then 359 | local err_list = parse_err(stderr_buffer) 360 | vim.list_extend(list, err_list) 361 | end 362 | 363 | table.insert(list, { 364 | user_data = 'compile_info', 365 | text = ' ', 366 | }) 367 | table.insert(list, { 368 | user_data = 'compile_info', 369 | text = ('Compilation %s at %s, duration %fs'):format( 370 | out.code ~= 0 and 'exited abnormally with code ' .. out.code or 'finished', 371 | os.date('%a %b %H:%M:%S'), 372 | duration 373 | ), 374 | }) 375 | 376 | update_qf(list, true) 377 | end) 378 | end) 379 | end 380 | 381 | local function env_with_compile() 382 | local bufname = api.nvim_buf_get_name(0) 383 | local cwd = vim.uv.cwd() 384 | local env_file = vim.fs.joinpath(cwd, '.env') 385 | coroutine.wrap(function() 386 | local co = assert(coroutine.running()) 387 | vim.uv.fs_open(env_file, 'r', 438, function(err, fd) 388 | assert(not err) 389 | coroutine.resume(co, fd) 390 | end) 391 | local fd = coroutine.yield() 392 | 393 | vim.uv.fs_fstat(fd, function(err, stat) 394 | assert(not err) 395 | coroutine.resume(co, stat.size) 396 | end) 397 | local size = coroutine.yield() 398 | if size == 0 then 399 | return 400 | end 401 | 402 | vim.uv.fs_read(fd, size, 0, function(err, data) 403 | assert(not err) 404 | local lines = vim.split(data, '\n') 405 | local cmd = nil 406 | for _, line in ipairs(lines) do 407 | if line:find('^COMPILE_COMMAND') then 408 | cmd = line:sub(17, #line) 409 | break 410 | end 411 | end 412 | if cmd then 413 | compiler(cmd, bufname) 414 | end 415 | end) 416 | end)() 417 | end 418 | 419 | local function close_running() 420 | if job and not job:is_closing() then 421 | job:kill('sigterm') 422 | vim.notify('close running job ' .. job.pid, vim.log.levels.WARN) 423 | job = nil 424 | end 425 | end 426 | 427 | api.nvim_create_user_command('Compile', function() 428 | if not ansi_ns then 429 | ansi_ns = api.nvim_create_namespace('ansi_colors') 430 | end 431 | close_running() 432 | env_with_compile() 433 | end, {}) 434 | 435 | api.nvim_create_user_command('Recompile', function() 436 | close_running() 437 | if last_cmd then 438 | local bufname = api.nvim_buf_get_name(0) 439 | compiler(last_cmd, bufname) 440 | end 441 | end, {}) 442 | -------------------------------------------------------------------------------- /lua/private/tetris.lua: -------------------------------------------------------------------------------- 1 | local api, uv = vim.api, vim.uv 2 | local M = {} 3 | 4 | local config = { 5 | width = math.floor(vim.o.columns * 0.15), 6 | height = math.floor(vim.o.lines * 0.5), 7 | block_size = 2, 8 | } 9 | 10 | local state = { 11 | board = {}, 12 | board_colors = {}, 13 | current_piece = nil, 14 | current_x = 0, 15 | current_y = 0, 16 | ns_id = nil, 17 | score = 0, 18 | game_over = false, 19 | win = nil, 20 | buf = nil, 21 | timer = nil, 22 | speed = 500, 23 | } 24 | 25 | local pieces = { 26 | I = { 27 | shapes = { 28 | { 0b1111 }, -- ████ 29 | { 0b1, 0b1, 0b1, 0b1 }, -- █ █ █ █ 30 | }, 31 | color = 'TetrisI', 32 | width = { 4, 1 }, 33 | height = { 1, 4 }, 34 | }, 35 | O = { 36 | shapes = { 37 | { 0b11, 0b11 }, -- ██ 38 | -- ██ 39 | }, 40 | color = 'TetrisO', 41 | width = { 2 }, 42 | height = { 2 }, 43 | }, 44 | T = { 45 | shapes = { 46 | { 0b010, 0b111 }, -- ·█ 47 | -- ███ 48 | { 0b10, 0b11, 0b10 }, -- █ 49 | -- ██ 50 | -- █ 51 | { 0b111, 0b010 }, -- ███ 52 | -- ·█ 53 | { 0b01, 0b11, 0b01 }, -- ·█ 54 | -- ██ 55 | -- ·█ 56 | }, 57 | color = 'TetrisT', 58 | width = { 3, 2, 3, 2 }, 59 | height = { 2, 3, 2, 3 }, 60 | }, 61 | S = { 62 | shapes = { 63 | { 0b011, 0b110 }, -- ·██ 64 | -- ██ 65 | { 0b10, 0b11, 0b01 }, -- █ 66 | -- ██ 67 | -- ·█ 68 | }, 69 | color = 'TetrisS', 70 | width = { 3, 2 }, 71 | height = { 2, 3 }, 72 | }, 73 | Z = { 74 | shapes = { 75 | { 0b110, 0b011 }, -- ██ 76 | -- ·██ 77 | { 0b01, 0b11, 0b10 }, -- ·█ 78 | -- ██ 79 | -- █ 80 | }, 81 | color = 'TetrisZ', 82 | width = { 3, 2 }, 83 | height = { 2, 3 }, 84 | }, 85 | J = { 86 | shapes = { 87 | { 0b100, 0b111 }, -- █ 88 | -- ███ 89 | { 0b11, 0b10, 0b10 }, -- ██ 90 | -- █· 91 | -- █· 92 | { 0b111, 0b001 }, -- ███ 93 | -- ··█ 94 | { 0b01, 0b01, 0b11 }, -- ·█ 95 | -- ·█ 96 | -- ██ 97 | }, 98 | color = 'TetrisJ', 99 | width = { 3, 2, 3, 2 }, 100 | height = { 2, 3, 2, 3 }, 101 | }, 102 | L = { 103 | shapes = { 104 | { 0b001, 0b111 }, -- ··█ 105 | -- ███ 106 | { 0b10, 0b10, 0b11 }, -- █ 107 | -- █· 108 | -- ██ 109 | { 0b111, 0b100 }, -- ███ 110 | -- █ 111 | { 0b11, 0b01, 0b01 }, -- ██ 112 | -- ·█ 113 | -- ·█ 114 | }, 115 | color = 'TetrisL', 116 | width = { 3, 2, 3, 2 }, 117 | height = { 2, 3, 2, 3 }, 118 | }, 119 | } 120 | 121 | local function setup_highlights() 122 | api.nvim_set_hl(0, 'TetrisI', { bg = '#00f0f0', fg = '#000000' }) 123 | api.nvim_set_hl(0, 'TetrisO', { bg = '#f0f000', fg = '#000000' }) 124 | api.nvim_set_hl(0, 'TetrisT', { bg = '#a000f0', fg = '#000000' }) 125 | api.nvim_set_hl(0, 'TetrisS', { bg = '#00f000', fg = '#000000' }) 126 | api.nvim_set_hl(0, 'TetrisZ', { bg = '#f00000', fg = '#000000' }) 127 | api.nvim_set_hl(0, 'TetrisJ', { bg = '#0000f0', fg = '#000000' }) 128 | api.nvim_set_hl(0, 'TetrisL', { bg = '#f0a000', fg = '#000000' }) 129 | api.nvim_set_hl(0, 'TetrisBorder', { bg = '#808080', fg = '#000000' }) 130 | api.nvim_set_hl(0, 'TetrisEmpty', { bg = '#1a1a1a', fg = '#1a1a1a' }) 131 | end 132 | 133 | local function init_board() 134 | state.board = {} 135 | state.board_colors = {} 136 | for y = 1, config.height do 137 | state.board[y] = 0 -- empty line 138 | state.board_colors[y] = {} 139 | end 140 | end 141 | 142 | local function is_valid_position(piece, x, y) 143 | local shape = piece.shape 144 | local width = piece.width 145 | local height = piece.height 146 | 147 | if x < 1 or x + width - 1 > config.width then 148 | return false 149 | end 150 | 151 | if y < 1 or y + height - 1 > config.height then 152 | return false 153 | end 154 | 155 | -- collision detection 156 | for i, row_bits in ipairs(shape) do 157 | local board_y = y + i - 1 158 | if board_y >= 1 and board_y <= config.height then 159 | local shift = config.width - x - width + 1 160 | local shifted_row = bit.lshift(row_bits, shift) 161 | 162 | if bit.band(state.board[board_y], shifted_row) ~= 0 then 163 | return false 164 | end 165 | end 166 | end 167 | 168 | return true 169 | end 170 | 171 | local function spawn_piece() 172 | local piece_names = { 'I', 'O', 'T', 'S', 'Z', 'J', 'L' } 173 | local name = piece_names[math.random(#piece_names)] 174 | local piece = pieces[name] 175 | 176 | state.current_piece = { 177 | name = name, 178 | shape = piece.shapes[1], 179 | color = piece.color, 180 | rotation = 1, 181 | width = piece.width[1], 182 | height = piece.height[1], 183 | } 184 | 185 | state.current_x = math.floor((config.width - state.current_piece.width) / 2) + 1 186 | state.current_y = 1 187 | 188 | if not is_valid_position(state.current_piece, state.current_x, state.current_y) then 189 | state.game_over = true 190 | end 191 | end 192 | 193 | local function lock_piece() 194 | local piece = state.current_piece 195 | local shape = piece.shape 196 | local x = state.current_x 197 | local y = state.current_y 198 | local width = piece.width 199 | 200 | for i, row_bits in ipairs(shape) do 201 | local board_y = y + i - 1 202 | if board_y >= 1 and board_y <= config.height then 203 | local shift = config.width - x - width + 1 204 | local shifted_row = bit.lshift(row_bits, shift) 205 | state.board[board_y] = bit.bor(state.board[board_y], shifted_row) 206 | 207 | for j = 0, width - 1 do 208 | if bit.band(row_bits, bit.lshift(1, width - 1 - j)) ~= 0 then 209 | state.board_colors[board_y][x + j] = piece.color 210 | end 211 | end 212 | end 213 | end 214 | end 215 | 216 | local function clear_lines() 217 | local lines_cleared = 0 218 | local full_line = bit.lshift(1, config.width) - 1 219 | 220 | for y = config.height, 1, -1 do 221 | if state.board[y] == full_line then 222 | lines_cleared = lines_cleared + 1 223 | table.remove(state.board, y) 224 | table.remove(state.board_colors, y) 225 | table.insert(state.board, 1, 0) 226 | table.insert(state.board_colors, 1, {}) 227 | end 228 | end 229 | 230 | if lines_cleared > 0 then 231 | local score_table = { 232 | [1] = 100, -- single 233 | [2] = 300, -- double 234 | [3] = 500, -- thriple 235 | [4] = 800, -- four 236 | } 237 | state.score = state.score + (score_table[lines_cleared] or 0) 238 | -- improve speed 239 | state.speed = math.max(100, state.speed - lines_cleared * 10) 240 | end 241 | 242 | return lines_cleared 243 | end 244 | 245 | local function render() 246 | if not state.buf or not vim.api.nvim_buf_is_valid(state.buf) then 247 | return 248 | end 249 | 250 | local lines = {} 251 | local highlights = {} 252 | 253 | local render_board = {} 254 | local render_colors = {} 255 | 256 | for y = 1, config.height do 257 | render_board[y] = state.board[y] 258 | render_colors[y] = vim.deepcopy(state.board_colors[y]) 259 | end 260 | 261 | if state.current_piece and not state.game_over then 262 | local piece = state.current_piece 263 | local x = state.current_x 264 | local y = state.current_y 265 | local width = piece.width 266 | 267 | for i, row_bits in ipairs(piece.shape) do 268 | local board_y = y + i - 1 269 | if board_y >= 1 and board_y <= config.height then 270 | local shift = config.width - x - width + 1 271 | local shifted_row = bit.lshift(row_bits, shift) 272 | render_board[board_y] = bit.bor(render_board[board_y], shifted_row) 273 | 274 | for j = 0, width - 1 do 275 | if bit.band(row_bits, bit.lshift(1, width - 1 - j)) ~= 0 then 276 | render_colors[board_y][x + j] = piece.color 277 | end 278 | end 279 | end 280 | end 281 | end 282 | 283 | for y = 1, config.height do 284 | local line = '' 285 | for x = 1, config.width do 286 | local has_block = bit.band(render_board[y], bit.lshift(1, config.width - x)) ~= 0 287 | local chars = string.rep(' ', 1) 288 | line = line .. chars 289 | 290 | if has_block then 291 | local color = render_colors[y][x] or 'TetrisEmpty' 292 | table.insert(highlights, { 293 | line = #lines, 294 | col_start = (x - 1) * config.block_size, 295 | col_end = x * config.block_size, 296 | hl_group = color, 297 | }) 298 | else 299 | table.insert(highlights, { 300 | line = #lines, 301 | col_start = (x - 1) * config.block_size, 302 | col_end = x * config.block_size, 303 | hl_group = 'TetrisEmpty', 304 | }) 305 | end 306 | end 307 | table.insert(lines, line) 308 | end 309 | 310 | table.insert(lines, '') 311 | table.insert(lines, string.format('Score: %d', state.score)) 312 | table.insert(lines, string.format('Speed: %dms', state.speed)) 313 | table.insert(lines, '') 314 | table.insert(lines, 'Scoring:') 315 | table.insert(lines, 'Single: 100 Double: 300') 316 | table.insert(lines, 'Triple: 500 Tetris: 800') 317 | table.insert(lines, '') 318 | table.insert(lines, 'Controls:') 319 | table.insert(lines, '← → h l : Move') 320 | table.insert(lines, '↑ k : Rotate') 321 | table.insert(lines, '↓ j : Soft Drop') 322 | table.insert(lines, 'Space : Hard Drop') 323 | table.insert(lines, 'q Esc : Quit') 324 | 325 | if state.game_over then 326 | table.insert(lines, '') 327 | table.insert(lines, 'GAME OVER!') 328 | table.insert(lines, string.format('Final Score: %d', state.score)) 329 | end 330 | 331 | vim.bo[state.buf].modifiable = true 332 | vim.api.nvim_buf_set_lines(state.buf, 0, -1, false, lines) 333 | vim.bo[state.buf].modifiable = false 334 | 335 | state.ns_id = state.ns_id or api.nvim_create_namespace('tetris') 336 | api.nvim_buf_clear_namespace(state.buf, state.ns_id, 0, -1) 337 | 338 | for _, hl in ipairs(highlights) do 339 | vim.hl.range( 340 | state.buf, 341 | state.ns_id, 342 | hl.hl_group, 343 | { hl.line, hl.col_start }, 344 | { hl.line, hl.col_end }, 345 | {} 346 | ) 347 | end 348 | end 349 | 350 | local function game_loop() 351 | if state.game_over then 352 | if state.timer and not state.timer:is_closing() then 353 | state.timer:stop() 354 | state.timer:close() 355 | state.timer = nil 356 | end 357 | render() 358 | return 359 | end 360 | 361 | if is_valid_position(state.current_piece, state.current_x, state.current_y + 1) then 362 | state.current_y = state.current_y + 1 363 | else 364 | lock_piece() 365 | clear_lines() 366 | spawn_piece() 367 | end 368 | 369 | render() 370 | end 371 | 372 | local function move(dx, dy) 373 | if state.game_over then 374 | return 375 | end 376 | 377 | local new_x = state.current_x + dx 378 | local new_y = state.current_y + dy 379 | 380 | if is_valid_position(state.current_piece, new_x, new_y) then 381 | state.current_x = new_x 382 | state.current_y = new_y 383 | render() 384 | return true 385 | end 386 | return false 387 | end 388 | 389 | local function rotate() 390 | if state.game_over then 391 | return 392 | end 393 | 394 | local piece = state.current_piece 395 | local piece_def = pieces[piece.name] 396 | local next_rotation = (piece.rotation % #piece_def.shapes) + 1 397 | 398 | local new_piece = { 399 | name = piece.name, 400 | shape = piece_def.shapes[next_rotation], 401 | color = piece.color, 402 | rotation = next_rotation, 403 | width = piece_def.width[next_rotation], 404 | height = piece_def.height[next_rotation], 405 | } 406 | 407 | if is_valid_position(new_piece, state.current_x, state.current_y) then 408 | state.current_piece = new_piece 409 | render() 410 | return 411 | end 412 | 413 | for _, offset in ipairs({ -1, 1, -2, 2 }) do 414 | if is_valid_position(new_piece, state.current_x + offset, state.current_y) then 415 | state.current_piece = new_piece 416 | state.current_x = state.current_x + offset 417 | render() 418 | return 419 | end 420 | end 421 | end 422 | 423 | local function hard_drop() 424 | if state.game_over then 425 | return 426 | end 427 | 428 | while move(0, 1) do 429 | end 430 | end 431 | 432 | local function setup_keymaps() 433 | local opts = { buffer = state.buf } 434 | vim.keymap.set('n', '', function() 435 | move(-1, 0) 436 | end, opts) 437 | vim.keymap.set('n', 'h', function() 438 | move(-1, 0) 439 | end, opts) 440 | vim.keymap.set('n', '', function() 441 | move(1, 0) 442 | end, opts) 443 | vim.keymap.set('n', 'l', function() 444 | move(1, 0) 445 | end, opts) 446 | vim.keymap.set('n', '', function() 447 | move(0, 1) 448 | end, opts) 449 | vim.keymap.set('n', 'j', function() 450 | move(0, 1) 451 | end, opts) 452 | vim.keymap.set('n', '', function() 453 | rotate() 454 | end, opts) 455 | vim.keymap.set('n', 'k', function() 456 | rotate() 457 | end, opts) 458 | vim.keymap.set('n', '', function() 459 | hard_drop() 460 | end, opts) 461 | vim.keymap.set('n', 'q', function() 462 | M.close() 463 | end, opts) 464 | vim.keymap.set('n', '', function() 465 | M.close() 466 | end, opts) 467 | end 468 | 469 | function M.close() 470 | if state.timer and not state.timer:is_closing() then 471 | state.timer:stop() 472 | state.timer:close() 473 | state.timer = nil 474 | end 475 | 476 | if state.win and vim.api.nvim_win_is_valid(state.win) then 477 | vim.api.nvim_win_close(state.win, true) 478 | end 479 | 480 | state.win = nil 481 | state.buf = nil 482 | end 483 | 484 | function M.start() 485 | math.randomseed(os.time()) 486 | setup_highlights() 487 | init_board() 488 | 489 | state.buf = api.nvim_create_buf(false, true) 490 | vim.bo[state.buf].bufhidden = 'wipe' 491 | vim.bo[state.buf].modifiable = false 492 | 493 | local width = config.width * config.block_size 494 | local height = config.height + 16 495 | 496 | local win_opts = { 497 | relative = 'editor', 498 | width = width, 499 | height = height, 500 | col = math.floor((vim.o.columns - width) / 2), 501 | row = math.floor((vim.o.lines - height) / 2), 502 | style = 'minimal', 503 | border = 'rounded', 504 | title = 'Tetris', 505 | title_pos = 'center', 506 | } 507 | 508 | state.win = vim.api.nvim_open_win(state.buf, true, win_opts) 509 | setup_keymaps() 510 | 511 | state.score = 0 512 | state.game_over = false 513 | state.speed = 500 514 | 515 | spawn_piece() 516 | render() 517 | 518 | state.timer = assert(uv.new_timer()) 519 | state.timer:start(state.speed, state.speed, function() 520 | vim.schedule(function() 521 | game_loop() 522 | end) 523 | end) 524 | end 525 | 526 | api.nvim_create_user_command('Tetris', function() 527 | M.start() 528 | end, {}) 529 | 530 | return M 531 | -------------------------------------------------------------------------------- /snippets/zig.json: -------------------------------------------------------------------------------- 1 | { 2 | "var": { 3 | "prefix": "var", 4 | "body": ["var ${1:name}: ${2:type} = ${3:value};$0"], 5 | "description": "var decl" 6 | }, 7 | "const": { 8 | "prefix": "const", 9 | "body": ["const ${1:name}: ${2:type} = ${3:value};$0"], 10 | "description": "const decl" 11 | }, 12 | "arr_init": { 13 | "prefix": "arr_init", 14 | "body": ["[_:${1:sentinel}]${1:type}{${2:val1}, ${3:val2}, ${4:val3}};$0"], 15 | "description": "array/sentinel init" 16 | }, 17 | "list": { 18 | "prefix": "list", 19 | "body": [".{${1:val1}, ${2:val2}, ${3:val3}};$0"], 20 | "description": "anonymous list" 21 | }, 22 | "fn": { 23 | "prefix": "fn", 24 | "body": ["fn ${1:name}() ${2:return_type} {", " $0", "}"], 25 | "description": "fn decl" 26 | }, 27 | "generic_fn": { 28 | "prefix": "fn_T", 29 | "body": [ 30 | "fn ${1:name}(comptime T: type) ${2:return_type} {", 31 | " $0", 32 | "}" 33 | ], 34 | "description": "generic fn decl" 35 | }, 36 | "pub_fn": { 37 | "prefix": "pub_fn", 38 | "body": ["pub fn ${1:main}() ${2:void} {", " $0", "}"], 39 | "description": "pub fn decl" 40 | }, 41 | "extern_fn": { 42 | "prefix": "ext_fn", 43 | "body": [ 44 | "extern \"${1:sourceName}\" stdcallcc fn ${2:name}() ${3:return_type};$0" 45 | ], 46 | "description": "extern fn" 47 | }, 48 | "exp_fn": { 49 | "prefix": "exp_fn", 50 | "body": ["export fn ${1:name}() ${2:return_type} {", " $0", "}"], 51 | "description": "export fn" 52 | }, 53 | "inl_fn": { 54 | "prefix": "inline", 55 | "body": ["inline fn ${1:name}() ${2:return_type} {", " $0", "}"], 56 | "description": "inline fn" 57 | }, 58 | "nakedcc_fn": { 59 | "prefix": "naked", 60 | "body": ["nakedcc fn _${1:name}() ${2:return_type} {", " $0", "}"], 61 | "description": "nakedcc fn" 62 | }, 63 | "block": { 64 | "prefix": "block", 65 | "body": [ 66 | "${1:label}: {", 67 | " const ${2:name} = ${3:value}", 68 | " break :${1:label} ${:return_value}$0", 69 | "};" 70 | ], 71 | "description": "block expr" 72 | }, 73 | "struct_val": { 74 | "prefix": "stru_val", 75 | "body": [ 76 | "struct {", 77 | " .${1:field}: ${2:type},", 78 | " .${3:field}: ${4:type},$0", 79 | "};" 80 | ], 81 | "description": "struct val" 82 | }, 83 | "struct_decl": { 84 | "prefix": "stru_decl", 85 | "body": [ 86 | "const ${1:StructName} = struct {", 87 | " ${2:field}: ${3:type},", 88 | " ${4:field}: ${5:type},$0", 89 | "};" 90 | ], 91 | "description": "struct decl" 92 | }, 93 | "enum": { 94 | "prefix": "enum", 95 | "body": [ 96 | "const ${1:EnumName} = enum(${2:type}) {", 97 | " ${3:variant} = ${4:count},", 98 | " ${5:variant} = ${6:count},$0", 99 | "};" 100 | ], 101 | "description": "enum decl" 102 | }, 103 | "union": { 104 | "prefix": "union", 105 | "body": [ 106 | "const ${1:UnionName} = union(${2:enum}) {", 107 | " ${3:variant} : ${4:type},", 108 | " ${5:variant} : ${6:type},$0", 109 | "};" 110 | ], 111 | "description": "tagged union decl" 112 | }, 113 | "for_val": { 114 | "prefix": "for_v", 115 | "body": ["for (${1:items}) |${2:value}| {", " $0", "}"], 116 | "description": "for value loop" 117 | }, 118 | "for_val_index": { 119 | "prefix": "for_v_i", 120 | "body": ["for (${1:items}) |${2:value},${3:index}| {", " $0", "}"], 121 | "description": "for value,index loop" 122 | }, 123 | "for_inline": { 124 | "prefix": "for_inline", 125 | "body": ["inline for (${1:items}) |${2:value}| {", " $0", "}"], 126 | "description": "inline for loop" 127 | }, 128 | "for_label": { 129 | "prefix": "for_l", 130 | "body": [ 131 | "${1:label}: for (${2:items}) |_| {", 132 | " for (${3:items2}) |_| {", 133 | " ${4|break,continue|} :${1:label};$0", 134 | " }", 135 | "}" 136 | ], 137 | "description": "labeled for loop" 138 | }, 139 | "for_else": { 140 | "prefix": "for_e", 141 | "body": [ 142 | "for (${1:items}) |${2:value}| {", 143 | " break true;$0", 144 | "} else false;" 145 | ], 146 | "description": "for else loop expr" 147 | }, 148 | "while": { 149 | "prefix": "while", 150 | "body": ["while (${1:cond}) : (${2:continue_expr}) {", " $0", "}"], 151 | "description": "while loop" 152 | }, 153 | "while_else": { 154 | "prefix": "while_e", 155 | "body": [ 156 | "while (${1:cond}) : (${2:continue_expr}) {", 157 | " break true;$0", 158 | "} else false;" 159 | ], 160 | "description": "while else loop expression" 161 | }, 162 | "while_opt": { 163 | "prefix": "while?", 164 | "body": [ 165 | "while (${1:nullable}) |${2:value}| {", 166 | " $0", 167 | "} else |err| {", 168 | " ", 169 | "}" 170 | ], 171 | "description": "while optional loop" 172 | }, 173 | "while_label": { 174 | "prefix": "while_l", 175 | "body": [ 176 | "${1:label}: while (${2:cond}) : (${3:continue_expr}) {", 177 | " while (${4:cond}) : (${5:continue_expr}) {", 178 | " ${6|break,continue|} :${1:label};$0", 179 | " }", 180 | "}" 181 | ], 182 | "description": "labeled while loop" 183 | }, 184 | "while_inline": { 185 | "prefix": "while_inline", 186 | "body": ["inline while (${1:cond}) (${2:continue_expr}) {", " $0", "}"], 187 | "description": "inline while loop" 188 | }, 189 | "if": { 190 | "prefix": "if", 191 | "body": ["if (${1:cond}) {", " $0", "}"], 192 | "description": "if expr" 193 | }, 194 | "if_else": { 195 | "prefix": "if_e", 196 | "body": ["if (${1:cond}) {", " $0", "} else {", " unreachable;", "}"], 197 | "description": "if else expr" 198 | }, 199 | "if_opt": { 200 | "prefix": "if?", 201 | "body": ["if (${1:nullable}) |value| {", " $0", "}"], 202 | "description": "if optional" 203 | }, 204 | "if_else_opt": { 205 | "prefix": "if_e?", 206 | "body": [ 207 | "if (${1:nullable}) |value| {", 208 | " $0", 209 | "} else |err| switch(err) {", 210 | " ${2:err_variants} => ${3:value},", 211 | " ${4:err_variants} => ${5:value},", 212 | " else => ${6:value},", 213 | "}" 214 | ], 215 | "description": "if else optional" 216 | }, 217 | "switch": { 218 | "prefix": "switch", 219 | "body": [ 220 | "switch (${1:value}) {", 221 | " ${2:values/range} => ${3:value},", 222 | " ${4:values/range} => ${5:value},", 223 | " else => ${6:value},", 224 | "};$0" 225 | ], 226 | "description": "switch expr" 227 | }, 228 | "test": { 229 | "prefix": "test", 230 | "body": ["test ${1:name} {", " const x = true", " assert(x)$0", "}"], 231 | "description": "test" 232 | }, 233 | "orelse": { 234 | "prefix": "orelse", 235 | "body": ["orelse return unreachable$0"], 236 | "description": "orelse expr" 237 | }, 238 | "defer": { 239 | "prefix": "def", 240 | "body": ["defer {", " $0", "}"], 241 | "description": "defer block" 242 | }, 243 | "errdefer": { 244 | "prefix": "errd", 245 | "body": ["errdefer {", " $0", "}"], 246 | "description": "errdefer block" 247 | }, 248 | "error": { 249 | "prefix": "error", 250 | "body": ["error {", " ${1:variant},", " ${2:variant},$0", "};"], 251 | "description": "error decl" 252 | }, 253 | "catch": { 254 | "prefix": "catch", 255 | "body": [" catch |${1:err}| {", " $0", "};"], 256 | "description": "catch error block" 257 | }, 258 | "comptime": { 259 | "prefix": "comp", 260 | "body": ["comptime {", " $0", "}"], 261 | "description": "comptime block" 262 | }, 263 | "asm": { 264 | "prefix": "asm", 265 | "body": ["asm ${1:volatile} (", " $0", ");"], 266 | "description": "asm block" 267 | }, 268 | "async_call": { 269 | "prefix": "async", 270 | "body": ["async ${1:fn_name}(${2:params})$0"], 271 | "description": "async fn call" 272 | }, 273 | "await": { 274 | "prefix": "await", 275 | "body": ["await ${1:anyframe->T}$0"], 276 | "description": "await frame" 277 | }, 278 | "suspend_block": { 279 | "prefix": "suspend", 280 | "body": ["suspend {", " $0", "}"], 281 | "description": "suspend block" 282 | }, 283 | "resume": { 284 | "prefix": "resume", 285 | "body": ["resume ${1:anyframe};$0"], 286 | "description": "resume frame" 287 | }, 288 | "free": { 289 | "prefix": "free", 290 | "body": ["${1:allocator}.free();$0"], 291 | "description": "allocator free" 292 | }, 293 | "@alignOf": { 294 | "prefix": "alignOf", 295 | "body": ["@alignOf(${1:type})$0"], 296 | "description": "align on specific type" 297 | }, 298 | "@as": { 299 | "prefix": "as", 300 | "body": ["@as(${1:cast_type}, ${2:value})$0"], 301 | "description": "cast value" 302 | }, 303 | "@frame": { 304 | "prefix": "frame", 305 | "body": ["@frame()$0"], 306 | "description": "switching frame" 307 | }, 308 | "@import": { 309 | "prefix": "imp", 310 | "body": ["@import(\"${1:dep_name}\");$0"], 311 | "description": "import dependency" 312 | }, 313 | "@import(std)": { 314 | "prefix": "imp_std", 315 | "body": [ 316 | "@import(\"std\").${1|ascii,atomic,base64,build,builtin,c,coff,crypto,cstr,debug,dwarf,elf,event,fifo,fmt,fs,hash,hash_map,heap,http,io,json,macho,math,mem,meta,net,os,packed_int_array,pdb,process,rand,rb,sort,start,testing,time,unicode,valgrind,zigg|};$0" 317 | ], 318 | "description": "import std namespace" 319 | }, 320 | "@typeOf": { 321 | "prefix": "typeOf", 322 | "body": ["@typeOf(${1:value})$0"], 323 | "description": "type of value" 324 | }, 325 | "@typeName": { 326 | "prefix": "typeName", 327 | "body": ["@typeName(@typeOf(value), value)$0"], 328 | "description": "type name of value" 329 | }, 330 | "@panic": { 331 | "prefix": "panic", 332 | "body": ["@panic(\"${1:message}\");$0"], 333 | "description": "panic" 334 | }, 335 | "@setCold": { 336 | "prefix": "setC", 337 | "body": ["@setCold(true);$0"], 338 | "description": "set rarely call info to optimizer" 339 | }, 340 | "@sizeOf": { 341 | "prefix": "sizeOf", 342 | "body": ["@sizeOf(${1:type})$0"], 343 | "description": "size of type" 344 | }, 345 | "@compileError": { 346 | "prefix": "compileErr", 347 | "body": ["@compileError(\"${1:message}\");$0"], 348 | "description": "compile error" 349 | }, 350 | "@compileLog": { 351 | "prefix": ["compileLog", "log"], 352 | "body": ["@compileError(\"${1:message}\");$0"], 353 | "description": "compile log" 354 | }, 355 | "@math.fn": { 356 | "prefix": "math", 357 | "body": [ 358 | "@${1|sqrt,sin,cos,exp,exp2,log,log2,log10,fabs,floor,ceil,trunc,round|}(\"${2:value}\");$0" 359 | ], 360 | "description": "math functions" 361 | }, 362 | "fnCast": { 363 | "prefix": "cast", 364 | "body": [ 365 | "@${1|alignCast,bitCast,errSetCast,floatCast,intCast,ptrCast,truncate|}(${2:value}, ${3:value})$0" 366 | ], 367 | "description": "fnCast function" 368 | }, 369 | "@opWithOverflow": { 370 | "prefix": ["addWith", "subWith", "mulWith", "overflow"], 371 | "body": [ 372 | "@${1|add,sub,mul,shl|}WithOverflow(${2:type}, ${3:op_1}, ${4:op_2}, ${5:result_pointer})$0" 373 | ], 374 | "description": "Algebraic/Shift operation with overflow check" 375 | }, 376 | "@bitCast": { 377 | "prefix": "bitCast", 378 | "body": ["@byteOffsetOf(${1:packed_type}, ${2:value})$0"], 379 | "description": "value to bit cast" 380 | }, 381 | "@bitOffsetOf": { 382 | "prefix": "bitOff", 383 | "body": ["@bitOffsetOf(${1:packed_type}, ${2:value})$0"], 384 | "description": "bit offset" 385 | }, 386 | "@byteOffsetOf": { 387 | "prefix": "byteOff", 388 | "body": ["@byteOffsetOf(${1:packed_type}, ${2:value})$0"], 389 | "description": "byte offset" 390 | }, 391 | "@bytesToSlice": { 392 | "prefix": "bytesToSlice", 393 | "body": ["@bytesToSlice(${1:packed_type}, ${2:slice})$0"], 394 | "description": "bytes to slice" 395 | }, 396 | "@memberCount": { 397 | "prefix": "memberC", 398 | "body": ["@memberCount(${1:enum_name})$0"], 399 | "description": "enum member count" 400 | }, 401 | "@memberName": { 402 | "prefix": "member_name", 403 | "body": ["@memberName(${1:enum_name}, ${2:variant_count})$0"], 404 | "description": "enum member name" 405 | }, 406 | "@tagName": { 407 | "prefix": "tagName", 408 | "body": ["@tagName(${1:variant}, ${2:variant_string})$0"], 409 | "description": "enum tag name" 410 | }, 411 | "@tagType": { 412 | "prefix": "tagType", 413 | "body": ["@tagType(${1:enum_name})$0"], 414 | "description": "enum tag's count type" 415 | }, 416 | "reflection.type_prop": { 417 | "prefix": "reflect.type_prop", 418 | "body": ["@typeOf(${1:value}).${2|Payload,ErrorSet,Child|}$0"], 419 | "description": "type property (Property,ErrorSet,...)" 420 | }, 421 | "@atomicLoad": { 422 | "prefix": "atomicLoad", 423 | "body": [ 424 | "@atomicLoad(${1:type}, ${2:const_ptr}, .${3|AcqRel,Acquire,Monotonic,Release,SecCst,Unordered|})$0" 425 | ], 426 | "description": "atomics: load ptr dereferenced value" 427 | }, 428 | "@atomicRmw": { 429 | "prefix": "atomicRmw", 430 | "body": [ 431 | "@atomicRmw(${1:type}, ${2:ptr}, .${3|Add,And,Max,Min,Nand,Or,Sub,Xchg,Xor|}, ${4:operand_type}, ${5:builtin.AtomicOrder})$0" 432 | ], 433 | "description": "atomics: modify memory and return prev value" 434 | }, 435 | "std.debug.warn": { 436 | "prefix": "warn", 437 | "body": ["${1:std.debug.}warn(\"${2:message} {}\\n\", .{${3:err}});$0"], 438 | "description": "std.debug.warn" 439 | }, 440 | "std.mem.eql": { 441 | "prefix": "mem.eql", 442 | "body": ["mem.eql(${1:type}, ${2:value}, ${3:value})$0"], 443 | "description": "std.mem.eql" 444 | }, 445 | "std.in/out": { 446 | "prefix": ["stdin", "stdout", "stderr"], 447 | "body": [ 448 | "std.io.${1|getStdOut().outStream(),getStdIn().inStream(),getStdErr()|}$0" 449 | ], 450 | "description": "std.io in/out/err" 451 | }, 452 | "std.debug.assert": { 453 | "prefix": "assert", 454 | "body": ["${1:std.debug.}assert(${2:cond});$0"], 455 | "description": "std.debug.assert" 456 | }, 457 | "std.testing.expect": { 458 | "prefix": "expect", 459 | "body": ["${1:std.testing.}expect(${2:cond});$0"], 460 | "description": "std.testing.expect" 461 | }, 462 | "std.heap.ArenaAllocator": { 463 | "prefix": ["arena", "alloc", "heap"], 464 | "body": [ 465 | "var ${1:arena_all_name} = std.heap.ArenaAllocator.init(std.heap.direct_allocator);", 466 | "defer ${1:arena_all_name}.deinit();", 467 | "const allocator = &${1:arena_all_name}.allocator;", 468 | "const ptr = try allocator.create(${1:type});$0" 469 | ], 470 | "description": "std.heap.ArenaAllocator" 471 | }, 472 | "main_template": { 473 | "prefix": ["main", "hello"], 474 | "body": [ 475 | "const std = @import(\"std\");", 476 | "", 477 | "pub fn main() !void {", 478 | " const stdout = &std.io.getStdOut().outStream();", 479 | " try stdout.print(\"Hello {}!\\n\", .{\"world\"});$0", 480 | "}" 481 | ], 482 | "description": "main/hello world" 483 | }, 484 | "builder_template": { 485 | "prefix": "builder_template", 486 | "body": [ 487 | "const Builder = @import(\"std\").build.Builder;", 488 | "", 489 | "pub fn build(b: *Builder) void {", 490 | " const exe = b.addExecutable(${example}, ${2:example}.zig);", 491 | " exe.setBuildMode(b.standardReleaseOptions());", 492 | " b.default_step.dependeOn(&exe.step);$0", 493 | "}" 494 | ], 495 | "description": "Default build.zig builder" 496 | }, 497 | "adt_template": { 498 | "prefix": "adt_template", 499 | "body": [ 500 | "const std = @import(\"std\");\n", 501 | "const ExprTag = enum {\n Num,\n Plus,\n};\n", 502 | "const Expr = union(ExprTag) {\n Num: i32,\n Plus: struct{ e1: *Expr, e2: *Expr},\n};", 503 | "fn eval(e: *const Expr) i32 {", 504 | " return switch (e.*) {", 505 | " .Num => |n| n,", 506 | " .Plus => |*plus_elem| eval(plus_elem.e1) + eval(plus_elem.e2),", 507 | " else => unreachable,\n };\n}", 508 | "pub fn main() !void {", 509 | " const stdout = &std.io.getStdOut().outStream();", 510 | " const e = &Expr{ .Plus = .{ .e1 = &Expr{ .Num = 6}, .e2 = &Expr{ .Num = 5}}};", 511 | " try stdout.print(\"Hello {}!\\n\", .{\"world\"});$0", 512 | "}" 513 | ], 514 | "description": "Algebraic Data Type" 515 | } 516 | } 517 | -------------------------------------------------------------------------------- /colors/solarized.lua: -------------------------------------------------------------------------------- 1 | local colors = { 2 | base04 = '#00202b', 3 | base03 = '#002838', 4 | base02 = '#073642', 5 | base01 = '#586e75', 6 | base00 = '#657b83', 7 | base0 = '#839496', 8 | base1 = '#93a1a1', 9 | base2 = '#eee8d5', 10 | base3 = '#fdf6e3', 11 | yellow = '#b58900', 12 | orange = '#b86114', 13 | red = '#d75f5f', 14 | violet = '#887ec8', 15 | blue = '#268bd2', 16 | cyan = '#2aa198', 17 | green = '#87a828', -- L=64.4, C=64.8, Contrast=5.63 18 | magenta = '#d33682', 19 | fg = '#a8a8a8', 20 | } 21 | 22 | vim.g.colors_name = 'solarized' 23 | 24 | local function h(group, properties) 25 | vim.api.nvim_set_hl(0, group, properties) 26 | end 27 | 28 | local function hex_to_rgb(hex) 29 | hex = hex:gsub('#', '') 30 | return { 31 | tonumber(hex:sub(1, 2), 16), 32 | tonumber(hex:sub(3, 4), 16), 33 | tonumber(hex:sub(5, 6), 16), 34 | } 35 | end 36 | 37 | local function rgb_to_hex(c) 38 | return string.format('#%02x%02x%02x', c[1], c[2], c[3]) 39 | end 40 | 41 | local function blend(fg, bg, t) 42 | local a, b = hex_to_rgb(fg), hex_to_rgb(bg) 43 | local c = { 44 | math.floor(a[1] * (1 - t) + b[1] * t + 0.5), 45 | math.floor(a[2] * (1 - t) + b[2] * t + 0.5), 46 | math.floor(a[3] * (1 - t) + b[3] * t + 0.5), 47 | } 48 | return rgb_to_hex(c) 49 | end 50 | 51 | -- ============================================================================ 52 | -- Scientific Color Analysis Functions 53 | -- ============================================================================ 54 | local function relative_luminance(hex) 55 | local rgb = hex_to_rgb(hex) 56 | local function adjust(c) 57 | c = c / 255 58 | return c <= 0.03928 and c / 12.92 or math.pow((c + 0.055) / 1.055, 2.4) 59 | end 60 | return 0.2126 * adjust(rgb[1]) + 0.7152 * adjust(rgb[2]) + 0.0722 * adjust(rgb[3]) 61 | end 62 | 63 | local function contrast_ratio(fg, bg) 64 | local l1 = relative_luminance(fg) 65 | local l2 = relative_luminance(bg) 66 | local lighter = math.max(l1, l2) 67 | local darker = math.min(l1, l2) 68 | return (lighter + 0.05) / (darker + 0.05) 69 | end 70 | 71 | local function rgb_to_xyz(hex) 72 | local rgb = hex_to_rgb(hex) 73 | local function linearize(c) 74 | c = c / 255 75 | return c <= 0.04045 and c / 12.92 or math.pow((c + 0.055) / 1.055, 2.4) 76 | end 77 | local r, g, b = linearize(rgb[1]), linearize(rgb[2]), linearize(rgb[3]) 78 | return { 79 | x = r * 0.4124564 + g * 0.3575761 + b * 0.1804375, 80 | y = r * 0.2126729 + g * 0.7151522 + b * 0.0721750, 81 | z = r * 0.0193339 + g * 0.1191920 + b * 0.9503041, 82 | } 83 | end 84 | 85 | local function xyz_to_lab(xyz) 86 | local Xn, Yn, Zn = 0.95047, 1.00000, 1.08883 87 | local x, y, z = xyz.x / Xn, xyz.y / Yn, xyz.z / Zn 88 | local delta = 6.0 / 29.0 89 | local function f(t) 90 | return t > delta ^ 3 and t ^ (1 / 3) or t / (3 * delta ^ 2) + 4 / 29 91 | end 92 | local fx, fy, fz = f(x), f(y), f(z) 93 | return { 94 | L = 116 * fy - 16, 95 | a = 500 * (fx - fy), 96 | b = 200 * (fy - fz), 97 | } 98 | end 99 | 100 | local function lab_to_lch(lab) 101 | local C = math.sqrt(lab.a ^ 2 + lab.b ^ 2) 102 | local H = math.atan2(lab.b, lab.a) * 180 / math.pi 103 | if H < 0 then 104 | H = H + 360 105 | end 106 | return { L = lab.L, C = C, H = H } 107 | end 108 | 109 | local function rgb_to_lch(hex) 110 | return lab_to_lch(xyz_to_lab(rgb_to_xyz(hex))) 111 | end 112 | 113 | local function lch_to_lab(lch) 114 | local h_rad = lch.H * math.pi / 180 115 | return { 116 | L = lch.L, 117 | a = lch.C * math.cos(h_rad), 118 | b = lch.C * math.sin(h_rad), 119 | } 120 | end 121 | 122 | local function lab_to_xyz(lab) 123 | local Xn, Yn, Zn = 0.95047, 1.00000, 1.08883 124 | local fy = (lab.L + 16) / 116 125 | local fx = lab.a / 500 + fy 126 | local fz = fy - lab.b / 200 127 | local delta = 6.0 / 29.0 128 | local function finv(t) 129 | return t > delta and t ^ 3 or 3 * delta ^ 2 * (t - 4 / 29) 130 | end 131 | return { 132 | x = Xn * finv(fx), 133 | y = Yn * finv(fy), 134 | z = Zn * finv(fz), 135 | } 136 | end 137 | 138 | local function xyz_to_rgb(xyz) 139 | local r = xyz.x * 3.2404542 + xyz.y * -1.5371385 + xyz.z * -0.4985314 140 | local g = xyz.x * -0.9692660 + xyz.y * 1.8760108 + xyz.z * 0.0415560 141 | local b = xyz.x * 0.0556434 + xyz.y * -0.2040259 + xyz.z * 1.0572252 142 | local function gamma(c) 143 | return c <= 0.0031308 and c * 12.92 or 1.055 * c ^ (1 / 2.4) - 0.055 144 | end 145 | local function clamp(c) 146 | return math.max(0, math.min(255, math.floor(gamma(c) * 255 + 0.5))) 147 | end 148 | return { clamp(r), clamp(g), clamp(b) } 149 | end 150 | 151 | local function lch_to_rgb(lch) 152 | return rgb_to_hex(xyz_to_rgb(lab_to_xyz(lch_to_lab(lch)))) 153 | end 154 | 155 | -- ============================================================================ 156 | -- Detailed Color Testing 157 | -- ============================================================================ 158 | local function test_color_detailed(hex) 159 | local results = {} 160 | 161 | if not hex:match('^#[0-9a-fA-F]+$') then 162 | return { 'Error: Invalid hex format. Use #rrggbb (e.g., #84a800)' } 163 | end 164 | 165 | table.insert(results, string.format('=== Color Analysis: %s ===', hex)) 166 | table.insert(results, '') 167 | 168 | -- LCH analysis 169 | local lch = rgb_to_lch(hex) 170 | local contrast = contrast_ratio(hex, colors.base03) 171 | 172 | -- Detect if this is a neutral gray (C ≈ 0) 173 | local is_gray = lch.C < 5 174 | 175 | table.insert(results, 'LCH Color Space Values:') 176 | table.insert(results, string.format(' Lightness (L): %.1f', lch.L)) 177 | table.insert(results, string.format(' - 0 = black, 100 = white')) 178 | 179 | if is_gray then 180 | table.insert(results, string.format(' - For normal text: 65-75 recommended')) 181 | else 182 | table.insert(results, string.format(' - For syntax colors: 55-65 recommended')) 183 | end 184 | 185 | table.insert(results, string.format(' Chroma (C): %.1f', lch.C)) 186 | table.insert(results, string.format(' - 0 = gray, higher = more saturated')) 187 | 188 | if is_gray then 189 | table.insert(results, string.format(' - This is a neutral gray (C≈0) ✓')) 190 | else 191 | table.insert(results, string.format(' - Recommended: 40-70 (moderate)')) 192 | end 193 | 194 | table.insert(results, string.format(' Hue (H): %.1f°', lch.H)) 195 | if not is_gray then 196 | table.insert(results, string.format(' - 0°=red, 90°=yellow, 180°=green, 270°=blue')) 197 | else 198 | table.insert(results, string.format(' - (Hue irrelevant for neutral gray)')) 199 | end 200 | table.insert(results, '') 201 | 202 | -- Contrast analysis 203 | table.insert(results, string.format('WCAG Contrast vs background (%s):', colors.base03)) 204 | table.insert(results, string.format(' Ratio: %.2f:1', contrast)) 205 | table.insert( 206 | results, 207 | string.format(' Level AA (≥4.5:1): %s', contrast >= 4.5 and '✓ PASS' or '✗ FAIL') 208 | ) 209 | table.insert( 210 | results, 211 | string.format(' Level AAA (≥7:1): %s', contrast >= 7 and '✓ PASS' or '✗ FAIL') 212 | ) 213 | table.insert( 214 | results, 215 | string.format( 216 | ' Comfortable (4.5-6.5): %s', 217 | contrast >= 4.5 and contrast <= 6.5 and '✓ Yes' or '~ Outside range' 218 | ) 219 | ) 220 | table.insert(results, '') 221 | 222 | -- Ergonomic assessment 223 | table.insert(results, 'Ergonomic Assessment:') 224 | 225 | if is_gray then 226 | -- For normal text (gray) 227 | local L_ok = lch.L >= 65 and lch.L <= 75 228 | table.insert(results, string.format(' Type: Normal text (neutral gray)')) 229 | table.insert( 230 | results, 231 | string.format( 232 | ' Lightness: %s', 233 | L_ok and '✓ Optimal for body text' 234 | or lch.L < 65 and '⚠ Too dark for extended reading' 235 | or '⚠ Too bright (may cause glare)' 236 | ) 237 | ) 238 | table.insert(results, string.format(' Saturation: ✓ Neutral (correct for text)')) 239 | else 240 | -- For syntax colors 241 | local L_ok = lch.L >= 55 and lch.L <= 65 242 | local C_ok = lch.C >= 40 and lch.C <= 70 243 | 244 | table.insert(results, string.format(' Type: Syntax highlight color')) 245 | table.insert( 246 | results, 247 | string.format( 248 | ' Lightness: %s', 249 | L_ok and '✓ Optimal' or lch.L < 55 and '⚠ Too dark' or '⚠ Too bright' 250 | ) 251 | ) 252 | table.insert( 253 | results, 254 | string.format( 255 | ' Saturation: %s', 256 | C_ok and '✓ Moderate' or lch.C < 40 and '⚠ Low (washed out)' or '⚠ High (fatiguing)' 257 | ) 258 | ) 259 | end 260 | table.insert(results, '') 261 | 262 | -- Optimization suggestion - FIXED LOGIC 263 | local needs_optimization = false 264 | 265 | if is_gray then 266 | if lch.L < 65 or lch.L > 75 or contrast < 4.5 or contrast > 6.5 then 267 | needs_optimization = true 268 | end 269 | else 270 | -- Need optimization if: 271 | -- 1. L or C out of range, OR 272 | -- 2. Contrast too low (< 4.5), OR 273 | -- 3. Contrast too high (> 6.5) 274 | local L_ok = lch.L >= 55 and lch.L <= 65 275 | local C_ok = lch.C >= 40 and lch.C <= 70 276 | local contrast_ok = contrast >= 4.5 and contrast <= 6.5 277 | 278 | if not (L_ok and C_ok and contrast_ok) then 279 | needs_optimization = true 280 | end 281 | end 282 | 283 | if needs_optimization then 284 | table.insert(results, '【Optimization Suggestion】') 285 | 286 | if is_gray then 287 | -- For gray, only adjust L 288 | local opt_L = math.max(65, math.min(75, lch.L)) 289 | if lch.L < 65 then 290 | opt_L = 68 291 | elseif lch.L > 75 then 292 | opt_L = 72 293 | end 294 | 295 | -- Adjust L for contrast 296 | if contrast < 4.5 then 297 | -- Increase L to achieve 4.5:1 contrast 298 | for test_L = opt_L, 85, 0.5 do 299 | local test_val = math.floor((test_L / 100) * 255 + 0.5) 300 | local test_hex = string.format('#%02x%02x%02x', test_val, test_val, test_val) 301 | if contrast_ratio(test_hex, colors.base03) >= 4.5 then 302 | opt_L = test_L 303 | break 304 | end 305 | end 306 | end 307 | 308 | local opt_lch = { L = opt_L, C = 0, H = 0 } 309 | local opt_val = math.floor((opt_lch.L / 100) * 255 + 0.5) 310 | local opt_hex = string.format('#%02x%02x%02x', opt_val, opt_val, opt_val) 311 | local opt_contrast = contrast_ratio(opt_hex, colors.base03) 312 | 313 | table.insert(results, string.format(' Suggested: %s (neutral gray)', opt_hex)) 314 | table.insert( 315 | results, 316 | string.format( 317 | ' Changes: L: %.1f→%.1f (C and H preserved as neutral)', 318 | lch.L, 319 | opt_lch.L 320 | ) 321 | ) 322 | table.insert(results, string.format(' Contrast: %.2f→%.2f:1', contrast, opt_contrast)) 323 | else 324 | -- For colors, adjust L and C 325 | local opt_L = lch.L 326 | local opt_C = lch.C 327 | 328 | -- Step 1: Adjust L to target range 329 | if lch.L < 55 then 330 | opt_L = 58 331 | elseif lch.L > 65 then 332 | opt_L = 62 333 | end 334 | 335 | -- Step 2: Adjust C to target range 336 | if lch.C < 40 then 337 | opt_C = 50 338 | elseif lch.C > 70 then 339 | opt_C = 60 340 | end 341 | 342 | -- Step 3: CRITICAL - Ensure minimum contrast of 4.5:1 343 | if contrast < 4.5 then 344 | -- Binary search for minimum L that achieves 4.5:1 345 | local low, high = opt_L, 75 346 | for _ = 1, 30 do 347 | local mid = (low + high) / 2 348 | local test_lch = { L = mid, C = opt_C, H = lch.H } 349 | local test_hex = lch_to_rgb(test_lch) 350 | local test_contrast = contrast_ratio(test_hex, colors.base03) 351 | 352 | if test_contrast >= 4.5 then 353 | opt_L = mid 354 | high = mid - 0.1 355 | else 356 | low = mid + 0.1 357 | end 358 | end 359 | elseif contrast > 6.5 then 360 | -- Too bright, reduce L slightly 361 | opt_L = opt_L * 0.95 362 | end 363 | 364 | local opt_lch = { 365 | L = opt_L, 366 | C = opt_C, 367 | H = lch.H, 368 | } 369 | 370 | local opt_hex = lch_to_rgb(opt_lch) 371 | local opt_contrast = contrast_ratio(opt_hex, colors.base03) 372 | 373 | table.insert(results, string.format(' Suggested: %s', opt_hex)) 374 | table.insert( 375 | results, 376 | string.format( 377 | ' Changes: L: %.1f→%.1f C: %.1f→%.1f H: %.1f° (preserved)', 378 | lch.L, 379 | opt_lch.L, 380 | lch.C, 381 | opt_lch.C, 382 | lch.H 383 | ) 384 | ) 385 | table.insert(results, string.format(' Contrast: %.2f→%.2f:1', contrast, opt_contrast)) 386 | end 387 | table.insert(results, '') 388 | else 389 | table.insert(results, '✓ Color is already well-optimized!') 390 | table.insert(results, '') 391 | end 392 | 393 | -- RGB breakdown 394 | local rgb = hex_to_rgb(hex) 395 | table.insert(results, 'RGB Components:') 396 | table.insert( 397 | results, 398 | string.format(' Red: %3d (0x%02x) %.1f%%', rgb[1], rgb[1], rgb[1] / 255 * 100) 399 | ) 400 | table.insert( 401 | results, 402 | string.format(' Green: %3d (0x%02x) %.1f%%', rgb[2], rgb[2], rgb[2] / 255 * 100) 403 | ) 404 | table.insert( 405 | results, 406 | string.format(' Blue: %3d (0x%02x) %.1f%%', rgb[3], rgb[3], rgb[3] / 255 * 100) 407 | ) 408 | 409 | if is_gray then 410 | table.insert(results, ' Note: Equal RGB values = neutral gray') 411 | end 412 | 413 | return results 414 | end 415 | 416 | -- ============================================================================ 417 | -- Theme Analysis 418 | -- ============================================================================ 419 | local function analyze_theme_simple() 420 | local results = {} 421 | 422 | table.insert(results, '=== Scientific Theme Analysis ===') 423 | table.insert(results, 'Principles: LAB Uniformity + Comfortable Contrast + Moderate Saturation') 424 | table.insert(results, '') 425 | 426 | local color_list = { 427 | { 'cyan', 'String' }, 428 | { 'green', 'Keyword' }, 429 | { 'blue', 'Function' }, 430 | { 'yellow', 'Type' }, 431 | { 'violet', 'Constant' }, 432 | { 'orange', 'Special' }, 433 | { 'red', 'Error' }, 434 | { 'magenta', 'Todo' }, 435 | } 436 | 437 | -- Calculate L* uniformity 438 | local L_values = {} 439 | for _, item in ipairs(color_list) do 440 | local lch = rgb_to_lch(colors[item[1]]) 441 | table.insert(L_values, lch.L) 442 | end 443 | 444 | local sum_L = 0 445 | for _, L in ipairs(L_values) do 446 | sum_L = sum_L + L 447 | end 448 | local mean_L = sum_L / #L_values 449 | 450 | local variance = 0 451 | for _, L in ipairs(L_values) do 452 | variance = variance + (L - mean_L) ^ 2 453 | end 454 | local std_dev = math.sqrt(variance / #L_values) 455 | 456 | table.insert(results, '【1. Lightness Uniformity】') 457 | table.insert(results, string.format(' Average L*: %.1f', mean_L)) 458 | table.insert(results, string.format(' Std Dev: %.1f', std_dev)) 459 | if std_dev < 8 then 460 | table.insert(results, ' ✓ Excellent uniformity (low eye strain)') 461 | elseif std_dev < 12 then 462 | table.insert(results, ' ~ Good uniformity') 463 | else 464 | table.insert(results, ' ⚠ High variance (may cause fatigue)') 465 | end 466 | table.insert(results, '') 467 | 468 | -- Individual color analysis 469 | table.insert(results, '【2. Individual Colors】') 470 | table.insert(results, 'Color L* C* H° Contrast Assessment') 471 | table.insert( 472 | results, 473 | '────────────────────────────────────────────────────' 474 | ) 475 | 476 | local needs_adjustment = {} 477 | 478 | for _, item in ipairs(color_list) do 479 | local name, label = item[1], item[2] 480 | local hex = colors[name] 481 | local lch = rgb_to_lch(hex) 482 | local contrast = contrast_ratio(hex, colors.base03) 483 | 484 | local issues = {} 485 | if lch.L < 50 then 486 | table.insert(issues, 'dark') 487 | elseif lch.L > 70 then 488 | table.insert(issues, 'bright') 489 | end 490 | if lch.C < 35 then 491 | table.insert(issues, 'low sat') 492 | elseif lch.C > 75 then 493 | table.insert(issues, 'high sat') 494 | end 495 | if contrast < 4 then 496 | table.insert(issues, 'low contrast') 497 | elseif contrast > 7 then 498 | table.insert(issues, 'high contrast') 499 | end 500 | 501 | local status = #issues == 0 and '✓' or table.concat(issues, ',') 502 | 503 | table.insert( 504 | results, 505 | string.format( 506 | '%-8s %5.1f %5.1f %5.0f° %5.2f:1 %s', 507 | label, 508 | lch.L, 509 | lch.C, 510 | lch.H, 511 | contrast, 512 | status 513 | ) 514 | ) 515 | 516 | if #issues > 0 then 517 | table.insert(needs_adjustment, { 518 | name = name, 519 | label = label, 520 | lch = lch, 521 | issues = issues, 522 | }) 523 | end 524 | end 525 | 526 | table.insert( 527 | results, 528 | '────────────────────────────────────────────────────' 529 | ) 530 | table.insert(results, '') 531 | 532 | -- Optimization suggestions 533 | if #needs_adjustment == 0 then 534 | table.insert(results, '【3. Result】') 535 | table.insert(results, ' ✓ Your palette is well-balanced!') 536 | else 537 | table.insert(results, '【3. Suggested Micro-adjustments】') 538 | table.insert(results, '') 539 | 540 | for _, item in ipairs(needs_adjustment) do 541 | local current_hex = colors[item.name] 542 | local current_lch = item.lch 543 | 544 | local target_L = math.max(55, math.min(65, current_lch.L)) 545 | if current_lch.L < 55 then 546 | target_L = 58 547 | elseif current_lch.L > 65 then 548 | target_L = 62 549 | end 550 | 551 | local target_C = math.max(40, math.min(70, current_lch.C)) 552 | if current_lch.C < 40 then 553 | target_C = 50 554 | elseif current_lch.C > 70 then 555 | target_C = 60 556 | end 557 | 558 | local new_lch = { 559 | L = current_lch.L + (target_L - current_lch.L) * 0.3, 560 | C = current_lch.C + (target_C - current_lch.C) * 0.3, 561 | H = current_lch.H, 562 | } 563 | 564 | local new_hex = lch_to_rgb(new_lch) 565 | local new_contrast = contrast_ratio(new_hex, colors.base03) 566 | 567 | table.insert(results, string.format('%s (%s):', item.label, item.name)) 568 | table.insert(results, string.format(' %s → %s', current_hex, new_hex)) 569 | table.insert( 570 | results, 571 | string.format( 572 | ' L: %.1f→%.1f C: %.1f→%.1f Contrast: %.2f→%.2f', 573 | current_lch.L, 574 | new_lch.L, 575 | current_lch.C, 576 | new_lch.C, 577 | contrast_ratio(current_hex, colors.base03), 578 | new_contrast 579 | ) 580 | ) 581 | table.insert(results, '') 582 | end 583 | end 584 | 585 | return results 586 | end 587 | 588 | local function calculate_standard_fg(background, target_contrast) 589 | target_contrast = target_contrast or 6.3 590 | 591 | local low, high = 50, 90 592 | local best_L = 70 593 | local best_diff = 999 594 | 595 | for _ = 1, 50 do 596 | local mid = (low + high) / 2 597 | local test_val = math.floor((mid / 100) * 255 + 0.5) 598 | local test_hex = string.format('#%02x%02x%02x', test_val, test_val, test_val) 599 | local test_contrast = contrast_ratio(test_hex, background) 600 | 601 | local diff = math.abs(test_contrast - target_contrast) 602 | if diff < best_diff then 603 | best_diff = diff 604 | best_L = mid 605 | end 606 | 607 | if test_contrast < target_contrast then 608 | low = mid 609 | else 610 | high = mid 611 | end 612 | 613 | if diff < 0.01 then 614 | break 615 | end 616 | end 617 | 618 | local optimal_val = math.floor((best_L / 100) * 255 + 0.5) 619 | local optimal_hex = string.format('#%02x%02x%02x', optimal_val, optimal_val, optimal_val) 620 | 621 | return { 622 | hex = optimal_hex, 623 | L = best_L, 624 | contrast = contrast_ratio(optimal_hex, background), 625 | } 626 | end 627 | 628 | vim.api.nvim_create_user_command('ColorForeground', function(opts) 629 | print(vim.inspect(calculate_standard_fg(colors.base03, tonumber(opts.args)))) 630 | end, { desc = 'Analyze theme with scientific principles', nargs = '?' }) 631 | 632 | vim.api.nvim_create_user_command('ColorAnalyze', function() 633 | local results = analyze_theme_simple() 634 | vim.api.nvim_echo({ { table.concat(results, '\n'), 'Normal' } }, true, {}) 635 | end, { desc = 'Analyze theme with scientific principles' }) 636 | 637 | vim.api.nvim_create_user_command('ColorTest', function(opts) 638 | local results = test_color_detailed(opts.args) 639 | vim.api.nvim_echo({ { table.concat(results, '\n'), 'Normal' } }, true, {}) 640 | end, { nargs = 1, desc = 'Test a color with detailed analysis' }) 641 | 642 | -- General editor highlights 643 | h('Normal', { fg = colors.fg, bg = colors.base03 }) 644 | h('EndOfBuffer', { fg = colors.base03 }) 645 | h('CursorLine', { bg = colors.base02 }) 646 | h('CursorLineNr', { fg = colors.base1, bg = colors.base02 }) 647 | h('LineNr', { fg = colors.base01 }) 648 | h('Comment', { fg = colors.base01, italic = true }) 649 | h('String', { fg = colors.cyan }) 650 | h('Function', { fg = colors.blue }) 651 | h('Keyword', { fg = colors.green }) 652 | h('Constant', { fg = colors.violet }) 653 | h('Identifier', { fg = colors.fg }) 654 | h('Statement', { fg = colors.green }) 655 | h('Number', { link = 'Constant' }) 656 | h('Float', { link = 'Number' }) 657 | h('PreProc', { fg = colors.orange }) 658 | h('Type', { fg = colors.yellow }) 659 | h('Special', { fg = colors.orange }) 660 | h('Operator', { fg = colors.base0 }) 661 | h('Underlined', { fg = colors.violet, underline = true }) 662 | h('Todo', { fg = colors.magenta, bold = true }) 663 | h('Error', { fg = colors.red, bg = colors.base03, bold = true }) 664 | h('WarningMsg', { fg = colors.orange }) 665 | h('IncSearch', { fg = colors.base03, bg = colors.orange }) 666 | h('Search', { fg = colors.base03, bg = colors.yellow }) 667 | h('Visual', { fg = colors.base01, bg = colors.base03, reverse = true }) 668 | h('Pmenu', { fg = colors.base0, bg = colors.base04 }) 669 | h('PmenuMatch', { fg = colors.cyan, bg = colors.base04, bold = true }) 670 | h('PmenuMatchSel', { fg = colors.cyan, bg = colors.base00, bold = true }) 671 | h('PmenuSel', { fg = colors.base3, bg = colors.base00 }) 672 | h('PmenuSbar', { bg = colors.base1 }) 673 | h('PmenuThumb', { bg = colors.base01 }) 674 | h('MatchParen', { bg = colors.base02 }) 675 | h('WinBar', { bg = colors.base02 }) 676 | h('NormalFloat', { bg = colors.base04 }) 677 | h('FloatBorder', { fg = colors.blue }) 678 | h('Title', { fg = colors.yellow }) 679 | h('WinSeparator', { fg = colors.base00 }) 680 | h('StatusLine', { bg = colors.base1, fg = colors.base02 }) 681 | h('StatusLineNC', { bg = colors.base00, fg = colors.base02 }) 682 | h('ModeMsg', { fg = colors.cyan }) 683 | h('ColorColumn', { bg = colors.base02 }) 684 | h('Title', { fg = colors.orange }) 685 | h('WildMenu', { fg = colors.base2, bg = colors.base02, reverse = true }) 686 | h('Folded', { bg = colors.base04, fg = colors.base0 }) 687 | h('ErrorMsg', { fg = colors.red }) 688 | h('ComplMatchIns', { fg = colors.base01 }) 689 | h('Directory', { fg = colors.cyan }) 690 | h('QuickFixLine', { bold = true }) 691 | h('qfFileName', { fg = colors.blue }) 692 | h('qfSeparator', { fg = colors.base01 }) 693 | h('qfLineNr', { link = 'LineNr' }) 694 | h('qfText', { link = 'Normal' }) 695 | 696 | -- Treesitter highlights 697 | h('@variable', { link = 'Identifier' }) 698 | h('@variable.builtin', { link = '@variable' }) 699 | h('@variable.parameter', { link = '@variable' }) 700 | h('@variable.parameter.builtin', { link = '@variable.builtin' }) 701 | h('@variable.member', { link = '@variable' }) 702 | 703 | h('@constant', { link = 'Constant' }) 704 | h('@constant.builtin', { link = 'Constant' }) 705 | h('@constant.macro', { link = 'Constant' }) 706 | 707 | h('@module', { link = 'Identifier' }) 708 | h('@module.builtin', { link = '@module' }) 709 | 710 | h('@label', { link = 'Label' }) 711 | h('@string', { link = 'String' }) 712 | h('@string.documentation', { link = 'Comment' }) 713 | h('@string.regexp', { link = '@string' }) 714 | h('@string.escape', { link = 'SpecialChar' }) 715 | h('@string.special', { link = '@string' }) 716 | h('@string.special.symbol', { link = '@string' }) 717 | h('@string.special.path', { link = '@string' }) 718 | h('@string.special.url', { link = 'Underlined' }) 719 | 720 | h('@character', { link = 'String' }) 721 | h('@character.special', { link = '@character' }) 722 | 723 | h('@boolean', { link = 'Constant' }) 724 | h('@number', { link = 'Number' }) 725 | h('@number.float', { link = 'Float' }) 726 | 727 | h('@type', { link = 'Type' }) 728 | h('@type.builtin', { link = 'Type' }) 729 | h('@type.definition', { link = 'Type' }) 730 | 731 | h('@attribute', { link = 'Macro' }) 732 | h('@attribute.builtin', { link = 'Special' }) 733 | h('@property', { link = 'Identifier' }) 734 | 735 | h('@function', { link = 'Function' }) 736 | h('@function.builtin', { link = 'Function' }) 737 | h('@function.call', { link = '@function' }) 738 | h('@function.macro', { link = '@function' }) 739 | h('@function.method', { link = '@function' }) 740 | h('@function.method.call', { link = '@function' }) 741 | h('@constructor', { link = 'Function' }) 742 | h('@operator', { link = 'Operator' }) 743 | 744 | h('@keyword', { link = 'Keyword' }) 745 | h('@keyword.coroutine', { link = '@keyword' }) 746 | h('@keyword.function', { link = '@keyword' }) 747 | h('@keyword.operator', { link = '@keyword' }) 748 | h('@keyword.import', { link = 'PreProc' }) 749 | h('@keyword.type', { link = '@keyword' }) 750 | h('@keyword.modifier', { link = '@keyword' }) 751 | h('@keyword.repeat', { link = '@keyword' }) 752 | h('@keyword.return', { link = '@keyword' }) 753 | h('@keyword.debug', { link = '@keyword' }) 754 | h('@keyword.exception', { link = '@keyword' }) 755 | h('@keyword.conditional', { link = '@keyword' }) 756 | h('@keyword.conditional.ternary', { link = '@operator' }) 757 | h('@keyword.directive', { link = '@keyword' }) 758 | h('@keyword.directive.define', { link = '@keyword' }) 759 | 760 | h('@punctuation', { fg = colors.fg }) 761 | h('@punctuation.delimiter', { link = '@punctuation' }) 762 | h('@punctuation.bracket', { link = '@punctuation' }) 763 | h('@punctuation.special', { link = '@punctuation' }) 764 | 765 | h('@comment', { link = 'Comment' }) 766 | h('@comment.documentation', { link = '@comment' }) 767 | h('@comment.error', { fg = colors.red, bold = true }) 768 | h('@comment.warning', { fg = colors.yellow, bold = true }) 769 | h('@comment.todo', { link = 'Special' }) 770 | h('@comment.note', { link = 'Special' }) 771 | 772 | h('@markup', { link = 'Comment' }) 773 | h('@markup.strong', { bold = true }) 774 | h('@markup.italic', { italic = true }) 775 | h('@markup.strikethrough', { strikethrough = true }) 776 | h('@markup.underline', { link = 'Underlined' }) 777 | h('@markup.heading', { link = 'Title' }) 778 | h('@markup.heading.1', { link = '@markup.heading' }) 779 | h('@markup.heading.2', { link = '@markup.heading' }) 780 | h('@markup.heading.3', { link = '@markup.heading' }) 781 | h('@markup.heading.4', { link = '@markup.heading' }) 782 | h('@markup.heading.5', { link = '@markup.heading' }) 783 | h('@markup.heading.6', { link = '@markup.heading' }) 784 | h('@markup.quote', {}) 785 | h('@markup.math', { link = 'String' }) 786 | h('@markup.link', { link = 'Underlined' }) 787 | h('@markup.link.label', { link = '@markup.link' }) 788 | h('@markup.link.url', { link = '@markup.link' }) 789 | h('@markup.raw', {}) 790 | h('@markup.raw.block', { link = '@markup.raw' }) 791 | h('@markup.list', {}) 792 | h('@markup.list.checked', { fg = colors.green }) 793 | h('@markup.list.unchecked', { link = '@markup.list' }) 794 | 795 | h('@diff.plus', { link = 'Added' }) 796 | h('@diff.minus', { link = 'Removed' }) 797 | h('@diff.delta', { link = 'Changed' }) 798 | 799 | h('@tag', { fg = colors.green }) 800 | h('@tag.attribute', { fg = colors.base0 }) 801 | h('@tag.delimiter', { fg = colors.base0 }) 802 | h('@tag.builtin', { link = 'Special' }) 803 | 804 | h('@constant.comment', { link = 'SpecialComment' }) 805 | h('@number.comment', { link = 'Comment' }) 806 | h('@punctuation.bracket.comment', { link = 'SpecialComment' }) 807 | h('@punctuation.delimiter.comment', { link = 'SpecialComment' }) 808 | h('@label.vimdoc', { link = 'String' }) 809 | h('@markup.heading.1.delimiter.vimdoc', { link = '@markup.heading.1' }) 810 | h('@markup.heading.2.delimiter.vimdoc', { link = '@markup.heading.2' }) 811 | 812 | h('@parameter', { fg = colors.base0 }) 813 | h('@class', { fg = colors.yellow }) 814 | h('@method', { fg = colors.blue }) 815 | 816 | h('@constant.comment', { link = 'SpecialComment' }) 817 | h('@number.comment', { link = 'Comment' }) 818 | h('@punctuation.bracket.comment', { link = 'SpecialComment' }) 819 | h('@punctuation.delimiter.comment', { link = 'SpecialComment' }) 820 | h('@label.vimdoc', { link = 'String' }) 821 | h('@markup.heading.1.delimiter.vimdoc', { link = '@markup.heading.1' }) 822 | h('@markup.heading.2.delimiter.vimdoc', { link = '@markup.heading.2' }) 823 | 824 | h('@interface', { fg = colors.yellow }) 825 | h('@namespace', { fg = colors.base0 }) 826 | 827 | -- Neovim LSP semantic highlights 828 | h('@lsp.type.class', { link = '@type' }) 829 | h('@lsp.type.comment', { link = '@comment' }) 830 | h('@lsp.type.decorator', { link = '@attribute' }) 831 | h('@lsp.type.enum', { link = '@type' }) 832 | h('@lsp.type.enumMember', { link = '@constant' }) 833 | h('@lsp.type.event', { link = '@type' }) 834 | h('@lsp.type.function', { link = '@function' }) 835 | h('@lsp.type.interface', { link = '@type' }) 836 | h('@lsp.type.keyword', { link = '@keyword' }) 837 | h('@lsp.type.macro', { link = 'Macro' }) 838 | h('@lsp.type.method', { link = '@function.method' }) 839 | h('@lsp.type.modifier', { link = '@type.qualifier' }) 840 | h('@lsp.type.namespace', { link = '@module' }) 841 | h('@lsp.type.number', { link = '@number' }) 842 | h('@lsp.type.operator', { link = '@operator' }) 843 | h('@lsp.type.parameter', { link = '@variable.parameter' }) 844 | h('@lsp.type.property', { link = '@property' }) 845 | h('@lsp.type.regexp', { link = '@string.regexp' }) 846 | h('@lsp.type.string', { link = '@string' }) 847 | h('@lsp.type.struct', { link = '@type' }) 848 | h('@lsp.type.type', { link = '@type' }) 849 | h('@lsp.type.typeParameter', { link = '@type.definition' }) 850 | h('@lsp.type.variable', { link = '@variable' }) 851 | 852 | h('@lsp.mod.abstract', {}) 853 | h('@lsp.mod.async', {}) 854 | h('@lsp.mod.declaration', {}) 855 | h('@lsp.mod.defaultLibrary', {}) 856 | h('@lsp.mod.definition', {}) 857 | h('@lsp.mod.deprecated', { link = 'DiagnosticDeprecated' }) 858 | h('@lsp.mod.documentation', {}) 859 | h('@lsp.mod.modification', {}) 860 | h('@lsp.mod.readonly', {}) 861 | h('@lsp.mod.static', {}) 862 | 863 | -- Diagnostics 864 | h('DiagnosticError', { fg = colors.red }) 865 | h('DiagnosticWarn', { fg = colors.yellow }) 866 | h('DiagnosticInfo', { fg = colors.blue }) 867 | h('DiagnosticHint', { fg = colors.cyan }) 868 | h('DiagnosticVirtualTextError', { bg = blend(colors.red, colors.base03, 0.3) }) 869 | h('DiagnosticVirtualTextWarn', { bg = blend(colors.yellow, colors.base03, 0.5) }) 870 | h('DiagnosticVirtualTextInfo', { bg = blend(colors.blue, colors.base03, 0.5) }) 871 | h('DiagnosticVirtualTextHint', { bg = blend(colors.cyan, colors.base03, 0.5) }) 872 | 873 | h('DiagnosticPrefixError', { fg = colors.red, bg = blend(colors.red, colors.base03, 0.3) }) 874 | h('DiagnosticPrefixWarn', { fg = colors.yellow, bg = blend(colors.yellow, colors.base03, 0.5) }) 875 | h('DiagnosticPrefixInfo', { fg = colors.blue, bg = blend(colors.blue, colors.base03, 0.5) }) 876 | h('DiagnosticPrefixHint', { fg = colors.cyan, bg = blend(colors.cyan, colors.base03, 0.5) }) 877 | 878 | h('DiagnosticUnderlineError', { undercurl = true, sp = colors.red }) 879 | h('DiagnosticUnderlineWarn', { undercurl = true, sp = colors.yellow }) 880 | h('DiagnosticUnderlineInfo', { undercurl = true, sp = colors.blue }) 881 | h('DiagnosticUnderlineHint', { undercurl = true, sp = colors.cyan }) 882 | 883 | -- LSP 884 | h('LspReferenceText', { bg = colors.base02 }) 885 | h('LspReferenceRead', { bg = colors.base02 }) 886 | h('LspReferenceWrite', { bg = colors.base02 }) 887 | h('LspReferenceTarget', { link = 'LspReferenceText' }) 888 | h('LspInlayHint', { link = 'NonText' }) 889 | h('LspCodeLens', { link = 'NonText' }) 890 | h('LspCodeLensSeparator', { link = 'NonText' }) 891 | h('LspSignatureActiveParameter', { link = 'LspReferenceText' }) 892 | 893 | -- Indentmini 894 | h('IndentLine', { link = 'Comment' }) 895 | h('IndentLineCurrent', { fg = '#084352' }) 896 | 897 | -- GitSigns 898 | h('GitSignsAdd', { fg = colors.green, bg = colors.base03 }) 899 | h('GitSignsChange', { fg = colors.yellow, bg = colors.base03 }) 900 | h('GitSignsDelete', { fg = colors.red, bg = colors.base03 }) 901 | h('DashboardHeader', { fg = colors.green }) 902 | h('ModeLineMode', { bold = true }) 903 | h('ModeLinefileinfo', { bold = true }) 904 | --------------------------------------------------------------------------------