commit 294e135ed7bc818285b36e82399687d6111fd17a Author: Hoang Nguyen Date: Sat Oct 28 00:00:00 2023 +0700 Initial commit diff --git a/.neoconf.json b/.neoconf.json new file mode 100644 index 0000000..d666302 --- /dev/null +++ b/.neoconf.json @@ -0,0 +1,15 @@ +{ + "neoconf": { + "live_reload": false, + "import": { + "vscode": false + } + }, + "lspconfig": { + "gopls": { + "gopls.buildFlags": [ + "-tags=mage" + ] + } + } +} diff --git a/.stylua.toml b/.stylua.toml new file mode 100644 index 0000000..c7ce035 --- /dev/null +++ b/.stylua.toml @@ -0,0 +1,6 @@ +column_width = 120 +line_endings = 'Unix' +indent_type = 'Spaces' +indent_width = 4 +quote_style = 'AutoPreferSingle' +call_parentheses = 'NoSingleTable' diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..8c60438 --- /dev/null +++ b/LICENSE @@ -0,0 +1,9 @@ +MIT License + +Copyright (c) 2021-2023 Hoang Nguyen + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..ff387a4 --- /dev/null +++ b/README.md @@ -0,0 +1,62 @@ +# FollieHiyuki's Neovim configuration + +This Neovim config used to live within [my dotfiles](/FollieHiyuki/dotfiles-ansible). Due to the complexity of refactoring both regular files/templates and this Lua codebase, I decided to move it into its own repository. As such, this project stays under the same license as my dotfiles, MIT. + +## TODO + +### Colorschemes + +- OneDark +- Catpuccin + +### Missing tree-sitter parsers + +- [templ](https://github.com/vrischmann/tree-sitter-templ) +- [rescript](https://github.com/nkrkv/tree-sitter-rescript/) +- nftables +- Go template (text/html) +- Jinja2 +- [typst](https://github.com/typst/typst/issues/118) + +### Additional DAP/LSP servers + +- elixirls (config for `nvim-lspconfig` and `nvim-dap` + install script) +- denols +- ocamllsp +- vscode-js-debug (config for `nvim-dap` + install script) + +### More plugins + +- clangd_extensions.nvim +- crates.nvim +- numb.nvim +- package-info.nvim +- pretty-fold.nvim / nvim-ufo +- refactoring.nvim +- rust-tools.nvim +- telekasten.nvim +- tangerine.nvim / aniseed / hotpot / nfnl +- silicon.nvim / silicon.lua +- nabla.nvim +- markdown-preview.nvim -> peek.nvim / smp.nvim +- grpc-nvim +- nvim-ide +- haskell-tools.nvim +- nvim-asciidoc-preview +- lsp-lens.nvim +- venv-selector.nvim +- virtual-types.nvim +- hover.nvim +- nvim-tree.lua -> neo-tree??? + +### Logic + +- Smooth colorschemes changing (dynamic highlight groups registration via event hooks) + +## FAQ + +### Why not using [mason.nvim](https://github.com/williamboman/mason.nvim)? + +Most things installed via **mason.nvim** are in binary format, which might be incompatible to run on the Linux distribution I use - [AlpineLinux](https://alpinelinux.org). I resolved to custom [mage](https://magefile.org) tasks instead. The [tasks' source code](./magefile.go) is pretty simple and naive, but it works for me. + +External LSP servers that can't be installed directly from AlpineLinux's repositories, or with simple `cargo/go install` commands, are defined inside [pkg.yaml](./pkg.yaml) file. diff --git a/after/ftplugin/asciidoc.lua b/after/ftplugin/asciidoc.lua new file mode 100644 index 0000000..0d07285 --- /dev/null +++ b/after/ftplugin/asciidoc.lua @@ -0,0 +1,4 @@ +local o = vim.opt_local +o.wrap = true +o.number = false +o.relativenumber = false diff --git a/after/ftplugin/gemini.lua b/after/ftplugin/gemini.lua new file mode 120000 index 0000000..6f7c94b --- /dev/null +++ b/after/ftplugin/gemini.lua @@ -0,0 +1 @@ +asciidoc.lua \ No newline at end of file diff --git a/after/ftplugin/go.lua b/after/ftplugin/go.lua new file mode 100644 index 0000000..765e4ac --- /dev/null +++ b/after/ftplugin/go.lua @@ -0,0 +1,5 @@ +local o = vim.opt_local +o.list = false +o.softtabstop = 2 +o.tabstop = 2 +o.expandtab = false diff --git a/after/ftplugin/gomod.lua b/after/ftplugin/gomod.lua new file mode 120000 index 0000000..bc684f3 --- /dev/null +++ b/after/ftplugin/gomod.lua @@ -0,0 +1 @@ +go.lua \ No newline at end of file diff --git a/after/ftplugin/gowork.lua b/after/ftplugin/gowork.lua new file mode 120000 index 0000000..6254c7a --- /dev/null +++ b/after/ftplugin/gowork.lua @@ -0,0 +1 @@ +/home/follie/.config/nvim/after/ftplugin/go.lua \ No newline at end of file diff --git a/after/ftplugin/hcl.lua b/after/ftplugin/hcl.lua new file mode 100644 index 0000000..ae423a7 --- /dev/null +++ b/after/ftplugin/hcl.lua @@ -0,0 +1,6 @@ +local o = vim.opt_local +o.autoindent = false +o.expandtab = true +o.softtabstop = 2 +o.tabstop = 2 +o.commentstring = "# %s" diff --git a/after/ftplugin/json.lua b/after/ftplugin/json.lua new file mode 100644 index 0000000..fcafc43 --- /dev/null +++ b/after/ftplugin/json.lua @@ -0,0 +1,9 @@ +local o = vim.opt_local +o.autoindent = true +o.conceallevel = 0 +o.expandtab = true +o.foldmethod = 'syntax' +o.formatoptions = 'tcq2l' +o.shiftwidth = 2 +o.softtabstop = 2 +o.tabstop = 4 diff --git a/after/ftplugin/jsonc.lua b/after/ftplugin/jsonc.lua new file mode 120000 index 0000000..551c0bf --- /dev/null +++ b/after/ftplugin/jsonc.lua @@ -0,0 +1 @@ +json.lua \ No newline at end of file diff --git a/after/ftplugin/make.lua b/after/ftplugin/make.lua new file mode 100644 index 0000000..1fb87a3 --- /dev/null +++ b/after/ftplugin/make.lua @@ -0,0 +1,4 @@ +local o = vim.opt_local +o.expandtab = false +o.softtabstop = 2 +o.tabstop = 2 diff --git a/after/ftplugin/markdown.lua b/after/ftplugin/markdown.lua new file mode 120000 index 0000000..18ba468 --- /dev/null +++ b/after/ftplugin/markdown.lua @@ -0,0 +1 @@ +/home/follie/.config/nvim/after/ftplugin/asciidoc.lua \ No newline at end of file diff --git a/after/ftplugin/terraform.lua b/after/ftplugin/terraform.lua new file mode 120000 index 0000000..7ad1e75 --- /dev/null +++ b/after/ftplugin/terraform.lua @@ -0,0 +1 @@ +hcl.lua \ No newline at end of file diff --git a/after/ftplugin/tex.lua b/after/ftplugin/tex.lua new file mode 100644 index 0000000..9e06b04 --- /dev/null +++ b/after/ftplugin/tex.lua @@ -0,0 +1,12 @@ +local o = vim.opt_local +o.wrap = true +o.number = false +o.relativenumber = false + +-- Preview file at cursor with TexLab LSP command +vim.keymap.set('n', 'p', 'TexlabForward', { + buffer = true, + noremap = true, + silent = true, + desc = 'Preview LaTEX file', +}) diff --git a/after/ftplugin/vim.lua b/after/ftplugin/vim.lua new file mode 100644 index 0000000..b6d0152 --- /dev/null +++ b/after/ftplugin/vim.lua @@ -0,0 +1,4 @@ +local o = vim.opt_local +o.colorcolumn = { 120 } +o.iskeyword = vim.opt.iskeyword + ':' + '#' +o.tags = vim.opt.tags + '$DATA_PATH/tags' diff --git a/after/ftplugin/yaml.lua b/after/ftplugin/yaml.lua new file mode 100644 index 0000000..8a7d896 --- /dev/null +++ b/after/ftplugin/yaml.lua @@ -0,0 +1,6 @@ +local o = vim.opt_local +o.autoindent = true +o.expandtab = true +o.softtabstop = 2 +o.tabstop = 2 +o.indentkeys:remove(':') diff --git a/after/plugin/colors.lua b/after/plugin/colors.lua new file mode 100644 index 0000000..d5247c0 --- /dev/null +++ b/after/plugin/colors.lua @@ -0,0 +1 @@ +vim.cmd('colorscheme nord') diff --git a/after/syntax/gohtmltmpl.vim b/after/syntax/gohtmltmpl.vim new file mode 100644 index 0000000..ea3dbf0 --- /dev/null +++ b/after/syntax/gohtmltmpl.vim @@ -0,0 +1,17 @@ +if exists("b:current_syntax") + finish +endif + +if !exists("g:main_syntax") + let g:main_syntax = 'html' +endif + +runtime! syntax/gotexttmpl.vim +runtime! syntax/html.vim +unlet b:current_syntax + +syn cluster htmlPreproc add=gotplAction,goTplComment + +let b:current_syntax = "gohtmltmpl" + +" vim: sw=2 ts=2 et diff --git a/after/syntax/gotexttmpl.vim b/after/syntax/gotexttmpl.vim new file mode 100644 index 0000000..f38bb26 --- /dev/null +++ b/after/syntax/gotexttmpl.vim @@ -0,0 +1,83 @@ +" Copyright 2011 The Go Authors. All rights reserved. +" Use of this source code is governed by a BSD-style +" license that can be found in the LICENSE file. +" +" gotexttmpl.vim: Vim syntax file for Go templates. + +" Quit when a (custom) syntax file was already loaded +if exists("b:current_syntax") + finish +endif + +syn case match + +" Go escapes +syn match goEscapeOctal display contained "\\[0-7]\{3}" +syn match goEscapeC display contained +\\[abfnrtv\\'"]+ +syn match goEscapeX display contained "\\x\x\{2}" +syn match goEscapeU display contained "\\u\x\{4}" +syn match goEscapeBigU display contained "\\U\x\{8}" +syn match goEscapeError display contained +\\[^0-7xuUabfnrtv\\'"]+ + +hi def link goEscapeOctal goSpecialString +hi def link goEscapeC goSpecialString +hi def link goEscapeX goSpecialString +hi def link goEscapeU goSpecialString +hi def link goEscapeBigU goSpecialString +hi def link goSpecialString Special +hi def link goEscapeError Error + +" Strings and their contents +syn cluster goStringGroup contains=goEscapeOctal,goEscapeC,goEscapeX,goEscapeU,goEscapeBigU,goEscapeError +syn region goString contained start=+"+ skip=+\\\\\|\\"+ end=+"+ contains=@goStringGroup +syn region goRawString contained start=+`+ end=+`+ + +hi def link goString String +hi def link goRawString String + +" Characters; their contents +syn cluster goCharacterGroup contains=goEscapeOctal,goEscapeC,goEscapeX,goEscapeU,goEscapeBigU +syn region goCharacter contained start=+'+ skip=+\\\\\|\\'+ end=+'+ contains=@goCharacterGroup + +hi def link goCharacter Character + +" Integers +syn match goDecimalInt contained "\<\d\+\([Ee]\d\+\)\?\>" +syn match goHexadecimalInt contained "\<0x\x\+\>" +syn match goOctalInt contained "\<0\o\+\>" +syn match goOctalError contained "\<0\o*[89]\d*\>" +syn cluster goInt contains=goDecimalInt,goHexadecimalInt,goOctalInt +" Floating point +syn match goFloat contained "\<\d\+\.\d*\([Ee][-+]\d\+\)\?\>" +syn match goFloat contained "\<\.\d\+\([Ee][-+]\d\+\)\?\>" +syn match goFloat contained "\<\d\+[Ee][-+]\d\+\>" +" Imaginary literals +syn match goImaginary contained "\<\d\+i\>" +syn match goImaginary contained "\<\d\+\.\d*\([Ee][-+]\d\+\)\?i\>" +syn match goImaginary contained "\<\.\d\+\([Ee][-+]\d\+\)\?i\>" +syn match goImaginary contained "\<\d\+[Ee][-+]\d\+i\>" + +hi def link goInt Number +hi def link goFloat Number +hi def link goImaginary Number + +" Token groups +syn cluster gotplLiteral contains=goString,goRawString,goCharacter,@goInt,goFloat,goImaginary +syn keyword gotplControl contained if else end range with template +syn keyword gotplFunctions contained and html index js len not or print printf println urlquery eq ne lt le gt ge +syn match gotplVariable contained /\$[a-zA-Z0-9_]*\>/ +syn match goTplIdentifier contained /\.[^[:blank:]}]\+\>/ + +hi def link gotplControl Keyword +hi def link gotplFunctions Function +hi def link goTplVariable Special + +syn region gotplAction start="{{" end="}}" contains=@gotplLiteral,gotplControl,gotplFunctions,gotplVariable,goTplIdentifier display +syn region goTplComment start="{{\(- \)\?/\*" end="\*/\( -\)\?}}" display + +hi def link gotplAction PreProc +hi def link goTplComment Comment + +let b:current_syntax = "gotexttmpl" + +" vim: sw=2 ts=2 et diff --git a/after/syntax/jinja.vim b/after/syntax/jinja.vim new file mode 100644 index 0000000..5304f1b --- /dev/null +++ b/after/syntax/jinja.vim @@ -0,0 +1,90 @@ +" Oringinal author: Armin Ronacher +" Modified by: Alejandro "HiPhish" Sanchez +" See the LICENSE file for copyright information + + +syntax case match + +if exists('b:current_syntax') && b:current_syntax =~? 'jinja' + finish +endif + +" Jinja template built-in tags and parameters (without filter, macro, is and raw, they +" have special threatment) +syn keyword jinjaStatement containedin=jinjaVarBlock,jinjaTagBlock,jinjaNested contained and if else in not or recursive as import + +syn keyword jinjaStatement containedin=jinjaVarBlock,jinjaTagBlock,jinjaNested contained is filter skipwhite nextgroup=jinjaFilter +syn keyword jinjaStatement containedin=jinjaTagBlock contained macro skipwhite nextgroup=jinjaFunction +syn keyword jinjaStatement containedin=jinjaTagBlock contained block skipwhite nextgroup=jinjaBlockName + +" Variable Names +syn match jinjaVariable containedin=jinjaVarBlock,jinjaTagBlock,jinjaNested contained /[a-zA-Z_][a-zA-Z0-9_]*/ +syn keyword jinjaSpecial containedin=jinjaVarBlock,jinjaTagBlock,jinjaNested contained false true none False True None loop super caller varargs kwargs + +" Filters +syn match jinjaOperator "|" containedin=jinjaVarBlock,jinjaTagBlock,jinjaNested contained skipwhite nextgroup=jinjaFilter +syn match jinjaFilter contained /[a-zA-Z_][a-zA-Z0-9_]*/ +syn match jinjaFunction contained /[a-zA-Z_][a-zA-Z0-9_]*/ +syn match jinjaBlockName contained /[a-zA-Z_][a-zA-Z0-9_]*/ + +" Jinja template constants +syn region jinjaString containedin=jinjaVarBlock,jinjaTagBlock,jinjaNested contained start=/"/ skip=/\(\\\)\@\)*\\"/ end=/"/ +syn region jinjaString containedin=jinjaVarBlock,jinjaTagBlock,jinjaNested contained start=/'/ skip=/\(\\\)\@\)*\\'/ end=/'/ +syn match jinjaNumber containedin=jinjaVarBlock,jinjaTagBlock,jinjaNested contained /[0-9]\+\(\.[0-9]\+\)\?/ + +" Operators +syn match jinjaOperator containedin=jinjaVarBlock,jinjaTagBlock,jinjaNested contained /[+\-*\/<>=!,:]/ +syn match jinjaPunctuation containedin=jinjaVarBlock,jinjaTagBlock,jinjaNested contained /[()\[\]]/ +syn match jinjaOperator containedin=jinjaVarBlock,jinjaTagBlock,jinjaNested contained /\./ nextgroup=jinjaAttribute +syn match jinjaAttribute contained /[a-zA-Z_][a-zA-Z0-9_]*/ + +" Jinja template tag and variable blocks +syn region jinjaNested matchgroup=jinjaOperator start="(" end=")" transparent display containedin=jinjaVarBlock,jinjaTagBlock,jinjaNested contained +syn region jinjaNested matchgroup=jinjaOperator start="\[" end="\]" transparent display containedin=jinjaVarBlock,jinjaTagBlock,jinjaNested contained +syn region jinjaNested matchgroup=jinjaOperator start="{" end="}" transparent display containedin=jinjaVarBlock,jinjaTagBlock,jinjaNested contained +syn region jinjaTagBlock matchgroup=jinjaTagDelim start=/{%-\?/ end=/-\?%}/ containedin=ALLBUT,jinjaTagBlock,jinjaVarBlock,jinjaRaw,jinjaString,jinjaNested,jinjaComment + +syn region jinjaVarBlock matchgroup=jinjaVarDelim start=/{{-\?/ end=/-\?}}/ containedin=ALLBUT,jinjaTagBlock,jinjaVarBlock,jinjaRaw,jinjaString,jinjaNested,jinjaComment + +" Jinja template 'raw' tag +syn region jinjaRaw matchgroup=jinjaRawDelim start="{%\s*raw\s*%}" end="{%\s*endraw\s*%}" containedin=ALLBUT,jinjaTagBlock,jinjaVarBlock,jinjaString,jinjaComment + +" Jinja comments +syn region jinjaComment matchgroup=jinjaCommentDelim start="{#" end="#}" containedin=ALLBUT,jinjaTagBlock,jinjaVarBlock,jinjaString + +" Block start keywords. A bit tricker. We only highlight at the start of a +" tag block and only if the name is not followed by a comma or equals sign +" which usually means that we have to deal with an assignment. +syn match jinjaStatement containedin=jinjaTagBlock contained /\({%-\?\s*\)\@<=\<[a-zA-Z_][a-zA-Z0-9_]*\>\(\s*[,=]\)\@!/ + +" and context modifiers +syn match jinjaStatement containedin=jinjaTagBlock contained /\/ + + +command -nargs=+ HiLink hi def link + +HiLink jinjaPunctuation jinjaOperator +HiLink jinjaAttribute jinjaVariable +HiLink jinjaFunction jinjaFilter + +HiLink jinjaTagDelim jinjaTagBlock +HiLink jinjaVarDelim jinjaVarBlock +HiLink jinjaCommentDelim jinjaComment +HiLink jinjaRawDelim jinja + +HiLink jinjaSpecial Special +HiLink jinjaOperator Normal +HiLink jinjaRaw Normal +HiLink jinjaTagBlock PreProc +HiLink jinjaVarBlock PreProc +HiLink jinjaStatement Statement +HiLink jinjaFilter Function +HiLink jinjaBlockName Function +HiLink jinjaVariable Identifier +HiLink jinjaString Constant +HiLink jinjaNumber Constant +HiLink jinjaComment Comment + +delcommand HiLink + +let b:current_syntax = 'jinja' diff --git a/after/syntax/nftables.vim b/after/syntax/nftables.vim new file mode 100644 index 0000000..3c9aacc --- /dev/null +++ b/after/syntax/nftables.vim @@ -0,0 +1,40 @@ +" Source: https://github.com/nfnty/vim-nftables +" License: MIT + +if exists('b:current_syntax') + finish +endif + +syn match nftablesSet /{.*}/ contains=nftablesSetEntry +syn match nftablesSetEntry /[a-zA-Z0-9]\+/ contained +hi def link nftablesSet Keyword +hi def link nftablesSetEntry Operator + +syn match nftablesNumber "\<[0-9A-Fa-f./:]\+\>" contains=nftablesMask,nftablesDelimiter +syn match nftablesHex "\<0x[0-9A-Fa-f]\+\>" +syn match nftablesDelimiter "[./:]" contained +syn match nftablesMask "/[0-9.]\+" contained contains=nftablesDelimiter +hi def link nftablesNumber Number +hi def link nftablesHex Number +hi def link nftablesDelimiter Operator +hi def link nftablesMask Operator + +syn region Comment start=/#/ end=/$/ +syn region String start=/"/ end=/"/ +syn keyword Function flush +syn keyword Function table chain map +syn keyword Statement type hook +syn keyword Type ip ip6 inet arp bridge +syn keyword Type filter nat route +syn keyword Type ether vlan arp ip icmp igmp ip6 icmpv6 tcp udp udplite sctp dccp ah esp comp icmpx +syn keyword Type ct +syn keyword Type length protocol priority mark iif iifname iiftype oif oifname oiftype skuid skgid rtclassid +syn keyword Constant prerouting input forward output postrouting + +syn keyword Special snat dnat masquerade redirect +syn keyword Special accept drop reject queue +syn keyword Keyword continue return jump goto +syn keyword Keyword counter log limit +syn keyword Keyword define + +let b:current_syntax = 'nftables' diff --git a/after/syntax/rescript.vim b/after/syntax/rescript.vim new file mode 100644 index 0000000..0df542e --- /dev/null +++ b/after/syntax/rescript.vim @@ -0,0 +1,122 @@ +if exists("b:current_syntax") + finish +endif + +" See https://github.com/rescript-lang/vim-rescript/issues/14 +syntax sync minlines=600 + +" Boolean +syntax keyword resBoolean true false + +" Keywords +syntax keyword resKeyword let rec type external mutable lazy private of with +syntax keyword resKeyword if else switch when +syntax keyword resKeyword and as open include module in constraint import export +syntax keyword resKeyword for to downto while +syntax keyword resKeyword try catch exception assert +syntax keyword resKeyword async await + +" Types +syntax keyword resType bool int float char string unit +syntax keyword resType list array option ref exn format + +" Operators +syntax keyword resOperator mod land lor lxor lsl lsr asr +syntax keyword resOperator or + +syntax match resOperator "\v\=" + +syntax match resOperator "\v\*" +syntax match resOperator "\v/" +syntax match resOperator "\v\+" +syntax match resOperator "\v-" + +syntax match resOperator "\v\*\." +syntax match resOperator "\v/\." +syntax match resOperator "\v\+\." +syntax match resOperator "\v-\." + +syntax match resOperator "\v\<" +syntax match resOperator "\v\<\=" +syntax match resOperator "\v\>" +syntax match resOperator "\v\>\=" + +syntax match resOperator "\v\@" + +syntax match resOperator "\v\!" +syntax match resOperator "\v\|" +syntax match resOperator "\v\&" + +" Refs +syntax match resOperator "\v\:\=" + +" Arrows / Pipes +syntax match resArrowPipe "\v\=\>" +syntax match resArrowPipe "\v\-\>" +syntax match resArrowPipe "\v\|\>" +syntax match resArrowPipe "\v\@\@" + +" Comment +syntax region resSingleLineComment start="//" end="$" contains=resTodo,@Spell +syntax region resMultiLineComment start="/\*\s*" end="\*/" contains=@Spell,resTodo,resMultiLineComment + +syntax keyword resTodo contained TODO FIXME XXX NOTE + +" Char +syntax match resChar "\v'\\.'|'.'" + +syntax match resNumber "-\=\<\d\(_\|\d\)*[l|L|n]\?\>" +syntax match resNumber "-\=\<0[x|X]\(\x\|_\)\+[l|L|n]\?\>" +syntax match resNumber "-\=\<0[o|O]\(\o\|_\)\+[l|L|n]\?\>" +syntax match resNumber "-\=\<0[b|B]\([01]\|_\)\+[l|L|n]\?\>" +syntax match resFloat "-\=\<\d\(_\|\d\)*\.\?\(_\|\d\)*\([eE][-+]\=\d\(_\|\d\)*\)\=\>" + +" Module / Constructor +syntax match resModuleOrVariant "\v<[A-Z][A-Za-z0-9_'$]*" +syntax match resModuleChain "\v<[A-Z][A-Za-z0-9_'$]*\." + +" Attribute +syntax match resAttribute "\v\@([a-zA-z][A-Za-z0-9_']*)(\.([a-zA-z])[A-Za-z0-9_']*)*" + +" String +syntax match resUnicodeChar "\v\\u[A-Fa-f0-9]\{4}" contained +syntax match resStringEscapeSeq "\v\\[\\"ntbrf]" contained +syntax match resInterpolatedStringEscapeSeq "\v\\[\\`ntbrf]" contained + +syntax region resString start="\v\"" end="\v\"" contains=resStringEscapeSeq,resUnicodeChar + +" Interpolation +syntax match resInterpolationVariable "\v\$[a-z_][A-Za-z0-0_'$]*" contained +syntax region resInterpolationBlock matchgroup=resInterpolationDelimiters start="\v\$\{" end="\v\}" contained contains=TOP +syntax region resString start="\v`" end="\v`" contains=resInterpolationBlock,resInterpolatedStringEscapeSeq +syntax region resString start="\v[a-z]`" end="\v`" contains=resInterpolationBlock,resInterpolationVariable,resInterpolatedStringEscapeSeq + +" Polymorphic variants +syntax match resPolyVariant "\v#[A-za-z][A-Za-z0-9_'$]*" +syntax match resPolyVariant "\v#[0-9]+" +syntax match resPolyVariant "\v#\".*\"" +syntax match resPolyVariant "\v#\\\".*\"" + +highlight default link resBoolean Boolean +highlight default link resKeyword Keyword +highlight default link resType Type +highlight default link resOperator Operator +highlight default link resArrowPipe Operator +highlight default link resSingleLineComment Comment +highlight default link resMultiLineComment Comment +highlight default link resTodo TODO +highlight default link resChar Character +highlight default link resNumber Number +highlight default link resFloat Float +highlight default link resModuleOrVariant Function +highlight default link resPolyVariant Function +highlight default link resModuleChain Macro +highlight default link resUnicodeChar Character +highlight default link resStringEscapeSeq Character +highlight default link resInterpolatedStringEscapeSeq Character +highlight default link resString String +highlight default link resInterpolationDelimiters Macro +highlight default link resInterpolationVariable Macro +highlight default link resAttribute PreProc + +let b:current_syntax = "rescript" diff --git a/after/syntax/templ.vim b/after/syntax/templ.vim new file mode 100644 index 0000000..bc149e8 --- /dev/null +++ b/after/syntax/templ.vim @@ -0,0 +1,56 @@ +" Vim syntax file +" Language: templ + +" syn region templCss start=/{% css .* %}/ keepend end=/{% endcss %}/ contains=cssTagName,cssAttributeSelector,cssClassName,cssIdentifier,cssAtRule,cssAttrRegion,css.*Prop,cssComment,cssValue.*,cssColor,cssURL,cssImportant,cssCustomProp,cssError,cssStringQ,cssStringQQ,cssFunction,cssUnicodeEscape,cssVendor,cssDefinition,cssHacks,cssNoise + +if version < 600 + syn clear +elseif exists("b:current_syntax") + finish +endif + +if !exists("main_syntax") + let main_syntax = 'templ' +endif + +syn include @html syntax/html.vim +unlet b:current_syntax + +syn include @css syntax/css.vim +unlet b:current_syntax + +syn include @go syntax/go.vim +unlet b:current_syntax + +runtime! syntax/go.vim +unlet b:current_syntax + +" templ +syn match templTemplateDec /^templ/ nextgroup=templReceiverDecl,templFunction skipwhite skipnl +syn match templReceiverDecl /(\s*\zs\%(\%(\w\+\s\+\)\?\*\?\w\+\%(\[\%(\%(\[\]\)\?\w\+\%(,\s*\)\?\)\+\]\)\?\)\ze\s*)/ contained contains=goReceiverVar,goReceiverType,goPointerOperator nextgroup=templFunction skipwhite skipnl +syn match templFunction /\w\+/ nextgroup=templSimpleParams,templTypeParams contained skipwhite skipnl +syn match templSimpleParams /(\%(\w\|\_s\|[*\.\[\],\{\}<>-]\)*)/ contained contains=goParamName,goType nextgroup=templTemplateBlock skipwhite skipnl +syn match templTypeParams /\[\%(\w\+\s\+\%(\~\?\%(\[]\)\?\w\%(\w\||\)\)*\%(,\s*\)\?\)\+\]/ nextgroup=templSimpleParams contained skipwhite skipnl +syn region templTemplateBlock start="{" end="}" contains=@html,templCall,@templFlows,templTemplateBlock contained skipwhite skipnl + +" @template() +syn match templCall /@/ nextgroup=templFunction contained skipwhite skipnl + + +" for ... +syn cluster templFlows contains=templFlow,templSwitch +syn match templFlow /\(for\|if\)/ contained skipwhite skipnl nextgroup=templFlowBlock +syn match templSwitch /switch/ contained skipwhite skipnl nextgroup=templSwitchBlock +syn keyword templSwitchKeyword case contained +syn keyword templSwitchKeyword default contained +syn region templSwitchBlock start=/.*{/ end="}" contained skipwhite skipnl contains=@html,templCall,@templFlows,templSwitchKeyword +syn region templFlowBlock start=/.*{/ end="}" contained skipwhite skipnl contains=@html,templCall,@templFlows + + +hi def link templTemplateDec Keyword +hi def link templFlow Keyword +hi def link templSwitch Keyword +hi def link templSwitchKeyword Keyword +hi def link templCall Special + +let b:current_syntax = "templ" diff --git a/after/syntax/typst.vim b/after/syntax/typst.vim new file mode 100644 index 0000000..4e39a6e --- /dev/null +++ b/after/syntax/typst.vim @@ -0,0 +1,407 @@ +" Vim syntax file +" Language: Typst +" Maintainer: Kaj Munhoz Arfvidsson +" Latest Revision: Apr 2023 + +if exists("b:current_syntax") + finish +endif + +syntax sync fromstart +syntax spell toplevel + +" Common {{{1 +syntax cluster typstCommon + \ contains=@typstComment + +" Common > Comment {{{2 +syntax cluster typstComment + \ contains=typstCommentBlock,typstCommentLine +syntax match typstCommentBlock + \ #/\*\%(\_.\{-}\)\*/# + \ contains=typstCommentTodo,@Spell +syntax match typstCommentLine + \ #//.*# + \ contains=typstCommentTodo,@Spell +syntax keyword typstCommentTodo + \ contained + \ TODO FIXME XXX TBD + + +" Code {{{1 +syntax cluster typstCode + \ contains=@typstCommon + \ ,@typstCodeKeywords + \ ,@typstCodeConstants + \ ,@typstCodeIdentifiers + \ ,@typstCodeFunctions + \ ,@typstCodeParens + +" Code > Keywords {{{2 +syntax cluster typstCodeKeywords + \ contains=typstCodeConditional + \ ,typstCodeRepeat + \ ,typstCodeKeyword + \ ,typstCodeStatement +syntax keyword typstCodeConditional + \ contained + \ if else +syntax keyword typstCodeRepeat + \ contained + \ while for +syntax keyword typstCodeKeyword + \ contained + \ not in and or return +syntax region typstCodeStatement + \ contained + \ matchgroup=typstCodeStatementWord start=/\v(let|set|show|import|include)>-@!/ end=/\v%(;|$)/ + \ contains=@typstCode + +" Code > Identifiers- {{{2 +syntax cluster typstCodeIdentifiers + \ contains=typstCodeIdentifier + \ ,typstCodeFieldAccess +syntax match typstCodeIdentifier + \ contained + \ /\v\K\k*%(-+\k+)*>-@!(<%(let|set|show|import|include))@-@!(<%(let|set|show|import|include))@ Functions {{{2 +syntax cluster typstCodeFunctions + \ contains=typstCodeFunction +syntax match typstCodeFunction + \ contained + \ /\v\K\k*%(-+\k+)*[\(\[]@=/ + \ nextgroup=typstCodeFunctionArgument +syntax match typstCodeFunctionArgument + \ contained + \ /\v%(%(\(.{-}\)|\[.{-}\]|\{.{-}\}))*/ transparent + \ contains=@typstCode + +" Code > Constants {{{2 +syntax cluster typstCodeConstants + \ contains=typstCodeConstant + \ ,typstCodeNumberInteger + \ ,typstCodeNumberFloat + \ ,typstCodeNumberLength + \ ,typstCodeNumberAngle + \ ,typstCodeNumberRatio + \ ,typstCodeNumberFraction + \ ,typstCodeString +syntax match typstCodeConstant + \ contained + \ /\v<%(none|auto|true|false)-@!>/ +syntax match typstCodeNumberInteger + \ contained + \ /\v<\d+>/ + +syntax match typstCodeNumberFloat + \ contained + \ /\v<\d+\.\d*>/ +syntax match typstCodeNumberLength + \ contained + \ /\v<\d+(\.\d*)?(pt|mm|cm|in|em)>/ +syntax match typstCodeNumberAngle + \ contained + \ /\v<\d+(\.\d*)?(deg|rad)>/ +syntax match typstCodeNumberRatio + \ contained + \ /\v<\d+(\.\d*)?\%/ +syntax match typstCodeNumberFraction + \ contained + \ /\v<\d+(\.\d*)?fr>/ +syntax region typstCodeString + \ contained + \ start=/"/ skip=/\v\\\\|\\"/ end=/"/ + \ contains=@Spell + +" Code > Parens {{{2 +syntax cluster typstCodeParens + \ contains=typstCodeParen + \ ,typstCodeBrace + \ ,typstCodeBracket + \ ,typstCodeDollar + \ ,typstMarkupRawInline + \ ,typstMarkupRawBlock +syntax region typstCodeParen + \ contained + \ matchgroup=Noise start=/\v\(/ end=/\v\)/ + \ contains=@typstCode +syntax region typstCodeBrace + \ contained + \ matchgroup=Noise start=/\v\{/ end=/\v\}/ + \ contains=@typstCode +syntax region typstCodeBracket + \ contained + \ matchgroup=Noise start=/\v\[/ end=/\v\]/ + \ contains=@typstMarkup +syntax region typstCodeDollar + \ contained + \ matchgroup=Number start=/\v\$/ end=/\v\$/ + \ contains=@typstMath + + +" Hashtag {{{1 +syntax cluster typstHashtag + \ contains=@typstHashtagKeywords + \ ,@typstHashtagConstants + \ ,@typstHashtagIdentifiers + \ ,@typstHashtagFunctions + \ ,@typstHashtagParens + +" Hashtag > Keywords {{{2 +syntax cluster typstHashtagKeywords + \ contains=typstHashtagConditional + \ ,typstHashtagRepeat + \ ,typstHashtagKeywords + \ ,typstHashtagStatement + +syntax match typstHashtagControlFlowError + \ /\v#%(if|while|for)>-@!.{-}$\_.{-}%(\{|\[)/ +syntax match typstHashtagControlFlow + \ /\v#%(if|while|for)>-@!.{-}\ze%(\{|\[)/ + \ contains=typstHashtagConditional,typstHashtagRepeat + \ nextgroup=@typstCode +syntax region typstHashtagConditional + \ contained + \ start=/\v#if>-@!/ end=/\v\ze(\{|\[)/ + \ contains=@typstCode +syntax region typstHashtagRepeat + \ contained + \ start=/\v#(while|for)>-@!/ end=/\v\ze(\{|\[)/ + \ contains=@typstCode +syntax match typstHashtagKeyword + \ /\v#(return)>-@!/ + \ skipwhite nextgroup=@typstCode +syntax region typstHashtagStatement + \ matchgroup=typstHashtagStatementWord start=/\v#(let|set|show|import|include)>-@!/ end=/\v%(;|$)/ + \ contains=@typstCode + +" Hashtag > Constants {{{2 +syntax cluster typstHashtagConstants + \ contains=typstHashtagConstant +syntax match typstHashtagConstant + \ /\v#(none|auto|true|false)>-@!/ + +" Hashtag > Identifiers {{{2 +syntax cluster typstHashtagIdentifiers + \ contains=typstHashtagIdentifier + \ ,typstHashtagFieldAccess +syntax match typstHashtagIdentifier + \ /\v#\K\k*%(-+\k+)*>-@!(<%(let|set|show|import|include|if|while|for|return))@-@!(<%(let|set|show|import|include|if|while|for|return))@ Functions {{{2 +syntax cluster typstHashtagFunctions + \ contains=typstHashtagFunction +syntax match typstHashtagFunction + \ /\v#\K\k*%(-+\k+)*[\(\[]@=/ + \ nextgroup=typstCodeFunctionArgument + +" Hashtag > Parens {{{2 +syntax cluster typstHashtagParens + \ contains=typstHashtagParen + \ ,typstHashtagBrace + \ ,typstHashtagBracket + \ ,typstHashtagDollar +syntax region typstHashtagParen + \ matchgroup=Noise start=/\v\#\(/ end=/\v\)/ + \ contains=@typstCode +syntax region typstHashtagBrace + \ matchgroup=Noise start=/\v\#\{/ end=/\v\}/ + \ contains=@typstCode +syntax region typstHashtagBracket + \ matchgroup=Noise start=/\v\#\[/ end=/\v\]/ + \ contains=@typstMarkup +syntax region typstHashtagDollar + \ matchgroup=Noise start=/\v\#\$/ end=/\v\$/ + \ contains=@typstMath + + +" Markup {{{1 +syntax cluster typstMarkup + \ contains=@typstCommon + \ ,@Spell + \ ,@typstHashtag + \ ,@typstMarkupText + \ ,@typstMarkupParens + +" Markup > Text {{{2 +syntax cluster typstMarkupText + \ contains=typstMarkupRawInline + \ ,typstMarkupRawBlock + \ ,typstMarkupLabel + \ ,typstMarkupReference + \ ,typstMarkupUrl + \ ,typstMarkupHeading + \ ,typstMarkupBulletList + \ ,typstMarkupEnumList + \ ,typstMarkupBold + \ ,typstMarkupItalic + \ ,typstMarkupLinebreak + \ ,typstMarkupNonbreakingSpace + \ ,typstMarkupShy + \ ,typstMarkupDash + \ ,typstMarkupEllipsis + \ ,typstMarkupTermList + +syntax match typstMarkupRawInline + \ /`.\{-}`/ + +syntax region typstMarkupRawBlock + \ matchgroup=Macro start=/```\w*/ + \ matchgroup=Macro end=/```/ keepend +syntax region typstCodeBlock + \ matchgroup=Macro start=/```typst/ + \ matchgroup=Macro end=/```/ contains=@typstCode keepend +syntax include @C syntax/c.vim +syntax region typstMarkupCCodeBlock + \ matchgroup=Macro start=/```c\>/ + \ matchgroup=Macro end=/```/ contains=@C keepend +syntax include @CPP syntax/cpp.vim +syntax region typstMarkupCPPCodeBlock + \ matchgroup=Macro start=/```cpp/ + \ matchgroup=Macro end=/```/ contains=@CPP keepend +syntax include @Python syntax/python.vim +syntax region typstMarkupPythonCodeBlock + \ matchgroup=Macro start=/```python/ + \ matchgroup=Macro end=/```/ contains=@Python keepend + +syntax match typstMarkupLabel + \ /\v\<\K%(\k*-*)*\>/ +syntax match typstMarkupReference + \ /\v\@\K%(\k*-*)*/ +syntax match typstMarkupUrl + \ /http[s]\?:\/\/[[:alnum:]%\/_#.-]*/ +syntax match typstMarkupHeading + \ /^\s*\zs=\{1,6}\s.*$/ + \ contains=typstMarkupLabel,@Spell +syntax match typstMarkupBulletList + \ /\v^\s*-\s+/ +syntax match typstMarkupEnumList + \ /\v^\s*(\+|\d+\.)\s+/ +syntax match typstMarkupItalicError + \ /\v(\w|\\)@ Parens {{{2 +syntax cluster typstMarkupParens + \ contains=typstMarkupDollar +syntax region typstMarkupDollar + \ matchgroup=Special start=/\$/ skip=/\\\$/ end=/\$/ + \ contains=@typstMath + + +" Math {{{1 +syntax cluster typstMath + \ contains=@typstCommon + \ ,@typstHashtag + \ ,typstMathFunction + \ ,typstMathNumber + \ ,typstMathSymbol + +syntax match typstMathFunction + \ /\<\v\zs\a\w+\ze\(/ + \ contained +syntax match typstMathNumber + \ /\<\d\+\>/ + \ contained + +" Math > Linked groups {{{2 +highlight default link typstMathFunction Statement +highlight default link typstMathNumber Number +highlight default link typstMathSymbol Statement + +" Highlighting {{{1 + +" Highlighting > Linked groups {{{2 +highlight default link typstCommentBlock Comment +highlight default link typstCommentLine Comment +highlight default link typstCommentTodo Todo +highlight default link typstCodeConditional Conditional +highlight default link typstCodeRepeat Repeat +highlight default link typstCodeKeyword Keyword +highlight default link typstCodeConstant Constant +highlight default link typstCodeNumberInteger Number +highlight default link typstCodeNumberFloat Number +highlight default link typstCodeNumberLength Number +highlight default link typstCodeNumberAngle Number +highlight default link typstCodeNumberRatio Number +highlight default link typstCodeNumberFraction Number +highlight default link typstCodeString String +highlight default link typstCodeStatementWord Statement +highlight default link typstCodeIdentifier Identifier +highlight default link typstCodeFieldAccess Identifier +highlight default link typstCodeFunction Function +highlight default link typstCodeParen Noise +highlight default link typstCodeBrace Noise +highlight default link typstCodeBracket Noise +highlight default link typstCodeDollar Noise +highlight default link typstHashtagControlFlowError Error +highlight default link typstHashtagConditional Conditional +highlight default link typstHashtagRepeat Repeat +highlight default link typstHashtagKeyword Keyword +highlight default link typstHashtagConstant Constant +highlight default link typstHashtagStatementWord Statement +highlight default link typstHashtagIdentifier Identifier +highlight default link typstHashtagFieldAccess Identifier +highlight default link typstHashtagFunction Function +highlight default link typstHashtagParen Noise +highlight default link typstHashtagBrace Noise +highlight default link typstHashtagBracket Noise +highlight default link typstHashtagDollar Noise +highlight default link typstMarkupRawInline Macro +highlight default link typstMarkupRawBlock Macro +highlight default link typstMarkupLabel Structure +highlight default link typstMarkupReference Structure +highlight default link typstMarkupBulletList Structure +highlight default link typstMarkupItalicError Error +highlight default link typstMarkupEnumList Structure +highlight default link typstMarkupLinebreak Structure +highlight default link typstMarkupNonbreakingSpace Structure +highlight default link typstMarkupShy Structure +highlight default link typstMarkupDash Structure +highlight default link typstMarkupEllipsis Structure +highlight default link typstMarkupTermList Structure +highlight default link typstMarkupDollar Noise + +" Highlighting > Custom Styling {{{2 +highlight default typstMarkupHeading term=underline,bold cterm=underline,bold gui=underline,bold +highlight default typstMarkupUrl term=underline cterm=underline gui=underline +highlight default typstMarkupBold term=bold cterm=bold gui=bold +highlight default typstMarkupItalicRegion term=italic cterm=italic gui=italic + +highlight default link typstMarkupBoldDelimiter typstMarkupBold +highlight default link typstMarkupItalicDelimiter typstMarkupItalic + +" }}}1 + +let b:current_syntax = "typst" + +" vim: foldlevel=0 tabstop=8 shiftwidth=4 softtabstop=4 expandtab diff --git a/colors/nord.lua b/colors/nord.lua new file mode 100644 index 0000000..ae40a96 --- /dev/null +++ b/colors/nord.lua @@ -0,0 +1 @@ +require('user.util.color').load_theme('nord') diff --git a/colors/onedark.lua b/colors/onedark.lua new file mode 100644 index 0000000..3ad88d7 --- /dev/null +++ b/colors/onedark.lua @@ -0,0 +1 @@ +require('user.util.color').load_theme('onedark') diff --git a/filetype.lua b/filetype.lua new file mode 100644 index 0000000..901d6b7 --- /dev/null +++ b/filetype.lua @@ -0,0 +1,54 @@ +vim.filetype.add { + filename = { + vifmrc = 'vim', + ['.ansible-lint'] = 'yaml', + ['.yamllint'] = 'yaml', + ['ansible.cfg'] = 'ini', + ['BUCK'] = 'bzl', + ['flake.lock'] = 'json', + ['go.mod'] = 'gomod', + ['mopidy.conf'] = 'cfg', + ['mpDris2.conf'] = 'cfg', + ['mpd.conf'] = 'conf', + ['terraform.tfstate'] = 'json', + }, + extension = { + http = 'http', + hurl = 'hurl', + k = 'kcl', + mmd = 'mermaid', + ncl = 'nickel', + nft = 'nftables', + river = 'hcl', + templ = 'templ', + tmpl = 'gohtmltmpl', + typ = 'typst', + vifm = 'vim', + -- Jinja2 + j2 = 'jinja', + jinja = 'jinja', + jinja2 = 'jinja', + -- Rescript + res = 'rescript', + resi = 'rescript', + }, + pattern = { + ['${HOME}/.kube/config'] = 'yaml', + -- Ansible + ['.*/roles/.*/tasks/.*%.ya?ml'] = 'yaml.ansible', + ['.*/roles/.*/handlers/.*%.ya?ml'] = 'yaml.ansible', + ['.*/playbooks/.*%.ya?ml'] = 'yaml.ansible', + ['playbook.ya?ml'] = 'yaml.ansible', + -- Waybar config + ['${XDG_CONFIG_HOME}/waybar/config'] = 'jsonc', + -- Rofi style (not really CSS, but similar) + ['${XDG_CONFIG_HOME}/rofi/.*%.rasi'] = 'css', + ['${XDG_CONFIG_HOME}/rofi/.*%.rasinc'] = 'css', + ['${XDG_CONFIG_HOME}/rofi/themes/.*%.rasi'] = 'css', + ['${XDG_CONFIG_HOME}/rofi/themes/.*%.rasinc'] = 'css', + -- please.build + ['%.?plzconfig'] = 'gitconfig', + ['%.plzconfig_[%w_]+'] = 'gitconfig', + ['%.plzconfig.[%w_%-]+'] = 'gitconfig', + }, +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..6cf9c39 --- /dev/null +++ b/go.mod @@ -0,0 +1,25 @@ +module nvim + +go 1.20 + +require ( + github.com/go-playground/validator/v10 v10.15.5 + github.com/goccy/go-yaml v1.11.2 + github.com/magefile/mage v1.15.0 + github.com/sirupsen/logrus v1.9.3 +) + +require ( + github.com/fatih/color v1.10.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.2 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/leodido/go-urn v1.2.4 // indirect + github.com/mattn/go-colorable v0.1.8 // indirect + github.com/mattn/go-isatty v0.0.12 // indirect + golang.org/x/crypto v0.7.0 // indirect + golang.org/x/net v0.8.0 // indirect + golang.org/x/sys v0.6.0 // indirect + golang.org/x/text v0.8.0 // indirect + golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..003055c --- /dev/null +++ b/go.sum @@ -0,0 +1,54 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg= +github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= +github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= +github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.15.5 h1:LEBecTWb/1j5TNY1YYG2RcOUN3R7NLylN+x8TTueE24= +github.com/go-playground/validator/v10 v10.15.5/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= +github.com/goccy/go-yaml v1.11.2 h1:joq77SxuyIs9zzxEjgyLBugMQ9NEgTWxXfz2wVqwAaQ= +github.com/goccy/go-yaml v1.11.2/go.mod h1:wKnAMd44+9JAAnGQpWVEgBzGt3YuTaQ4uXoHvE4m7WU= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= +github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= +github.com/magefile/mage v1.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg= +github.com/magefile/mage v1.15.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= +github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= +github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A= +golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= +golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/init.lua b/init.lua new file mode 100644 index 0000000..fd7053c --- /dev/null +++ b/init.lua @@ -0,0 +1,35 @@ +-- Configurations +require('user.config.options') + +-- Load plugins with lazy.nvim +require('user.config.lazy') + +-- Keymaps and Autocmds can wait +vim.api.nvim_create_autocmd('User', { + pattern = 'VeryLazy', + once = true, + callback = function() + require('user.config.autocmds') + require('user.config.keymap') + end, + group = vim.api.nvim_create_augroup('UserLazyConfig', { clear = true }), +}) + +-- Open nvim-tree on directory at start up +-- NOTE: the other autocmds are lazy-loaded, so this one can't be there +vim.api.nvim_create_autocmd('VimEnter', { + pattern = '*', + callback = function(args) + if vim.fn.isdirectory(args.file) == 1 then + -- create a new, empty buffer + vim.cmd.enew() + -- wipe the directory buffer + vim.cmd.bw(args.buf) + -- change to the directory + vim.cmd.cd(args.file) + -- open the tree (it will be autoloaded by lazy.nvim here) + require('nvim-tree.api').tree.open() + end + end, + group = vim.api.nvim_create_augroup('NvimTreeStartup', { clear = true }), +}) diff --git a/lazy-lock.json b/lazy-lock.json new file mode 100644 index 0000000..a0e04b3 --- /dev/null +++ b/lazy-lock.json @@ -0,0 +1,101 @@ +{ + "LuaSnip": { "branch": "master", "commit": "80a8528f084a97b624ae443a6f50ff8074ba486b" }, + "SchemaStore.nvim": { "branch": "main", "commit": "976e7e3e9c400b99f1cb337b2ce804a9652571ec" }, + "aerial.nvim": { "branch": "master", "commit": "3a3baf0930444c78d19964fdb401bd3a6a23270f" }, + "alpha-nvim": { "branch": "main", "commit": "234822140b265ec4ba3203e3e0be0e0bb826dff5" }, + "better-escape.nvim": { "branch": "master", "commit": "7031dc734add47bb71c010e0551829fa5799375f" }, + "carbon-now.nvim": { "branch": "main", "commit": "3caa535a6216a8f3152708ae0fe6087b76e58639" }, + "ccc.nvim": { "branch": "main", "commit": "cd2a10557b4a13e80929f59a7076ae792b2c60fa" }, + "cmp-buffer": { "branch": "main", "commit": "3022dbc9166796b644a841a02de8dd1cc1d311fa" }, + "cmp-dap": { "branch": "master", "commit": "d16f14a210cd28988b97ca8339d504533b7e09a4" }, + "cmp-fish": { "branch": "main", "commit": "8ae7bfb1b3251d433c4adf4e64396ef929fbd91f" }, + "cmp-nvim-lsp": { "branch": "main", "commit": "44b16d11215dce86f253ce0c30949813c0a90765" }, + "cmp-path": { "branch": "main", "commit": "91ff86cd9c29299a64f968ebb45846c485725f23" }, + "cmp_luasnip": { "branch": "master", "commit": "05a9ab28b53f71d1aece421ef32fee2cb857a843" }, + "conform.nvim": { "branch": "master", "commit": "253878436e2b6d73dfd91ccf0ac12d04cc683d34" }, + "dial.nvim": { "branch": "master", "commit": "019bbe9daea397c93a99adc747f8f071379fee5c" }, + "diffview.nvim": { "branch": "main", "commit": "d38c1b5266850f77f75e006bcc26213684e1e141" }, + "dressing.nvim": { "branch": "master", "commit": "1f2d1206a03bd3add8aedf6251e4534611de577f" }, + "emmylua-nvim": { "branch": "master", "commit": "306432bb1179efc504ba3403a748c4967e9e8e0c" }, + "flash.nvim": { "branch": "main", "commit": "48817af25f51c0590653bbc290866e4890fe1cbe" }, + "friendly-snippets": { "branch": "main", "commit": "43727c2ff84240e55d4069ec3e6158d74cb534b6" }, + "git-conflict.nvim": { "branch": "main", "commit": "896261933afe2fddf6fb043d9cd4d88301b151a9" }, + "gitsigns.nvim": { "branch": "main", "commit": "af0f583cd35286dd6f0e3ed52622728703237e50" }, + "headlines.nvim": { "branch": "master", "commit": "74a083a3c32a08be24f7dfcc6f448ecf47857f46" }, + "heirline.nvim": { "branch": "master", "commit": "7f1e805dfc001d5dbb7d894105063f463f6c7dcc" }, + "inc-rename.nvim": { "branch": "main", "commit": "ed0f6f2b917cac4eb3259f907da0a481b27a3b7e" }, + "indent-blankline.nvim": { "branch": "master", "commit": "64fdadb3ada61c4297452f7662789b01ee82ffcb" }, + "iswap.nvim": { "branch": "master", "commit": "e05889db694524f68eb69567a70a98470994b890" }, + "lazy.nvim": { "branch": "main", "commit": "16603c6917435d8446f7357cb61095138a417085" }, + "lsp_lines.nvim": { "branch": "main", "commit": "9e3f99fbbd28aaec80dc0158c43be8cca8dd5017" }, + "markdown-preview.nvim": { "branch": "master", "commit": "a923f5fc5ba36a3b17e289dc35dc17f66d0548ee" }, + "mini.ai": { "branch": "main", "commit": "fc9ce93eb8d58ce1a39101928fb5ea8f8b97844c" }, + "mini.align": { "branch": "main", "commit": "e3a0c114d3fea0479f8e8ee3e273c7ca4ba6ea8d" }, + "mini.animate": { "branch": "main", "commit": "6e6f2ee99fec91985d3817bfc74fbb4a0aff1c44" }, + "mini.bufremove": { "branch": "main", "commit": "f53c7f27e36009fe61563c11cde154b94a0e5b94" }, + "mini.comment": { "branch": "main", "commit": "e4320af992cd053a7da2f33d9f63a86c2ab6ce59" }, + "mini.move": { "branch": "main", "commit": "b3bca8e493f8d1da343ff1be06c21a47e2fde1c0" }, + "mini.pairs": { "branch": "main", "commit": "6f6bd7ed5757b40bc29c73dac0d743e4e6978124" }, + "mini.surround": { "branch": "main", "commit": "862cfaef72d789d320625ec34e2488a5cc8fd423" }, + "neoconf.nvim": { "branch": "main", "commit": "00dcf2b81c45de1768b4171faa16729f0888cfb8" }, + "neogen": { "branch": "main", "commit": "70127baaff25611deaf1a29d801fc054ad9d2dc1" }, + "neogit": { "branch": "master", "commit": "05899be06d611c485cfb6e17ef6e3b695de67b62" }, + "neotest": { "branch": "master", "commit": "901891484db3d46ce43d56871273dc7d40621356" }, + "neotest-go": { "branch": "main", "commit": "1a15e1136db43775214a3e7a598f8930c29c94b7" }, + "neotest-jest": { "branch": "main", "commit": "65ab61c77aa1c245f16982ffe1a4d31589e18023" }, + "neotest-python": { "branch": "master", "commit": "81d2265efac717bb567bc15cc652ae10801286b3" }, + "neotest-vitest": { "branch": "main", "commit": "41bf2f6b743f2ac5c5e9bd0949cee77ca7f2372c" }, + "neotest-zig": { "branch": "main", "commit": "f546293bdb85ae1ff872d617a54eefa65479fd8f" }, + "noice.nvim": { "branch": "main", "commit": "92433164e2f7118d4122c7674c3834d9511722ba" }, + "nui.nvim": { "branch": "main", "commit": "c0c8e347ceac53030f5c1ece1c5a5b6a17a25b32" }, + "nvim-cmp": { "branch": "main", "commit": "51260c02a8ffded8e16162dcf41a23ec90cfba62" }, + "nvim-cokeline": { "branch": "main", "commit": "2e71292a37535fdbcf0f9500aeb141021d90af8b" }, + "nvim-coverage": { "branch": "main", "commit": "13e357d4a2a230070c64ede61252225f0b7d3b5e" }, + "nvim-dap": { "branch": "master", "commit": "79dbc70eb79271ad801e4ff293887cde324c28d0" }, + "nvim-dap-ui": { "branch": "master", "commit": "34160a7ce6072ef332f350ae1d4a6a501daf0159" }, + "nvim-dap-virtual-text": { "branch": "master", "commit": "57f1dbd0458dd84a286b27768c142e1567f3ce3b" }, + "nvim-lint": { "branch": "master", "commit": "962a76877a4479a535b935bd7ef35ad41ba308b2" }, + "nvim-lspconfig": { "branch": "master", "commit": "cb49a4cd622d7ed775c31561bb8aa6a50712d6b9" }, + "nvim-navic": { "branch": "master", "commit": "0ffa7ffe6588f3417e680439872f5049e38a24db" }, + "nvim-notify": { "branch": "master", "commit": "e4a2022f4fec2d5ebc79afa612f96d8b11c627b3" }, + "nvim-scrollbar": { "branch": "main", "commit": "35f99d559041c7c0eff3a41f9093581ceea534e8" }, + "nvim-spectre": { "branch": "master", "commit": "696cff781a4a7ecc91549736835e2acbd00fe859" }, + "nvim-tree.lua": { "branch": "master", "commit": "78a9ca5ed6557f29cd0ce203df44213e54bfabb9" }, + "nvim-treesitter": { "branch": "master", "commit": "bf0a96568a54618138ac42c84758945cdafef86b" }, + "nvim-treesitter-context": { "branch": "master", "commit": "2806d83e3965017382ce08792ee527e708fa1bd4" }, + "nvim-treesitter-textobjects": { "branch": "master", "commit": "e69a504baf2951d52e1f1fbb05145d43f236cbf1" }, + "nvim-ts-autotag": { "branch": "main", "commit": "6be1192965df35f94b8ea6d323354f7dc7a557e4" }, + "nvim-ts-context-commentstring": { "branch": "main", "commit": "92e688f013c69f90c9bbd596019ec10235bc51de" }, + "nvim-web-devicons": { "branch": "master", "commit": "5de460ca7595806044eced31e3c36c159a493857" }, + "orgmode": { "branch": "master", "commit": "a6fc6b224188c519fa4e9c0beddd5f3b22575d6e" }, + "overseer.nvim": { "branch": "master", "commit": "95bd2d45af543238e25919ad2d9793a8cf61ac38" }, + "pantran.nvim": { "branch": "main", "commit": "461799624948bfb66f73b20b6fffa7e4c8ca6d08" }, + "parinfer-rust": { "branch": "master", "commit": "0e4d52e712641ad351a1bfe6cee3d34d63ed087b" }, + "persistence.nvim": { "branch": "main", "commit": "ad538bfd5336f1335cdb6fd4e0b0eebfa6e12f32" }, + "plenary.nvim": { "branch": "master", "commit": "50012918b2fc8357b87cff2a7f7f0446e47da174" }, + "project.nvim": { "branch": "main", "commit": "8c6bad7d22eef1b71144b401c9f74ed01526a4fb" }, + "rainbow-delimiters.nvim": { "branch": "master", "commit": "9fda1322e704acfbb5a9691e2c4303368d591367" }, + "rest.nvim": { "branch": "main", "commit": "b8d6c0a8762fd374e0204768a94241135ca3e311" }, + "scope.nvim": { "branch": "main", "commit": "01ce40fe7434fba9a122c01b4734479b3860609a" }, + "sniprun": { "branch": "master", "commit": "0ad935699a5e24ca3a0aa621c7425ac7029775b3" }, + "ssr.nvim": { "branch": "main", "commit": "bb323ba621ac647b4ac5638b47666e3ef3c279e1" }, + "stickybuf.nvim": { "branch": "master", "commit": "f3398f8639e903991acdf66e2d63de7a78fe708e" }, + "telescope-file-browser.nvim": { "branch": "master", "commit": "6e51d0cd6447cf2525412220ff0a2885eef9039c" }, + "telescope-fzf-native.nvim": { "branch": "main", "commit": "6c921ca12321edaa773e324ef64ea301a1d0da62" }, + "telescope-symbols.nvim": { "branch": "master", "commit": "f2060117d965df4a626f068a4ebbd8ee051aa076" }, + "telescope-tabs": { "branch": "master", "commit": "a38c8fee471257a0ff233e065c4a0d123f8e67b0" }, + "telescope.nvim": { "branch": "master", "commit": "4522d7e3ea75ffddabdc39957168a8a7060b5df0" }, + "todo-comments.nvim": { "branch": "main", "commit": "4a6737a8d70fe1ac55c64dfa47fcb189ca431872" }, + "toggleterm.nvim": { "branch": "main", "commit": "c80844fd52ba76f48fabf83e2b9f9b93273f418d" }, + "treesj": { "branch": "main", "commit": "070e6761d0b11a55446d988a69908f7a0928dbab" }, + "trouble.nvim": { "branch": "main", "commit": "f1168feada93c0154ede4d1fe9183bf69bac54ea" }, + "twilight.nvim": { "branch": "main", "commit": "8b7b50c0cb2dc781b2f4262a5ddd57571556d1e4" }, + "undotree": { "branch": "master", "commit": "3ff3aa02fd3a18d7fc5be9ed3166d36c545707fe" }, + "venn.nvim": { "branch": "main", "commit": "c114563960b8fb1197695d42798d1f3e7190b798" }, + "vim-log-highlighting": { "branch": "master", "commit": "1037e26f3120e6a6a2c0c33b14a84336dee2a78f" }, + "vim-matchup": { "branch": "master", "commit": "6dbe108230c7dbbf00555b7d4d9f6a891837ef07" }, + "vim-repeat": { "branch": "master", "commit": "24afe922e6a05891756ecf331f39a1f6743d3d5a" }, + "vim-startuptime": { "branch": "master", "commit": "454b3de856b7bd298700de33d79774ca9b9e3875" }, + "which-key.nvim": { "branch": "main", "commit": "4433e5ec9a507e5097571ed55c02ea9658fb268a" }, + "zen-mode.nvim": { "branch": "main", "commit": "50e2e2a36cc97847d9ab3b1a3555ba2ef6839b50" } +} \ No newline at end of file diff --git a/lua/user/config/autocmds.lua b/lua/user/config/autocmds.lua new file mode 100644 index 0000000..2f55882 --- /dev/null +++ b/lua/user/config/autocmds.lua @@ -0,0 +1,118 @@ +local group = vim.api.nvim_create_augroup('UserAutoCmds', { clear = true }) +local function close_with_q(buf) + vim.bo[buf].buflisted = false + vim.keymap.set('n', 'q', 'close', { buffer = buf, silent = true }) +end + +-- Close some filetypes with and auto delete buffer for them +vim.api.nvim_create_autocmd('FileType', { + pattern = { + 'qf', + 'help', + 'man', + 'query', + 'notify', + 'spectre_panel', + 'neotest-output', + 'neotest-output-panel', + 'neotest-summary', + 'startuptime', + 'PlenaryTestPopup', + }, + callback = function(args) + close_with_q(args.buf) + end, + group = group, +}) +vim.api.nvim_create_autocmd('FileType', { + pattern = { 'checkhealth', 'log' }, + callback = function(args) + -- Only do this to Neovim related log files + if vim.api.nvim_buf_get_option(args.buf, 'filetype') == 'log' + and vim.fn.expand('%:p:h') ~= vim.fn.stdpath('log') + then + return + end + vim.bo[args.buf].bufhidden = 'delete' + close_with_q(args.buf) + end, + group = group, +}) +vim.api.nvim_create_autocmd('FileType', { + pattern = { 'gitcommit', 'gitrebase' }, + callback = function(args) + vim.bo[args.buf].bufhidden = 'wipe' + vim.opt_local.colorcolumn = { 80 } + end, + group = group, +}) + +-- Show cursor line only in active window +vim.api.nvim_create_autocmd('WinEnter', { + callback = function() + local ok, cl = pcall(vim.api.nvim_win_get_var, 0, 'cursorline') + if ok and cl then + vim.wo.cursorline = true + vim.api.nvim_win_del_var(0, 'cursorline') + end + end, + group = group, +}) +vim.api.nvim_create_autocmd('WinLeave', { + callback = function() + local cl = vim.wo.cursorline + if cl then + vim.api.nvim_win_set_var(0, 'cursorline', cl) + vim.wo.cursorline = false + end + end, + group = group, +}) + +-- No undo history for temporary files +vim.api.nvim_create_autocmd('BufWritePre', { + pattern = { '/tmp/*', 'COMMIT_EDITMSG', 'MERGE_MSG', '*.tmp', '*.bak' }, + callback = function() + vim.opt_local.undofile = false + end, + group = group, +}) + +-- Equalize window sizes when resizing the terminal +vim.api.nvim_create_autocmd('VimResized', { + pattern = '*', + command = 'tabdo wincmd =', + group = group, +}) + +-- Force writing shada file on leaving +vim.api.nvim_create_autocmd('VimLeave', { + pattern = '*', + command = 'if has("nvim") | wshada! | else | wviminfo! | endif', + group = group, +}) + +-- Check if we need to reload the file when it changed +vim.api.nvim_create_autocmd({ 'FocusGained', 'TermClose', 'TermLeave' }, { + pattern = '*', + command = 'checktime', + group = group, +}) + +-- Highlight region on yank +vim.api.nvim_create_autocmd('TextYankPost', { + pattern = '*', + callback = function() + vim.highlight.on_yank { higroup = 'IncSearch', timeout = 300 } + end, + group = group, +}) + +-- Wrap in Telescope's preview buffer +vim.api.nvim_create_autocmd('User', { + pattern = 'TelescopePreviewerLoaded', + callback = function() + vim.opt_local.wrap = true + end, + group = group, +}) diff --git a/lua/user/config/keymap.lua b/lua/user/config/keymap.lua new file mode 100644 index 0000000..c5fa7db --- /dev/null +++ b/lua/user/config/keymap.lua @@ -0,0 +1,88 @@ +local util = require('user.util.misc') +local map = vim.keymap.set + +-- better up/down +map({ 'n', 'x' }, 'j', 'v:count == 0 ? "gj" : "j"', { expr = true, silent = true }) +map({ 'n', 'x' }, 'k', 'v:count == 0 ? "gk" : "k"', { expr = true, silent = true }) + +-- better indenting +map('v', '<', '', '>gv') + +-- remove trailing whitespaces +map('n', 'w', '%s/\\s\\+$//e', { desc = 'Remove whitespaces' }) + +-- Resize window using + arrow keys +map('n', '', 'resize +2', { desc = 'Increase window height' }) +map('n', '', 'resize -2', { desc = 'Decrease window height' }) +map('n', '', 'vertical resize -2', { desc = 'Decrease window width' }) +map('n', '', 'vertical resize +2', { desc = 'Increase window width' }) + +-- H, L instead of 0, $ +-- map({ 'n', 'x', 'o' }, 'H', '0') +-- map({ 'n', 'x', 'o' }, 'L', '$') + +-- Clear search with +map({ 'i', 'n' }, '', 'noh', { desc = 'Escape and clear hlsearch' }) + +-- More intuitive n/N direction +-- https://github.com/mhinz/vim-galore#saner-behavior-of-n-and-n +map('n', 'n', '"Nn"[v:searchforward]', { expr = true, desc = 'Next search result' }) +map('x', 'n', '"Nn"[v:searchforward]', { expr = true, desc = 'Next search result' }) +map('o', 'n', '"Nn"[v:searchforward]', { expr = true, desc = 'Next search result' }) +map('n', 'N', '"nN"[v:searchforward]', { expr = true, desc = 'Prev search result' }) +map('x', 'N', '"nN"[v:searchforward]', { expr = true, desc = 'Prev search result' }) +map('o', 'N', '"nN"[v:searchforward]', { expr = true, desc = 'Prev search result' }) + +-- switching between buffers +if not util.has('nvim-cokeline') then + map('n', ']b', 'bnext', { desc = 'Next buffer' }) + map('n', '[b', 'bprevious', { desc = 'Previous buffer' }) +end + +-- move between quickfix items +if not util.has('trouble.nvim') then + map('n', '[q', vim.cmd.cprev, { desc = 'Previous quickfix' }) + map('n', ']q', vim.cmd.cnext, { desc = 'Next quickfix' }) +end + +-- toggle LSP settings +map('n', 'ud', require('user.plugins.lsp.lspconfig.diagnostic').toggle, { desc = 'Toggle diagnostics' }) +map('n', 'uv', require('user.plugins.lsp.lspconfig.diagnostic').toggle_virtual_lines, { desc = 'Toggle virtual lines diagnostics' }) +map('n', 'uf', require('user.plugins.lsp.lspconfig.format').toggle, { desc = 'Toggle format on save' }) +if vim.lsp.inlay_hint then + map('n', 'uy', function() vim.lsp.inlay_hint(0, nil) end, { desc = 'Toggle inlay hints' }) +end + +-- toggle editor settings +map('n', 'ur', function() util.toggle('relativenumber', true) end, { desc = 'Toggle relativenumber' }) +map('n', 'ue', function() util.toggle('conceallevel', false, { 0, vim.go.conceallevel > 0 and vim.go.conceallevel or 3 }) end, { desc = 'Toggle conceallevel' }) +map('n', 'us', function() util.toggle('expandtab') end, { desc = 'Toggle tab/space indent' }) + +-- exit +map('n', 'qw', 'c', { desc = 'Delete window' }) +map('n', 'qq', 'qa', { desc = 'Quit all' }) + +-- get highlight groups under cursor +map('n', 'up', vim.show_pos, { desc = 'Inspect pos' }) +map('n', 'ut', vim.treesitter.inspect_tree, { desc = 'Inspect treesitter' }) + +-- escape in terminal mode +map('t', '', '', { desc = 'Enter Normal mode' }) + +-- easier window split +map('n', '-', 's', { desc = 'Split window below' }) +map('n', '|', 'v', { desc = 'Split window right' }) + +-- tabs +map('n', 'l', 'tablast', { desc = 'Last tab' }) +map('n', 'f', 'tabfirst', { desc = 'First tab' }) +map('n', '', 'tabnew', { desc = 'New tab' }) +map('n', ']', 'tabnext', { desc = 'Next tab' }) +map('n', 'd', 'tabclose', { desc = 'Close tab' }) +map('n', '[', 'tabprevious', { desc = 'Previous tab' }) +map('n', 'n', '+tabmove', { desc = 'Move tab next' }) +map('n', 'p', '-tabmove', { desc = 'Move tab previous' }) + +-- open Lazy UI +map('n', '', 'Lazy', { desc = 'Lazy' }) diff --git a/lua/user/config/lazy.lua b/lua/user/config/lazy.lua new file mode 100644 index 0000000..c6d0bed --- /dev/null +++ b/lua/user/config/lazy.lua @@ -0,0 +1,59 @@ +local lazypath = vim.fn.stdpath('data') .. '/lazy/lazy.nvim' +if not vim.loop.fs_stat(lazypath) then + vim.fn.system { + 'git', + 'clone', + '--filter=blob:none', + 'https://github.com/folke/lazy.nvim.git', + lazypath, + } + + -- Use specific commit of lazy.nvim if defined + local f = io.open(vim.fn.stdpath('config') .. '/lazy-lock.json', 'r') + if f then + local lock = vim.json.decode(f:read('*a')) + vim.fn.system { 'git', '-C', lazypath, 'checkout', lock['lazy.nvim'].commit } + end +end +vim.opt.rtp:prepend(lazypath) + +local opts = { + install = { colorscheme = { 'nord', 'onedark', 'habamax' } }, + defaults = { lazy = true }, + ui = { + wrap = false, + size = { width = 0.9, height = 0.85 }, + border = require('user.config.vars').border, + }, + checker = { enabled = false, notify = false }, + change_detection = { enabled = true, notify = false }, + performance = { + cache = { enabled = true }, + reset_packpath = true, + rtp = { + reset = true, + disabled_plugins = { + 'gzip', + 'matchit', + 'matchparen', + 'netrw', + 'netrwFilHandlers', + 'netrwPlugin', + 'netrwSettings', + 'netrw_gitignore', + 'rplugin', + 'spellfile', + 'sqlcomplete', + 'syntaxcomplete', + 'tar', + 'tarPlugin', + 'tohtml', + 'tutor', + 'zip', + 'zipPlugin', + }, + }, + }, +} + +require('lazy').setup('user.plugins', opts) diff --git a/lua/user/config/options.lua b/lua/user/config/options.lua new file mode 100644 index 0000000..a1c23b0 --- /dev/null +++ b/lua/user/config/options.lua @@ -0,0 +1,178 @@ +local global_vars = { + -- Leader keys + mapleader = ' ', + maplocalleader = '\\', + + -- TeX flavor + tex_flavor = 'latex', +} + +local vim_options = { + -- General settings + termguicolors = true, + background = 'dark', + mouse = 'nv', + errorbells = false, + visualbell = false, + confirm = true, + fileformats = 'unix', + magic = true, + virtualedit = 'block', + encoding = 'utf-8', + fileencoding = 'utf-8', + clipboard = { 'unnamed', 'unnamedplus' }, + wildignorecase = true, + wildignore = { + '.DS_Store', + '.git', + '.hg', + '.svn', + '*.pyc', + '*.o', + '*.obj', + '*.out', + '*.ico', + '*.jar', + '*.jpg', + '*.jpeg', + '*.png', + '*.gif', + '*.avi', + '*.wav', + '*.zip', + '**/tmp/**', + '**/node_modules/**', + '**/bazel-out/**', + '**/bazel-bin/**', + '**/bazel-testlogs/**', + '**/plz-out/**', + '**/.plz-cache/**', + '**/.plz-http-cache/**', + }, + showmode = false, + sessionoptions = { 'buffers', 'curdir', 'tabpages', 'winsize' }, + jumpoptions = 'stack', + formatoptions = '1jcroqlnt', + wrap = false, + sidescrolloff = 4, + scrolloff = 4, + whichwrap = { + ['<'] = true, + ['>'] = true, + ['['] = true, + [']'] = true, + ['~'] = true, + }, + splitkeep = 'screen', + -- colorcolumn = { 120 }, + -- cursorcolumn = true, + cursorline = true, + cursorlineopt = { 'number' }, + backspace = { 'indent', 'eol', 'start' }, + showcmd = false, + cmdheight = 0, + showtabline = 2, + laststatus = 3, + synmaxcol = 2500, + grepformat = '%f:%l:%c:%m', + grepprg = 'rg --hidden --vimgrep --smart-case --', + diffopt = { 'filler', 'iwhite', 'internal', 'algorithm:patience' }, + + -- CVE-2019-12735 (it is patched, but just to be cautious) + -- I don't use modeline personally anyway + modeline = false, + modelines = 0, + + -- Spell checking, dictionary + -- spell = true, + -- spellfile = vim.fn.stdpath('config') .. '/spell/en.utf-8.add', + -- dictionary = { '/usr/share/dict/american-english' }, + + -- Transparency + pumblend = 0, + winblend = 0, + + -- Conceal + conceallevel = 2, + concealcursor = 'nc', + foldenable = false, + foldlevelstart = 99, + + -- Case insensitive + ignorecase = true, + smartcase = true, + infercase = true, + + -- Update time + timeoutlen = 500, + ttimeoutlen = 25, + updatetime = 150, + redrawtime = 1500, + + -- No swapfile + backup = false, + writebackup = false, + swapfile = false, + + -- Completion menu + wildmode = 'longest:full,full', + complete = { '.', 'w', 'b', 'k' }, + completeopt = { 'menu', 'menuone', 'noselect' }, + pumheight = 16, + helpheight = 12, + previewheight = 12, + + -- Window rules + splitbelow = true, + splitright = true, + switchbuf = 'useopen', + winwidth = 30, + winminwidth = 10, + equalalways = false, + + -- Left column + signcolumn = 'auto:6', + number = true, + relativenumber = true, + -- numberwidth = 6, + + -- 4 spaces = 1 tab + tabstop = 4, + -- softtabstop = -1, -- use the value of 'shiftwidth' + shiftwidth = 0, -- use the value of 'tabstop' + expandtab = true, + smartindent = true, + shiftround = true, + + -- Trailings, line break + list = true, + listchars = { + tab = '»·', + nbsp = '+', + trail = '·', + extends = '→', + precedes = '←', + }, + showbreak = '↳ ', + -- linebreak = true, + -- breakat = [[\ \ ;:,!?]], + breakindent = true, + -- breakindentopt = { shift = 4, min = 20 }, + + -- Undo file path + undofile = true, + undolevels = 10000, +} + +-- Load global variables +for k, v in pairs(global_vars) do + vim.g[k] = v +end + +-- Load editor options +for k, v in pairs(vim_options) do + vim.opt[k] = v +end + +-- Special treats +vim.opt.shortmess:append { W = true, I = true, c = true, C = true } diff --git a/lua/user/config/vars.lua b/lua/user/config/vars.lua new file mode 100644 index 0000000..43bbbb5 --- /dev/null +++ b/lua/user/config/vars.lua @@ -0,0 +1,87 @@ +local M = {} + +M.border = 'rounded' +M.rg_args = { '--hidden', '--color=never', '--no-heading', '--with-filename', '--line-number', '--column' } + +-- Log level used for nvim-notify and LSP log +M.loglevel = vim.log.levels.INFO + +-- Some default LSP options (can be toggled) +M.lsp_format_on_save = false +M.lsp_virtual_lines = true +M.lsp_inlay_hints = true +M.lsp_document_highlight = true + +-- Only 'bottom_pane', 'horizontal' or 'cursor' right now +M.telescope_layout = 'horizontal' + +-- 'tabby' or 'cokeline' +M.tabline_provider = 'cokeline' + +-- Where to install tree-sitter parsers (will be added to rtp) +M.ts_parsers_path = vim.fn.stdpath('data') .. '/parsers' + +M.icons = { + notify = { -- also used for diagnostic signs + error = '', + hint = '', + info = '', + warn = '', + debug = '', + trace = '', + }, + git = { + added = ' ', + modified = ' ', + removed = ' ', + }, + dap = { + Stopped = { ' ', 'DiagnosticWarn', 'Visual' }, + Breakpoint = ' ', + BreakpointCondition = { ' ', 'DiagnosticHint' }, + BreakpointRejected = { ' ', 'DiagnosticError' }, + LogPoint = ' ', + }, + kind = { + -- aerial.nvim, nvim-navic + File = { icon = '', hl = '@text.uri' }, + Module = { icon = '', hl = '@namespace' }, + Namespace = { icon = '', hl = '@namespace' }, + Package = { icon = '', hl = '@namespace' }, + Class = { icon = '', hl = '@storageclass' }, + Method = { icon = '', hl = '@method' }, + Property = { icon = '', hl = '@property' }, + Field = { icon = '', hl = '@field' }, + Constructor = { icon = '󰒓', hl = '@constructor' }, + Enum = { icon = '', hl = '@lsp.type.enum' }, + Interface = { icon = '', hl = '@lsp.type.interface' }, + Function = { icon = '󰡱', hl = '@function' }, + Variable = { icon = '', hl = '@variable' }, + Constant = { icon = '', hl = '@constant' }, + String = { icon = '', hl = '@string' }, + Number = { icon = '', hl = '@number' }, + Boolean = { icon = '', hl = '@boolean' }, + Array = { icon = '', hl = '@constant' }, + Object = { icon = '', hl = '@type' }, + Key = { icon = '󰌋', hl = '@type' }, + Null = { icon = '󰟢', hl = '@none' }, + EnumMember = { icon = '', hl = '@constant' }, + Struct = { icon = '', hl = '@lsp.type.struct' }, + Event = { icon = '', hl = '@type' }, + Operator = { icon = '', hl = '@operator' }, + TypeParameter = { icon = '', hl = '@parameter' }, + + -- Additional ones for nvim-cmp. + -- No need to specify the highlights here. + Text = { icon = '' }, + Unit = { icon = '' }, + Value = { icon = ' ' }, + Keyword = { icon = '' }, + Snippet = { icon = '' }, + Color = { icon = '' }, + Reference = { icon = '' }, + Folder = { icon = '' }, + }, +} + +return M diff --git a/lua/user/plugins/completion/cmp.lua b/lua/user/plugins/completion/cmp.lua new file mode 100644 index 0000000..9717608 --- /dev/null +++ b/lua/user/plugins/completion/cmp.lua @@ -0,0 +1,139 @@ +local cmp, luasnip = require('cmp'), require('luasnip') +local vars = require('user.config.vars') + +local sources_config = { + buffer = { item_menu = 'BUF', option = { keyword_length = 2 } }, + dap = { item_menu = 'DAP' }, + fish = { item_menu = 'FISH' }, + luasnip = { item_menu = 'SNIP' }, + nvim_lsp = { item_menu = 'LSP' }, + orgmode = { item_menu = 'ORG' }, + path = { item_menu = 'PATH' }, +} + +local sources = vim.deepcopy(sources_config) +for source, _ in pairs(sources_config) do + sources[source].name = source + sources[source].item_menu = nil +end + +local has_words_before = function() + local line, col = unpack(vim.api.nvim_win_get_cursor(0)) + return col ~= 0 and vim.api.nvim_buf_get_lines(0, line - 1, line, true)[1]:sub(col, col):match('%s') == nil +end + +local cmpkind_icons = {} +for kind, spec in pairs(vars.icons.kind) do + cmpkind_icons[kind] = spec.icon +end + +cmp.setup { + view = { entries = { name = 'custom', selection_order = 'near_cursor' } }, + window = { + documentation = { + border = vars.border, + winhighlight = 'NormalFloat:NormalFloat,FloatBorder:FloatBorder', + }, + completion = { + scrollbar = true, + border = vars.border, + winhighlight = 'NormalFloat:NormalFloat,FloatBorder:FloatBorder', + completeopt = 'menu,menuone,noinsert', + keyword_pattern = [[\%(-\?\d\+\%(\.\d\+\)\?\|\h\w*\%(-\w*\)*\)]], + keyword_length = 1, + }, + }, + formatting = { + fields = { 'kind', 'abbr', 'menu' }, + format = function(entry, vim_item) + vim_item.menu = sources_config[entry.source.name].item_menu + + if vim.tbl_contains({ 'path' }, entry.source.name) then + local icon, hl_group = require('nvim-web-devicons').get_icon(entry:get_completion_item().label) + if icon then + vim_item.kind = icon + vim_item.kind_hl_group = hl_group + return vim_item + end + end + + vim_item.kind = cmpkind_icons[vim_item.kind] .. ' ' + return vim_item + end, + }, + mapping = { + [''] = cmp.mapping.confirm { + behavior = cmp.ConfirmBehavior.Replace, + select = true, + }, + [''] = cmp.mapping.complete(), + [''] = cmp.mapping.select_prev_item { behavior = cmp.SelectBehavior.Select }, + [''] = cmp.mapping.select_next_item { behavior = cmp.SelectBehavior.Select }, + [''] = cmp.mapping.scroll_docs(-4), + [''] = cmp.mapping.scroll_docs(4), + [''] = cmp.mapping(function(fallback) + if cmp.visible() then + cmp.close() + else + fallback() + end + end), + -- Change choice nodes for luasnip + [''] = cmp.mapping(function(fallback) + if luasnip.choice_active() then + luasnip.change_choice(1) + else + fallback() + end + end, { 'i', 's' }), + -- supertab-like mapping + [''] = cmp.mapping(function(fallback) + if cmp.visible() then + cmp.select_next_item { behavior = cmp.SelectBehavior.Insert } + elseif luasnip.expand_or_jumpable() then + luasnip.expand_or_jump() + elseif has_words_before() then + cmp.complete() + else + fallback() + end + end, { 'i', 's' }), + [''] = cmp.mapping(function(fallback) + if cmp.visible() then + cmp.select_prev_item { behavior = cmp.SelectBehavior.Insert } + elseif luasnip.jumpable(-1) then + luasnip.jump(-1) + else + fallback() + end + end, { 'i', 's' }), + }, + snippet = { + expand = function(args) + luasnip.lsp_expand(args.body) + end, + }, + sources = cmp.config.sources { + sources.nvim_lsp, + sources.buffer, + sources.luasnip, + sources.path, + }, +} + +-- Filetype-based completion sources +cmp.setup.filetype({ 'dap-repl', 'dapui_watches', 'dapui_hover' }, { + sources = { sources.dap, sources.buffer }, +}) +cmp.setup.filetype('fish', { + sources = { sources.fish, sources.buffer, sources.path }, +}) +cmp.setup.filetype('org', { + sources = cmp.config.sources { + sources.orgmode, + sources.nvim_lsp, + sources.luasnip, + sources.buffer, + sources.path, + }, +}) diff --git a/lua/user/plugins/completion/init.lua b/lua/user/plugins/completion/init.lua new file mode 100644 index 0000000..d168274 --- /dev/null +++ b/lua/user/plugins/completion/init.lua @@ -0,0 +1,55 @@ + +return { + { 'mtoohey31/cmp-fish', ft = 'fish' }, + { 'rcarriga/cmp-dap', ft = { 'dap-repl', 'dapui_watches', 'dapui_hover' } }, + { + 'hrsh7th/nvim-cmp', + event = 'InsertEnter', + dependencies = { + 'hrsh7th/cmp-buffer', + 'hrsh7th/cmp-path', + 'hrsh7th/cmp-nvim-lsp', + 'saadparwaiz1/cmp_luasnip', + { + 'L3MON4D3/LuaSnip', + dependencies = 'rafamadriz/friendly-snippets', + config = function() + require('user.plugins.completion.luasnip') + end, + }, + }, + config = function() + require('user.plugins.completion.cmp') + end, + }, + { + 'ii14/emmylua-nvim', + -- we only care about the EmmyLua files (loaded by lua-language-server) + init = function() + require('lazy.core.loader').disable_rtp_plugin('emmylua-nvim') + end, + }, + { + 'TabbyML/tabby', + event = 'InsertEnter', + cmd = 'Tabby', + enabled = vim.loop.os_uname().sysname == 'Darwin', -- I don't use NVIDIA, so only use this plugin on M1/M2 machines + init = function() + local config = { + tabby_server_url = 'http://127.0.0.1:10069', + tabby_enable = false, + tabby_suggestion_delay = vim.opt.updatetime:get(), + tabby_accept_binding = "", + tabby_dismiss_binding = "", + } + + for k, v in pairs(config) do + vim.g[k] = v + end + end, + config = function(plugin) + require('lazy.core.loader').disable_rtp_plugin('tabby') + vim.opt.rtp:append(plugin.dir .. "/clients/vim") + end, + }, +} diff --git a/lua/user/plugins/completion/luasnip.lua b/lua/user/plugins/completion/luasnip.lua new file mode 100644 index 0000000..a6fcdaf --- /dev/null +++ b/lua/user/plugins/completion/luasnip.lua @@ -0,0 +1,17 @@ +local types = require('luasnip.util.types') + +require('luasnip').config.set_config { + updateevents = 'InsertLeave', + history = true, + ext_opts = { + [types.choiceNode] = { + active = { virt_text = { { '●', 'LuasnipChoiceNodeActive' } } }, + }, + [types.insertNode] = { + active = { virt_text = { { '●', 'LuasnipInsertNodeActive' } } }, + }, + }, +} + +-- Loading snippets from 'friendly-snippets' +require('luasnip.loaders.from_vscode').lazy_load() diff --git a/lua/user/plugins/debugger/coverage.lua b/lua/user/plugins/debugger/coverage.lua new file mode 100644 index 0000000..a5601d7 --- /dev/null +++ b/lua/user/plugins/debugger/coverage.lua @@ -0,0 +1,41 @@ +local styles = { + single = { + topleft = '┌', + topright = '┐', + top = '─', + left = '│', + right = '│', + botleft = '└', + botright = '┘', + bot = '─', + }, + rounded = { + topleft = '╭', + topright = '╮', + top = '─', + left = '│', + right = '│', + botleft = '╰', + botright = '╯', + bot = '─', + }, + double = { + topleft = '╔', + topright = '╗', + top = '═', + left = '║', + right = '║', + botleft = '╚', + botright = '╝', + bot = '═', + }, +} + +return { + summary = { + min_coverage = 75.0, + width_percentage = 0.8, + borders = styles[require('user.config.vars').border], + window = { winblend = 0 }, + }, +} diff --git a/lua/user/plugins/debugger/dap/debugpy.lua b/lua/user/plugins/debugger/dap/debugpy.lua new file mode 100644 index 0000000..e679479 --- /dev/null +++ b/lua/user/plugins/debugger/dap/debugpy.lua @@ -0,0 +1,46 @@ +local dap = require('dap') +local debugpy_path = vim.fn.stdpath('data') .. '/dap/debugpy' + +dap.adapters.python = function(callback, config) + if config.request == 'attach' then + local port = (config.connect or config).port + local host = (config.connect or config).host or '127.0.0.1' + callback { + type = 'server', + port = assert(port, '`connect.port` is required for a python `attach` configuration'), + host = host, + options = { source_filetype = 'python' }, + } + return + end + + callback { + type = 'executable', + command = debugpy_path .. '/bin/python', + args = { '-m', 'debugpy.adapter' }, + options = { source_filetype = 'python' }, + } +end + +dap.configurations.python = { + { + name = 'Launch file', + type = 'python', + request = 'launch', + program = '${file}', + pythonPath = function() + local cwd, venv, conda = vim.fn.getcwd(), vim.env.VIRTUAL_ENV, vim.env.CONDA_PREFIX + if venv and vim.fn.executable(venv .. '/bin/python') == 1 then + return venv .. '/bin/python' + elseif conda and vim.fn.executable(conda .. '/bin/python') == 1 then + return conda .. '/bin/python' + elseif vim.fn.executable(cwd .. '/venv/bin/python') then + return cwd .. '/venv/bin/python' + elseif vim.fn.executable(cwd .. '/.venv/bin/python') then + return cwd .. '/.venv/bin/python' + else + return '/usr/bin/python' + end + end, + }, +} diff --git a/lua/user/plugins/debugger/dap/dlv.lua b/lua/user/plugins/debugger/dap/dlv.lua new file mode 100644 index 0000000..62a7230 --- /dev/null +++ b/lua/user/plugins/debugger/dap/dlv.lua @@ -0,0 +1,152 @@ +local dap = require('dap') +local lazy_util = require('lazy.core.util') + +-- Default port to attach to an existing Delve instance +local delve_port = 38697 + +local get_arguments = function() + local co = coroutine.running() + local args = {} + + if co then + return coroutine.create(function() + vim.ui.input({ prompt = 'Args: ' }, function(input) + args = vim.split(input or '', ' ') + end) + coroutine.resume(co, args) + end) + end + + vim.ui.input({ prompt = 'Args: ' }, function(input) + args = vim.split(input or '', ' ') + end) + return args +end + +local get_connection = function() + local co = coroutine.running() + local response = {} + + if co then + return coroutine.create(function() + vim.ui.input({ prompt = 'Connection: ' }, function(input) + response = vim.split(input or ('127.0.0.1:' .. delve_port), ':') + end) + coroutine.resume(co, { host = response[1], port = response[2] or delve_port }) + end) + end + + vim.ui.input({ prompt = 'Connection: ' }, function(input) + response = vim.split(input or ('127.0.0.1:' .. delve_port), ':') + end) + return { host = response[1], port = response[2] or delve_port } +end + +-- https://github.com/ray-x/go.nvim/blob/master/lua/go/dap.lua +dap.adapters.go = function(callback, config) + local host = (config.connect or config).host or '127.0.0.1' + + if config.request == 'attach' and config.mode == 'remote' then + callback { + type = 'server', + host = host, + port = (config.connect or config).port or delve_port, + } + return + end + + -- Randomize a random port number to launch Delve with + math.randomseed(os.time()) + local port = (config.connect or config).port or (38000 + math.random(1, 1000)) + + local stdout = vim.loop.new_pipe(false) + local stderr = vim.loop.new_pipe(false) + local handle, pid_or_err + + local function on_read(err, chunk) + assert(not err, err) + if not chunk or chunk == '' then return end + + if chunk:find("couldn't start") then + vim.schedule(function() + lazy_util.error(chunk, { title = '[go] DAP Error' }) + end) + end + + vim.schedule(function() + require('dap.repl').append(chunk) + end) + end + + handle, pid_or_err = vim.loop.spawn('dlv', { + stdio = { nil, stdout, stderr }, + args = { 'dap', '--listen', string.format('%s:%s', host, port) }, + detached = true, + }, function(code) + stdout:close() + stderr:close() + handle:close() + + if code ~= 0 then + vim.schedule(function() + lazy_util.warn(string.format('Delve exited with exit code: %d', code), { title = '[go] DAP Warning' }) + end) + end + end) + assert(handle, string.format('Error running delve: %s', pid_or_err)) + stdout:read_start(on_read) + stderr:read_start(on_read) + + -- Wait for delve to start + vim.defer_fn(function() + callback { type = 'server', host = host, port = port } + end, 1000) +end + +dap.configurations.go = { + { + name = 'Attach', + mode = 'local', + type = 'go', + request = 'attach', + processId = require('dap.utils').pick_process, + }, + -- TODO: test this remote attach setup with Bazel + -- Ref: + -- - https://github.com/bazelbuild/rules_go/issues/1844#issuecomment-1281231804 + -- - https://github.com/golang/vscode-go/wiki/debugging#remote-debugging + { + name = 'Attach (remote)', + mode = 'remote', + type = 'go', + request = 'attach', + connect = get_connection, + }, + { + name = 'Debug', + type = 'go', + request = 'launch', + program = '${file}', + }, + { + name = 'Debug (arguments)', + type = 'go', + request = 'launch', + program = '${file}', + args = get_arguments, + }, + { + name = 'Debug test', + type = 'go', + request = 'launch', + mode = 'test', + program = '${file}', + }, + { + name = 'Debug test (go.mod)', + type = 'go', + request = 'launch', + mode = 'test', + program = './${relativeFileDirname}', + }, +} diff --git a/lua/user/plugins/debugger/dap/init.lua b/lua/user/plugins/debugger/dap/init.lua new file mode 100644 index 0000000..d2da4d3 --- /dev/null +++ b/lua/user/plugins/debugger/dap/init.lua @@ -0,0 +1,19 @@ +local vars = require('user.config.vars') + +for name, sign in pairs(vars.icons.dap) do + sign = type(sign) == 'table' and sign or { sign } + vim.fn.sign_define('Dap' .. name, { + text = sign[1], + texthl = sign[2] or 'DiagnosticInfo', + linehl = sign[3], + numhl = sign[3], + }) +end + +require('user.plugins.debugger.dap.dlv') +require('user.plugins.debugger.dap.debugpy') + +-- Use overseer's JSON decoder +require('dap.ext.vscode').json_decode = require('overseer.json').decode +-- Then enable the integration manually +require('overseer').patch_dap(true) diff --git a/lua/user/plugins/debugger/init.lua b/lua/user/plugins/debugger/init.lua new file mode 100644 index 0000000..5ae3a0e --- /dev/null +++ b/lua/user/plugins/debugger/init.lua @@ -0,0 +1,138 @@ +return { + { + 'rcarriga/nvim-dap-ui', + keys = { + { 'du', function() require('dapui').toggle() end, desc = 'Dap UI' }, + { 'de', function() require('dapui').eval() end, desc = 'Eval', mode = { 'n', 'v' } }, + }, + dependencies = { + { 'theHamsta/nvim-dap-virtual-text', config = true }, + { + 'mfussenegger/nvim-dap', + keys = { + { 'dB', function() require('dap').set_breakpoint(vim.fn.input('Breakpoint condition: ')) end, desc = 'Breakpoint condition' }, + { 'db', function() require('dap').toggle_breakpoint() end, desc = 'Toggle breakpoint' }, + { 'dc', function() require('dap').continue() end, desc = 'Continue' }, + { 'dC', function() require('dap').run_to_cursor() end, desc = 'Run to cursor' }, + { 'dg', function() require('dap').goto_() end, desc = 'Go to line (skipped)' }, + { 'di', function() require('dap').step_into() end, desc = 'Step into' }, + { 'dj', function() require('dap').down() end, desc = 'Down' }, + { 'dk', function() require('dap').up() end, desc = 'Up' }, + { 'dl', function() require('dap').run_last() end, desc = 'Run last' }, + { 'do', function() require('dap').step_out() end, desc = 'Step out' }, + { 'dO', function() require('dap').step_over() end, desc = 'Step over' }, + { 'dp', function() require('dap').pause() end, desc = 'Pause' }, + { 'dr', function() require('dap').repl.toggle() end, desc = 'Toggle REPL' }, + { 'ds', function() require('dap').session() end, desc = 'Session' }, + { 'dt', function() require('dap').terminate() end, desc = 'Terminate' }, + { 'dw', function() require('dap.ui.widgets').hover() end, desc = 'Widgets' }, + }, + config = function() + require('user.plugins.debugger.dap') + end, + }, + }, + opts = { + floating = { border = require('user.config.vars').border }, + }, + config = function(_, opts) + local dap, dapui = require('dap'), require('dapui') + dapui.setup(opts) + + dap.listeners.after.event_initialized['dapui_config'] = function() + dapui.open() + end + dap.listeners.before.event_terminated['dapui_config'] = function() + dapui.close() + end + dap.listeners.before.event_exited['dapui_config'] = function() + dapui.close() + end + end, + }, + { + 'nvim-neotest/neotest', + dependencies = { + 'lawrence-laz/neotest-zig', + 'marilari88/neotest-vitest', + 'nvim-neotest/neotest-jest', + 'nvim-neotest/neotest-go', + 'nvim-neotest/neotest-python', + }, + keys = { + { + 'tt', + function() + local actions = { + ['Single position'] = function() + require('neotest').run.run() + end, + ['Current file'] = function() + require('neotest').run.run(vim.api.nvim_buf_get_name(0)) + end, + ['Directory'] = function() + vim.ui.input({ + prompt = 'Path: ', + default = vim.fn.getcwd(), + completion = 'file', + }, function(path) + if path then + require('neotest').run.run(path) + end + end) + end, + ['Nearest debug'] = function() + require('neotest').run.run { strategy = 'dap' } + end, + ['Rerun last position'] = function() + require('neotest').run.run_last() + end, + ['All adapters'] = function() + for _, adapter_id in ipairs(require('neotest').state.adapter_ids()) do + require('neotest').run.run { suite = true, adapter = adapter_id } + end + end, + } + + vim.ui.select(vim.tbl_keys(actions), { prompt = 'Run test option' }, function(choice) + if choice then actions[choice]() end + end) + end, + desc = 'Run test', + }, + { 'ts', function() require('neotest').run.stop() end, desc = 'Stop test' }, + { 'ta', function() require('neotest').run.attach() end, desc = 'Attach to test' }, + { 'ty', function() require('neotest').summary.toggle() end, desc = 'Toggle test summary' }, + { 'to', function() require('neotest').output.open { enter = true, auto_close = true } end, desc = 'Show test output' }, + { 'tO', function() require('neotest').output_panel.toggle() end, desc = 'Toggle test panel' }, + }, + config = function() + require('user.plugins.debugger.neotest') + end, + }, + { + 'andythigpen/nvim-coverage', + cmd = { 'Coverage', 'CoverageLoad', 'CoverageClear', 'CoverageSummary' }, + keys = { + { + 'tc', + function() + local actions = { + ['Load coverage'] = function() require('coverage').load(true) end, + ['Toggle signs'] = function() require('coverage').toggle() end, + ['Clear coverage cache'] = function() require('coverage').clear() end, + ['Show summary report'] = function() require('coverage').summary() end, + } + + vim.ui.select(vim.tbl_keys(actions), { prompt = 'Coverage action' }, function(choice) + if choice then actions[choice]() end + end) + end, + desc = 'Coverage', + } + }, + opts = function() + return require('user.plugins.debugger.coverage') + end, + }, +} diff --git a/lua/user/plugins/debugger/neotest.lua b/lua/user/plugins/debugger/neotest.lua new file mode 100644 index 0000000..43a0d4e --- /dev/null +++ b/lua/user/plugins/debugger/neotest.lua @@ -0,0 +1,44 @@ +local neotest_ns = vim.api.nvim_create_namespace('neotest') +vim.diagnostic.config({ + virtual_text = { + format = function(diagnostic) + local message = diagnostic.message:gsub('\n', ' '):gsub('\t', ' '):gsub('%s+', ' '):gsub('^%s+', '') + return message + end, + }, + virtual_lines = false, +}, neotest_ns) + +require('neotest').setup { + adapters = { + require('neotest-go'), -- TODO: https://github.com/nvim-neotest/neotest-go/issues/57 (generate coverage file) + require('neotest-jest') { cwd = require('neotest-jest').root }, + require('neotest-python'), + require('neotest-vitest'), + require('neotest-zig'), + }, + consumers = { + overseer = require('neotest.consumers.overseer'), + }, + discovery = { enabled = false }, + floating = { + border = require('user.config.vars').border, + max_height = 0.8, + max_width = 0.8, + }, + icons = { + child_indent = '│', + child_prefix = '├', + collapsed = '─', + expanded = '┐', + failed = '', + final_child_indent = ' ', + final_child_prefix = '└', + non_collapsible = '─', + passed = '', + running = '', + running_animated = { '/', '|', '\\', '-', '/', '|', '\\', '-' }, + skipped = '󰒬', + unknown = '' + }, +} diff --git a/lua/user/plugins/editor/headlines.lua b/lua/user/plugins/editor/headlines.lua new file mode 100644 index 0000000..c272dcb --- /dev/null +++ b/lua/user/plugins/editor/headlines.lua @@ -0,0 +1,23 @@ +local options = { + headline_highlights = { + 'Headline1', + 'Headline2', + 'Headline3', + 'Headline4', + 'Headline5', + 'Headline6', + }, + codeblock_highlight = 'CodeBlock', + dash_highlight = 'Dash', + dash_string = '-', + quote_highlight = 'Quote', + quote_string = '┃', + fat_headlines = false, + fat_headline_upper_string = '▃', + fat_headline_lower_string = '🬂', +} + +return { + markdown = options, + org = options, +} diff --git a/lua/user/plugins/editor/indent-blankline.lua b/lua/user/plugins/editor/indent-blankline.lua new file mode 100644 index 0000000..a25611a --- /dev/null +++ b/lua/user/plugins/editor/indent-blankline.lua @@ -0,0 +1,34 @@ +return { + indent = { char = '│', tab_char = '│' }, + scope = { + show_start = false, + show_end = false, + include = { node_type = { ['*'] = { '*' } } }, + }, + exclude = { + filetypes = { + 'alpha', + 'lazy', + 'log', + 'notify', + 'undotree', + 'Trouble', + 'NvimTree', + 'diff', + 'qf', + 'help', + 'prompt', + 'noice', + 'aerial', + 'TelescopePrompt', + 'Trouble', + 'OverseerForm', + 'gitcommit', + 'markdown', + 'org', + 'json', + 'txt', + '', -- for all buffers without a filetype + }, + }, +} diff --git a/lua/user/plugins/editor/init.lua b/lua/user/plugins/editor/init.lua new file mode 100644 index 0000000..e7fce6b --- /dev/null +++ b/lua/user/plugins/editor/init.lua @@ -0,0 +1,380 @@ +local ts_parsers_path = require('user.config.vars').ts_parsers_path + +return { + { 'MTDL9/vim-log-highlighting', ft = 'log' }, + { + 'nvim-orgmode/orgmode', + ft = 'org', + config = function() + require('user.plugins.editor.orgmode') + end, + }, + { + 'lukas-reineke/headlines.nvim', + ft = { 'org', 'markdown' }, + opts = require('user.plugins.editor.headlines'), + config = function(_, opts) + -- NOTE: lazily starting up headlines.nvim to prevent it from slowing down on file opening + vim.schedule(function() + require('headlines').setup(opts) + require('headlines').refresh() + end) + end, + }, + { + 'folke/todo-comments.nvim', + cmd = { 'TodoTrouble', 'TodoTelescope' }, + event = { 'BufReadPost', 'BufNewFile' }, + keys = { + { ']t', function() require('todo-comments').jump_next() end, desc = 'Next todo comment' }, + { '[t', function() require('todo-comments').jump_prev() end, desc = 'Previous todo comment' }, + { 'gt', 'TodoTrouble', desc = 'Todo items' }, + { 'ft', 'TodoTelescope', desc = 'Todo items' }, + }, + opts = require('user.plugins.editor.todo-comments'), + }, + { + 'nvim-treesitter/nvim-treesitter', + build = { '/bin/mkdir -p ' .. ts_parsers_path, ':TSUpdate' }, + event = { 'BufReadPost', 'BufNewFile' }, + keys = { + { '', desc = 'Increment selection' }, + { '', desc = 'Decrement selection', mode = 'x' }, + }, + dependencies = { + 'HiPhish/rainbow-delimiters.nvim', + { 'JoosepAlviste/nvim-ts-context-commentstring', opts = { enable_autocmd = false } }, + { + 'nvim-treesitter/nvim-treesitter-context', + keys = { + { '[c', function() require('treesitter-context').go_to_context() end, desc = 'Go to context' }, + }, + }, + { + 'andymass/vim-matchup', + init = function() + vim.g.matchup_matchparen_offscreen = { method = 'popup' } + end, + }, + }, + init = function(plugin) + vim.opt.rtp:append(ts_parsers_path) + + -- Make queries available early, since lots of plugins don't require nvim-treesitter anymore + require('lazy.core.loader').add_to_rtp(plugin) + require('nvim-treesitter.query_predicates') + end, + opts = require('user.plugins.editor.treesitter'), + config = function(_, opts) + require('nvim-treesitter.configs').setup(opts) + -- tree-sitter based folding + vim.opt.foldmethod = 'expr' + vim.opt.foldexpr = 'nvim_treesitter#foldexpr()' + end, + }, + { + 'windwp/nvim-ts-autotag', + ft = { + 'html', + 'javascript', + 'typescript', + 'javascriptreact', + 'typescriptreact', + 'vue', + 'xml', + 'php', + 'markdown', + 'svelte', + 'rescript', + 'glimmer', + 'astro', + 'html.handlebars', + }, + }, + { + 'lukas-reineke/indent-blankline.nvim', + event = { 'BufReadPost', 'BufNewFile' }, + main = 'ibl', + opts = require('user.plugins.editor.indent-blankline'), + }, + { + 'mizlan/iswap.nvim', + cmd = { 'ISwapWith', 'ISwap' }, + keys = { + { 'es', 'ISwapWith', desc = 'Swap arguments' }, + }, + opts = { hl_grey = 'LineNr', autoswap = true }, + }, + { + 'danymat/neogen', + cmd = 'Neogen', + keys = { + { 'eg', function() require('neogen').generate() end, desc = 'Generate annotation' }, + -- { '', function() require('neogen').jump_next() end, desc = 'Next annotation', mode = 'i' }, + -- { '', function() require('neogen').jump_prev() end, desc = 'Previous annotation', mode = 'i' }, + }, + opts = { + enabled = true, + input_after_comment = true, + snippet_engine = 'luasnip', + }, + }, + { + 'folke/zen-mode.nvim', + cmd = 'ZenMode', + keys = { + { 'ez', 'ZenMode', desc = 'Zen mode' }, + }, + dependencies = { + { + 'folke/twilight.nvim', + cmd = { 'Twilight', 'TwilightEnable' }, + opts = require('user.plugins.editor.twilight'), + }, + }, + opts = { window = { width = 150 } }, + }, + { + 'monaqa/dial.nvim', + keys = { + { '', function() return require('dial.map').inc_normal() end, expr = true, desc = 'Increment' }, + { '', function() return require('dial.map').dec_normal() end, expr = true, desc = 'Decrement' }, + { '', function() return require('dial.map').inc_visual() end, expr = true, desc = 'Increment', mode = 'x' }, + { '', function() return require('dial.map').dec_visual() end, expr = true, desc = 'Decrement', mode = 'x' }, + { 'g', function() return require('dial.map').inc_gvisual() end, expr = true, desc = 'Increment (multiline)', mode = 'x' }, + { 'g', function() return require('dial.map').dec_gvisual() end, expr = true, desc = 'Decrement (multiline)', mode = 'x' }, + }, + config = function() + local augend = require('dial.augend') + require('dial.config').augends:register_group { + default = { + augend.integer.alias.decimal_int, + augend.integer.alias.hex, + augend.constant.alias.bool, + augend.constant.new { elements = { 'True', 'False' }, word = true, cyclic = true }, + augend.hexcolor.new { case = 'lower' }, + augend.semver.alias.semver, + augend.date.alias['%Y/%m/%d'], + }, + } + end, + }, + { + 'max397574/better-escape.nvim', + event = 'InsertCharPre', + opts = { + mapping = { 'jk', 'kj' }, + timeout = vim.opt.timeoutlen:get(), + clear_empty_lines = false, + keys = '', + }, + }, + { + 'eraserhd/parinfer-rust', + build = 'cargo build --locked --release', + ft = { + 'clojure', + 'lisp', + 'scheme', + 'fennel', + 'racket', + 'hy', + 'janet', + 'carp', + 'wast', + }, + }, + { + 'jbyuki/venn.nvim', + cmd = 'VBox', + keys = { + { + 'ev', function() + local venn_enabled = vim.inspect(vim.b.venn_enabled) + local key_opts = { noremap = true, silent = true, buffer = true } + local lazy_util = require('lazy.core.util') + + if venn_enabled == 'nil' then + vim.b.venn_enabled = true + vim.opt_local.virtualedit = 'all' + + -- Draw lines with WASD keystroke + vim.keymap.set('n', 'A', 'h:VBox', key_opts) + vim.keymap.set('n', 'A', 'h:VBox', key_opts) + vim.keymap.set('n', 'S', 'j:VBox', key_opts) + vim.keymap.set('n', 'W', 'k:VBox', key_opts) + vim.keymap.set('n', 'D', 'l:VBox', key_opts) + -- Draw boxes by pressing 'f' with visual selection + vim.keymap.set('x', 'f', ':VBox', key_opts) + + lazy_util.info('Virtual box editing mode enabled.', { title = 'Venv mode', icon = ' ' }) + else + vim.b.venn_enabled = nil + vim.opt_local.virtualedit = vim.opt.virtualedit:get() + + vim.keymap.del('n', 'A', { buffer = true }) + vim.keymap.del('n', 'S', { buffer = true }) + vim.keymap.del('n', 'W', { buffer = true }) + vim.keymap.del('n', 'D', { buffer = true }) + vim.keymap.del('x', 'f', { buffer = true }) + + lazy_util.info('Virtual box editing mode disabled.', { title = 'Venv mode', icon = ' ' }) + end + end, + desc = 'Virtual box edit', + }, + }, + }, + { + 'folke/flash.nvim', + keys = { + { '-', function() require('flash').jump() end, mode = { 'n', 'x', 'o' }, desc = 'Flash' }, + { '_', function() require('flash').treesitter() end, mode = { 'n', 'x', 'o' }, desc = 'Flash (Treesitter)' }, + { 'gs', function() require('flash').remote() end, mode = 'o', desc = 'Flash (remote)' }, + { 'gS', function() require('flash').treesitter_search() end, mode = { 'x', 'o' }, desc = 'Flash search (Treesitter)' }, + { '', function() require('flash').toggle() end, mode = 'c', desc = 'Toggle Flash search' }, + 'f', 'F', 't', 'T', + }, + opts = { + search = { incremental = true }, + label = { after = false, before = true }, + modes = { + search = { enabled = false }, + char = { jump_labels = true, multi_line = false }, + }, + prompt = { enabled = false }, + }, + }, + { + 'Wansmer/treesj', + keys = { + { 'gJ', 'TSJToggle', desc = 'Join toggle' }, + }, + opts = { use_default_keymaps = false, max_join_length = 150 }, + }, + { + 'echasnovski/mini.move', + keys = { + { '', mode = { 'n', 'x' }, desc = 'Move left' }, + { '', mode = { 'n', 'x' }, desc = 'Move down' }, + { '', mode = { 'n', 'x' }, desc = 'Move up' }, + { '', mode = { 'n', 'x' }, desc = 'Move right' }, + }, + opts = { + options = { + reindent_linewise = true, + }, + }, + config = function(_, opts) + require('mini.move').setup(opts) + end, + }, + { + 'echasnovski/mini.ai', + event = 'VeryLazy', + dependencies = { + { + 'nvim-treesitter/nvim-treesitter-textobjects', + -- we only need the queries, other functions aren't used + init = function() + require('lazy.core.loader').disable_rtp_plugin('nvim-treesitter-textobjects') + end, + }, + }, + config = function() + require('user.plugins.editor.mini-ai') + end, + }, + { + 'echasnovski/mini.pairs', + event = 'InsertEnter', + config = function() + require('mini.pairs').setup() + end, + }, + { + 'echasnovski/mini.align', + keys = function(_, keys) + local plugin = require('lazy.core.config').spec.plugins['mini.align'] + local opts = require('lazy.core.plugin').values(plugin, 'opts', false) + local mappings = { + { opts.mappings.start, mode = { 'n', 'v' }, desc = 'Align text' }, + { opts.mappings.start_with_preview, mode = { 'n', 'v' }, desc = 'Align text (with preview)' }, + } + mappings = vim.tbl_filter(function(m) + return m[1] and #m[1] > 0 + end, mappings) + return vim.list_extend(mappings, keys) + end, + opts = { mappings = { start = 'ga', start_with_preview = 'gA' } }, + config = function(_, opts) + require('mini.align').setup(opts) + end, + }, + { + 'echasnovski/mini.comment', + keys = { + { 'gc', mode = 'o' }, + { 'gc', mode = { 'n', 'x' }, desc = 'Toggle comment' }, + { 'gcc', mode = 'n', desc = 'Toggle comment on current line' }, + }, + opts = { + options = { + ignore_blank_line = true, + custom_commentstring = function() + return require('ts_context_commentstring.internal').calculate_commentstring() or vim.bo.commentstring + end, + }, + }, + config = function(_, opts) + require('mini.comment').setup(opts) + end, + }, + { + 'echasnovski/mini.surround', + keys = function(_, keys) + local plugin = require('lazy.core.config').spec.plugins['mini.surround'] + local opts = require('lazy.core.plugin').values(plugin, 'opts', false) + local mappings = { + { opts.mappings.add, mode = { 'n', 'x' }, desc = 'Add surrounding' }, + { opts.mappings.delete, desc = 'Delete surrounding' }, + { opts.mappings.find, desc = 'Find surrounding' }, + { opts.mappings.find_left, desc = 'Find left surrounding' }, + { opts.mappings.highlight, desc = 'Highlight surrounding' }, + { opts.mappings.replace, desc = 'Replace surrounding' }, + { opts.mappings.update_n_lines, desc = 'Update `MiniSurround.config.n_lines`' }, + } + mappings = vim.tbl_filter(function(m) + return m[1] and #m[1] > 0 + end, mappings) + return vim.list_extend(mappings, keys) + end, + opts = { + mappings = { + add = 'sa', + delete = 'sd', + find = 'sf', + find_left = 'sF', + highlight = 'sh', + replace = 'sr', + update_n_lines = 'sn', + }, + silent = true, + search_method = 'cover_or_next', + highlight_duration = vim.opt.timeoutlen:get(), + }, + config = function(_, opts) + require('mini.surround').setup(opts) + end, + }, + { + 'echasnovski/mini.animate', + event = 'VeryLazy', + opts = function() + return require('user.plugins.editor.mini-animate') + end, + config = function(_, opts) + require('mini.animate').setup(opts) + end, + }, +} diff --git a/lua/user/plugins/editor/mini-ai.lua b/lua/user/plugins/editor/mini-ai.lua new file mode 100644 index 0000000..6f1ea95 --- /dev/null +++ b/lua/user/plugins/editor/mini-ai.lua @@ -0,0 +1,17 @@ +local ai = require('mini.ai') + +ai.setup { + silent = true, + -- https://github.com/echasnovski/mini.nvim/issues/122 + n_lines = 500, + -- Text objects from nvim-treesitter-textobjects + custom_textobjects = { + o = ai.gen_spec.treesitter({ + a = { '@block.outer', '@conditional.outer', '@loop.outer' }, + i = { '@block.inner', '@conditional.inner', '@loop.inner' }, + }), + f = ai.gen_spec.treesitter({ a = '@function.outer', i = '@function.inner' }), + c = ai.gen_spec.treesitter({ a = '@class.outer', i = '@class.inner' }), + t = { '<([%p%w]-)%f[^<%w][^<>]->.-', '^<.->().*()$' }, + }, +} diff --git a/lua/user/plugins/editor/mini-animate.lua b/lua/user/plugins/editor/mini-animate.lua new file mode 100644 index 0000000..460c8bc --- /dev/null +++ b/lua/user/plugins/editor/mini-animate.lua @@ -0,0 +1,33 @@ +-- Don't use animate when scrolling with the mouse +local mouse_scrolled = false +for _, scroll in ipairs({ 'Up', 'Down' }) do + local key = '' + vim.keymap.set({ 'n', 'i' }, key, function() + mouse_scrolled = true + return key + end, { expr = true }) +end + +local updatetime = vim.opt.updatetime:get() +local animate = require('mini.animate') +return { + cursor = { + path = animate.gen_path.line(), + timing = animate.gen_timing.linear { duration = updatetime, unit = 'total' }, + }, + resize = { timing = animate.gen_timing.linear { duration = updatetime, unit = 'total' }, }, + scroll = { + timing = animate.gen_timing.linear { duration = updatetime, unit = 'total' }, + subscroll = animate.gen_subscroll.equal { + predicate = function(total_scroll) + if mouse_scrolled then + mouse_scrolled = false + return false + end + return total_scroll > 1 + end, + }, + }, + open = { enable = false }, + close = { enable = false }, +} diff --git a/lua/user/plugins/editor/orgmode.lua b/lua/user/plugins/editor/orgmode.lua new file mode 100644 index 0000000..f93898b --- /dev/null +++ b/lua/user/plugins/editor/orgmode.lua @@ -0,0 +1,75 @@ +local c = require('user.themes.' .. vim.g.colors_name .. '.colors') + +local keyword_faces = { + TODO = ':foreground ' .. c.green .. ' :weight bold', + WAIT = ':foreground ' .. c.yellow .. ' :weight bold', + HOLD = ':foreground ' .. c.yellow .. ' :weight bold', + IDEA = ':foreground ' .. c.cyan .. ' :weight bold :slant italic', + DONE = ':foreground ' .. c.grey_bright .. ' :weight bold', + OKAY = ':foreground ' .. c.purple .. ' :weight bold :slant italic', + KILL = ':foreground ' .. c.red .. ' :weight bold', + CANCELED = ':foreground ' .. c.red .. ' :weight bold', + YES = ':foreground ' .. c.green .. ' :weight bold :underline on', + NO = ':foreground ' .. c.red .. ' :weight bold :underline on', +} + +local keywords = {} +for k, _ in pairs(keyword_faces) do + table.insert(keywords, k) +end + +require('orgmode').setup_ts_grammar() +require('orgmode').setup { + -- General settings + org_agenda_files = { '~/Documents/Org/agenda/*' }, + org_default_notes_file = '~/Documents/Org/notes.org', + org_todo_keywords = keywords, + org_todo_keyword_faces = keyword_faces, + org_hide_emphasis_markers = true, + org_highlight_latex_and_related = 'entities', + + -- Agenda settings + org_deadline_warning_days = 7, + org_agenda_span = 'week', + org_agenda_start_on_weekday = 7, -- Start a week in Sunday + org_agenda_min_height = 15, + + -- Tags settings + org_use_tag_inheritance = false, + + -- Other stuff + win_border = require('user.config.vars').border, + + -- Notifications + notifications = { + enabled = true, + cron_enabled = false, + repeater_reminder_time = false, + deadline_warning_reminder_time = false, + reminder_time = { 0, 5, 10 }, + deadline_reminder = true, + scheduled_reminder = true, + notifier = function(tasks) + for _, task in ipairs(tasks) do + local task_type = string.format('%s: <%s>', task.type, task.time:to_string()) + local task_description = string.format('%s %s %s', string.rep('*', task.level), task.todo, task.title) + require('lazy.core.util').info(string.format('%s\n%s', task_description, task_type), { + title = { task.category, task.humanized_duration }, + icon = '󱣹 ', + on_open = function(win) + local buf = vim.api.nvim_win_get_buf(win) + vim.api.nvim_buf_set_option(buf, 'filetype', 'org') + end, + }) + end + end, + }, +} + +require('which-key').register { + ['o'] = { + name = 'Org', + i = { name = 'Insert' }, + x = { name = 'Clock' }, + }, +} diff --git a/lua/user/plugins/editor/todo-comments.lua b/lua/user/plugins/editor/todo-comments.lua new file mode 100644 index 0000000..e0fd505 --- /dev/null +++ b/lua/user/plugins/editor/todo-comments.lua @@ -0,0 +1,32 @@ +return { + signs = false, + keywords = { + FIX = { icon = ' ', color = 'error', alt = { 'FIXME', 'BUG', 'FIXIT', 'ISSUE' } }, + TODO = { icon = ' ', color = 'info' }, + HACK = { icon = ' ', color = 'warning' }, + WARN = { icon = ' ', color = 'warning', alt = { 'WARNING', 'XXX' } }, + PERF = { icon = '󰓅 ', alt = { 'OPTIM', 'PERFORMANCE', 'OPTIMIZE' } }, + NOTE = { icon = ' ', color = 'hint', alt = { 'INFO' } }, + }, + merge_keywords = true, + gui_style = { fg = 'BOLD', bg = 'NONE' }, + highlight = { + before = '', + keyword = 'fg', + after = '', + comments_only = true, + exclude = { 'org' }, + }, + colors = { + error = { 'DiagnosticError', '@text.danger' }, + warning = { 'DiagnosticWarn', '@text.warning' }, + info = { 'DiagnosticInfo', '@text.note' }, + hint = { 'DiagnosticHint' }, + default = { 'Normal' }, + test = { 'Identifier' }, + }, + search = { + command = 'rg', + args = require('user.config.vars').rg_args, + }, +} diff --git a/lua/user/plugins/editor/treesitter.lua b/lua/user/plugins/editor/treesitter.lua new file mode 100644 index 0000000..8be6608 --- /dev/null +++ b/lua/user/plugins/editor/treesitter.lua @@ -0,0 +1,37 @@ +return { + parser_install_dir = require('user.config.vars').ts_parsers_path, + ensure_installed = 'all', + sync_install = false, + auto_install = false, + highlight = { + enable = true, + additional_vim_regex_highlighting = { 'org' }, + }, + incremental_selection = { + enable = true, + keymaps = { + init_selection = '', + node_incremental = '', + scope_incremental = '', + node_decremental = '', + }, + }, + indent = { enable = false }, + autotag = { + enable = true, + filetypes = { + 'html', + 'javascript', + 'javascriptreact', + 'typescript', + 'typescriptreact', + 'svelte', + 'vue', + }, + }, + matchup = { + enable = true, + disable_virtual_text = true, + include_match_words = true, + }, +} diff --git a/lua/user/plugins/editor/twilight.lua b/lua/user/plugins/editor/twilight.lua new file mode 100644 index 0000000..b1e58d5 --- /dev/null +++ b/lua/user/plugins/editor/twilight.lua @@ -0,0 +1,29 @@ +return { + dimming = { + alpha = 0.25, + color = { 'Normal', '#ffffff' }, + inactive = false, + }, + context = 10, + treesitter = true, + expand = { + 'function', + 'method', + 'table', + 'if_statement', + }, + exclude = { + 'alpha', + 'lazy', + 'aerial', + 'OverseerForm', + 'TelescopePrompt', + 'notify', + 'undotree', + 'Trouble', + 'NvimTree', + 'diff', + 'qf', + 'help', + }, +} diff --git a/lua/user/plugins/init.lua b/lua/user/plugins/init.lua new file mode 100644 index 0000000..db0ea05 --- /dev/null +++ b/lua/user/plugins/init.lua @@ -0,0 +1,10 @@ +return { + -- lazy.nvim can manage itself + { 'folke/lazy.nvim', version = '*' }, + + -- dependencies needed by the other + 'MunifTanjim/nui.nvim', + 'nvim-lua/plenary.nvim', + 'nvim-tree/nvim-web-devicons', + { 'tpope/vim-repeat', event = { 'BufNewFile', 'BufReadPost' } }, +} diff --git a/lua/user/plugins/lsp/aerial.lua b/lua/user/plugins/lsp/aerial.lua new file mode 100644 index 0000000..ce69147 --- /dev/null +++ b/lua/user/plugins/lsp/aerial.lua @@ -0,0 +1,27 @@ +local vars = require('user.config.vars') + +local icons = {} +for name, val in pairs(vars.icons.kind) do + if val.hl ~= nil then + icons[name] = val.icon .. ' ' + vim.api.nvim_set_hl(0, 'Aerial' .. name .. 'Icon', { link = val.hl }) + end +end +icons['Collapsed'] = '▶' + +return { + backends = { 'lsp', 'treesitter', 'markdown', 'man' }, + layout = { width = 30 }, + show_guides = true, + highlight_on_hover = true, + icons = icons, + filter_kind = false, + float = { border = vars.border, relative = 'editor' }, + nav = { + border = vars.border, + preview = true, + max_width = 0.8, + win_opts = { winblend = 0 }, + keymaps = { ['q'] = 'actions.close' }, + }, +} diff --git a/lua/user/plugins/lsp/conform.lua b/lua/user/plugins/lsp/conform.lua new file mode 100644 index 0000000..8125cf3 --- /dev/null +++ b/lua/user/plugins/lsp/conform.lua @@ -0,0 +1,13 @@ +local linters_path = vim.fn.stdpath('data') .. '/lint' +local util = require('user.util.misc') + +return { + buf = { filetypes = 'proto' }, + fish_indent = { filetypes = 'fish' }, + -- goimports = { filetypes = 'go', condition = util.root_has_file({ '.git', 'go.mod', 'go.work' }) }, + shfmt = { filetypes = 'sh' }, + sql_formatter = { filetypes = 'sql', cmd = linters_path .. '/sql_formatter/node_modules/.bin/sql-formatter' }, + stylua = { filetypes = 'lua', condition = util.root_has_file({ 'stylua.toml', '.stylua.toml' }) }, + templ = { filetypes = 'templ' }, + yamlfmt = { filetypes = { 'yaml', 'yaml.ansible' } }, +} diff --git a/lua/user/plugins/lsp/init.lua b/lua/user/plugins/lsp/init.lua new file mode 100644 index 0000000..083e4c8 --- /dev/null +++ b/lua/user/plugins/lsp/init.lua @@ -0,0 +1,117 @@ +return { + { + 'neovim/nvim-lspconfig', + event = { 'BufReadPost', 'BufNewFile' }, + dependencies = { + 'b0o/SchemaStore.nvim', + { 'folke/neoconf.nvim', cmd = 'Neoconf', config = true }, + { 'https://git.sr.ht/~whynothugo/lsp_lines.nvim', config = true }, + { + 'SmiteshP/nvim-navic', + config = function() + require('user.plugins.lsp.navic') + end, + }, + }, + config = function() + require('user.plugins.lsp.lspconfig') + end, + }, + { + 'mfussenegger/nvim-lint', + event = { 'BufReadPost', 'BufNewFile', 'BufWritePre' }, + opts = function() return require('user.plugins.lsp.lint') end, + config = function(_, opts) + local lint = require('lint') + + local function debounce(ms, fn) + local timer = vim.loop.new_timer() + return function(...) + local argv = { ... } + timer:start(ms, 0, function() + timer:stop() + vim.schedule_wrap(fn)(unpack(argv)) + end) + end + end + + -- Linters are invoked explicitly, so don't need to define `lint.linters_by_ft` + local function try_lint() + local ft = vim.bo.filetype + local names = lint._resolve_linter_by_ft(ft) + + local additional_linters = { fallback = {}, global = {} } + for name, conf in pairs(opts) do + local enabled_ft = type(conf.filetypes) == 'table' and conf.filetypes or { conf.filetypes } + if vim.tbl_contains(enabled_ft, ft) then + vim.list_extend(names, { name } or {}) + end + + -- * and _ is treated like in `conform.nvim` (assume they are defined as the 1st table element) + if enabled_ft[1] == '_' then + vim.list_extend(additional_linters.fallback, { name } or {}) + end + if enabled_ft[1] == '*' then + vim.list_extend(additional_linters.global, { name } or {}) + end + end + + -- Add fallback and global linters + if #names == 0 then + vim.list_extend(names, additional_linters.fallback or {}) + end + vim.list_extend(names, additional_linters.global or {}) + + -- Filter out linters that don't exist or don't match the defined condition + local file = vim.api.nvim_buf_get_name(0) + names = vim.tbl_filter(function(name) + local conf = opts[name] + return conf and not (type(conf) == 'table' and conf.condition and not conf.condition(file)) + end, names) + + -- Run linters + if #names > 0 then + lint.try_lint(names) + end + end + + -- Add custom linter configurations to the plugin's configuration + for name, conf in pairs(opts) do + if type(conf) == 'table' and type(lint.linters[name]) == 'table' then + lint.linters[name] = vim.tbl_deep_extend('force', lint.linters[name], conf) + else + lint.linters[name] = conf + end + + -- Remove our custom fields + lint.linters[name].filetypes = nil + lint.linters[name].condition = nil + end + + vim.api.nvim_create_autocmd({ 'BufWritePost', 'BufReadPost', 'InsertLeave' }, { + group = vim.api.nvim_create_augroup('nvim-lint', { clear = true }), + callback = debounce(100, try_lint), + }) + end, + }, + { + 'stevearc/conform.nvim', + cmd = 'ConformInfo', + keys = {}, + opts = function() return require('user.plugins.lsp.conform') end, + config = function(_, opts) + end, + }, + { 'smjonas/inc-rename.nvim', cmd = 'IncRename', opts = { hl_group = 'IncSearch' } }, + { + 'stevearc/aerial.nvim', + keys = { + { 'lg', 'AerialToggle', desc = 'Symbols' }, + { 'lG', 'AerialNavToggle', desc = 'Symbols (Nav)' }, + }, + cmd = { 'AerialToggle', 'AerialNavToggle' }, + opts = function() + return require('user.plugins.lsp.aerial') + end, + }, +} diff --git a/lua/user/plugins/lsp/lint.lua b/lua/user/plugins/lsp/lint.lua new file mode 100644 index 0000000..c866974 --- /dev/null +++ b/lua/user/plugins/lsp/lint.lua @@ -0,0 +1,29 @@ +local linters_path = vim.fn.stdpath('data') .. '/lint' +local util = require('user.util.misc') + +-- `condition` and `filetypes` are custom fields. +-- The other is the same as in `require('lint').linters.` +return { + buf = { filetypes = 'proto' }, + buildifier = { filetypes = 'bzl' }, + -- codespell = { filetypes = '*', cmd = linters_path .. '/codespell/bin/codespell' }, + cspell = { + cmd = linters_path .. '/cspell/node_modules/.bin/cspell', + filetypes = '*', + condition = util.root_has_file({ + '.cspell.json', + 'cspell.json', + '.cSpell.json', + 'cSpell.json', + 'cspell.config.json', + }), + }, + fish = { filetypes = 'fish', args = { '--private', '--no-execute' } }, + -- hadolint = { filetypes = 'dockerfile' }, + -- proselint = { filetypes = { 'markdown', 'asciidoc', 'org' }, cmd = linters_path .. '/proselint/bin/proselint' }, + selene = { filetypes = 'lua', condition = util.root_has_file('selene.toml') }, + sqlfluff = { filetypes = 'sql', cmd = linters_path .. '/sqlfluff/bin/sqlfluff' }, + tflint = { filetypes = 'terraform', condition = util.root_has_file('.tflint.hcl') }, + tfsec = { filetypes = 'terraform' }, + vale = { filetypes = 'markdown', condition = util.root_has_file('.vale.ini') }, +} diff --git a/lua/user/plugins/lsp/lspconfig/diagnostic.lua b/lua/user/plugins/lsp/lspconfig/diagnostic.lua new file mode 100644 index 0000000..bd9d57b --- /dev/null +++ b/lua/user/plugins/lsp/lspconfig/diagnostic.lua @@ -0,0 +1,90 @@ +local M = {} +local lazy_util = require('lazy.core.util') +local vars = require('user.config.vars') + +-- TODO: nvim-0.10.0 can have different virtual_text prefix per severity +local function set_virtual_text() + if vars.lsp_virtual_lines then + return false + end + return { prefix = '■', spacing = 2, source = 'if_many' } +end + +function M.configure_diagnostic() + vim.diagnostic.config { + virtual_text = set_virtual_text(), + virtual_lines = vars.lsp_virtual_lines, + severity_sort = true, + float = { + border = vars.border, + prefix = function(diag) + local level = vim.diagnostic.severity[diag.severity] + local prefix = string.format('%s ', vars.icons.notify[level:lower()]) + return prefix, 'Diagnostic' .. level:gsub('^%l', string.upper) + end, + }, + } + -- lsp_lines is annoying in LazyUI, so always disable it there + vim.diagnostic.config({ virtual_lines = false }, require('lazy.core.config').ns) + + -- :help diagnostic-handlers-example + -- Display only 1 sign from the highest severity + local ns = vim.api.nvim_create_namespace('customDiagnosticSign_ns') + local orig_signs_handler = vim.diagnostic.handlers.signs + vim.diagnostic.handlers.signs = { + show = function(_, bufnr, _, opts) + local diagnostics = vim.diagnostic.get(bufnr) + + local max_severity_per_line = {} + for _, d in pairs(diagnostics) do + local m = max_severity_per_line[d.lnum] + if not m or d.severity < m.severity then + max_severity_per_line[d.lnum] = d + end + end + + local filtered_diagnostics = vim.tbl_values(max_severity_per_line) + orig_signs_handler.show(ns, bufnr, filtered_diagnostics, opts) + end, + hide = function(_, bufnr) + orig_signs_handler.hide(ns, bufnr) + end, + } + + -- Set diagnostic sign icons + for _, name in ipairs { 'error', 'warn', 'hint', 'info' } do + local icon = vars.icons.notify[name] + local hl = 'DiagnosticSign' .. name:gsub('^%l', string.upper) + vim.fn.sign_define(hl, { text = icon, texthl = hl }) + end +end + +function M.toggle_virtual_lines() + vars.lsp_virtual_lines = not vars.lsp_virtual_lines + + -- Configure diagnostic again for the new settings + vim.diagnostic.config { + virtual_text = set_virtual_text(), + virtual_lines = vars.lsp_virtual_lines + } + + if vars.lsp_virtual_lines then + lazy_util.info('Virtual lines enabled', { title = 'Diagnostic' }) + else + lazy_util.info('Virtual lines disabled', { title = 'Diagnostic' }) + end +end + +local diagnostic_enabled = true +function M.toggle() + diagnostic_enabled = not diagnostic_enabled + if diagnostic_enabled then + vim.diagnostic.enable() + lazy_util.info('LSP diagnostic enabled', { title = 'Diagnostic' }) + else + vim.diagnostic.disable() + lazy_util.warn('LSP diagnostic disabled', { title = 'Diagnostic' }) + end +end + +return M diff --git a/lua/user/plugins/lsp/lspconfig/format.lua b/lua/user/plugins/lsp/lspconfig/format.lua new file mode 100644 index 0000000..6fa2df5 --- /dev/null +++ b/lua/user/plugins/lsp/lspconfig/format.lua @@ -0,0 +1,41 @@ +local M = {} +local lazy_util = require('lazy.core.util') +local vars = require('user.config.vars') + +function M.toggle() + vars.lsp_format_on_save = not vars.lsp_format_on_save + if vars.lsp_format_on_save then + lazy_util.info('Enabled format on save', { title = 'Format' }) + else + lazy_util.warn('Disabled format on save', { title = 'Format' }) + end +end + +function M.format() + local buf = vim.api.nvim_get_current_buf() + vim.lsp.buf.format { async = true, bufnr = buf } +end + +function M.on_attach(client, buf) + if + client.config + and client.config.capabilities + and client.config.capabilities.documentFormattingProvider == false + then + return + end + + if client.supports_method('textDocument/formatting') then + vim.api.nvim_create_autocmd('BufWritePre', { + group = vim.api.nvim_create_augroup('LspFormat', {}), + buffer = buf, + callback = function() + if vars.lsp_format_on_save then + M.format() + end + end, + }) + end +end + +return M diff --git a/lua/user/plugins/lsp/lspconfig/init.lua b/lua/user/plugins/lsp/lspconfig/init.lua new file mode 100644 index 0000000..7e59b1c --- /dev/null +++ b/lua/user/plugins/lsp/lspconfig/init.lua @@ -0,0 +1,80 @@ +local vars = require('user.config.vars') + +-- vim.lsp.set_log_level(vars.loglevel) +require('lspconfig.ui.windows').default_options.border = vars.border +require('user.plugins.lsp.lspconfig.diagnostic').configure_diagnostic() + +vim.api.nvim_create_autocmd('LspAttach', { + group = vim.api.nvim_create_augroup('UserLspConfig', {}), + callback = function(args) + local buffer = args.buf + local client = vim.lsp.get_client_by_id(args.data.client_id) + + -- Formatting + Key bindings + require('user.plugins.lsp.lspconfig.format').on_attach(client, buffer) + require('user.plugins.lsp.lspconfig.keymap').on_attach(client, buffer) + + -- Symbol context on winbar + if client.supports_method('textDocument/documentSymbol') then + require('nvim-navic').attach(client, buffer) + end + + -- Inlay hints + local inlay_hint = vim.lsp.buf.inlay_hint or vim.lsp.inlay_hint + if vars.lsp_inlay_hints and inlay_hint then + if client.supports_method('textDocument/inlayHint') then + inlay_hint(buffer, true) + end + end + + -- Bulb for code action hint in statusbar + if client.supports_method('textDocument/codeAction') then + vim.api.nvim_create_autocmd({ 'CursorHold', 'InsertLeave' }, { + group = vim.api.nvim_create_augroup('LspCodeAction', {}), + callback = require('user.util.lsp').codeAction_on_attach, + buffer = buffer, + }) + end + + -- Refresh codelens + if client.supports_method('textDocument/codeLens') then + vim.api.nvim_create_autocmd({ 'InsertLeave', 'BufEnter', 'CursorHold' }, { + group = vim.api.nvim_create_augroup('LspCodeLens', {}), + callback = vim.lsp.codelens.refresh, + buffer = buffer, + }) + end + + -- Document highlight + if vars.lsp_document_highlight and client.supports_method('textDocument/documentHighlight') then + local augroup = 'LspDocumentHighlight' + + vim.api.nvim_create_augroup(augroup, {}) + vim.api.nvim_create_autocmd({ 'CursorHold', 'CursorHoldI' }, { + group = augroup, + callback = vim.lsp.buf.document_highlight, + buffer = buffer, + }) + vim.api.nvim_create_autocmd('CursorMoved', { + group = augroup, + callback = vim.lsp.buf.clear_references, + buffer = buffer, + }) + end + + -- Specific tweaks for some servers + if client.name == 'ruff_lsp' then + client.server_capabilities.hoverProvider = false + end + end, +}) + +local servers = require('user.plugins.lsp.lspconfig.servers') +local capabilities = require('cmp_nvim_lsp').default_capabilities(vim.lsp.protocol.make_client_capabilities()) +for server, server_opts in pairs(servers) do + local opts = vim.tbl_deep_extend('force', { + capabilities = vim.deepcopy(capabilities), + }, server_opts or {}) + + require('lspconfig')[server].setup(opts) +end diff --git a/lua/user/plugins/lsp/lspconfig/keymap.lua b/lua/user/plugins/lsp/lspconfig/keymap.lua new file mode 100644 index 0000000..2fb9579 --- /dev/null +++ b/lua/user/plugins/lsp/lspconfig/keymap.lua @@ -0,0 +1,78 @@ +local M = {} + +M._keys = nil + +function M.get() + local format = function() + require('user.plugins.lsp.lspconfig.format').format() + end + + if not M._keys then + M._keys = { + { 'le', vim.diagnostic.open_float, desc = 'Line diagnostic' }, + { 'lf', format, desc = 'Format document', has = 'documentFormatting' }, + { 'lf', format, desc = 'Format range', mode = 'x', has = 'documentRangeFormatting' }, + { 'la', vim.lsp.buf.code_action, desc = 'Code action', mode = { 'n', 'x' }, has = 'codeAction' }, + { 'll', vim.lsp.codelens.run, desc = 'Codelens action', mode = 'n', has = 'codeLens' }, + { 'ld', 'Trouble document_diagnostics', desc = 'Document diagnostics' }, + { 'lw', 'Trouble workspace_diagnostics', desc = 'Workspace diagnostics' }, + + { 'gd', 'Trouble lsp_definitions', desc = 'Goto definition', has = 'definition' }, + { 'gr', 'Trouble lsp_references', desc = 'References' }, + { 'gD', vim.lsp.buf.declaration, desc = 'Goto declaration' }, + { 'gI', 'Trouble lsp_implementations', desc = 'Goto implementation' }, + { 'gy', 'Trouble lsp_type_definitions', desc = 'Goto type definition' }, + { 'K', vim.lsp.buf.hover, desc = 'Hover' }, + { 'gK', vim.lsp.buf.signature_help, desc = 'Signature help', has = 'signatureHelp' }, + { '', vim.lsp.buf.signature_help, mode = 'i', desc = 'Signature help', has = 'signatureHelp' }, + { ']d', M.diagnostic_goto(true), desc = 'Next diagnostic' }, + { '[d', M.diagnostic_goto(false), desc = 'Previous diagnostic' }, + { ']e', M.diagnostic_goto(true, 'ERROR'), desc = 'Next error' }, + { '[e', M.diagnostic_goto(false, 'ERROR'), desc = 'Previous error' }, + { ']w', M.diagnostic_goto(true, 'WARN'), desc = 'Next warning' }, + { '[w', M.diagnostic_goto(false, 'WARN'), desc = 'Previous warning' }, + } + + if require('user.util.misc').has('inc-rename.nvim') then + M._keys[#M._keys + 1] = { + 'lr', + function() + local inc_rename = require('inc_rename') + return ':' .. inc_rename.config.cmd_name .. ' ' .. vim.fn.expand('') + end, + expr = true, + desc = 'Rename', + has = 'rename', + } + else + M._keys[#M._keys + 1] = { 'lr', vim.lsp.buf.rename, desc = 'Rename', has = 'rename' } + end + end + return M._keys +end + +function M.on_attach(client, buffer) + local Keys = require('lazy.core.handler.keys') + local keymaps = Keys.resolve(M.get()) + + for _, keys in pairs(keymaps) do + local method = keys.has + if not keys.has or client.supports_method(method:find('/') and method or 'textDocument/' .. method) then + local opts = Keys.opts(keys) + opts.has = nil + opts.silent = opts.silent ~= false + opts.buffer = buffer + vim.keymap.set(keys.mode or 'n', keys.lhs, keys.rhs, opts) + end + end +end + +function M.diagnostic_goto(next, severity) + local go = next and vim.diagnostic.goto_next or vim.diagnostic.goto_prev + severity = severity and vim.diagnostic.severity[severity] or nil + return function() + go { severity = severity } + end +end + +return M diff --git a/lua/user/plugins/lsp/lspconfig/servers.lua b/lua/user/plugins/lsp/lspconfig/servers.lua new file mode 100644 index 0000000..44c3372 --- /dev/null +++ b/lua/user/plugins/lsp/lspconfig/servers.lua @@ -0,0 +1,226 @@ +local servers_path = vim.fn.stdpath('data') .. '/lsp' + +-- NOTE: check https://github.com/folke/neodev.nvim for more bloated library/runtime path +local function lua_lib_path() + local lib_path = {} + lib_path[os.getenv('VIMRUNTIME') .. '/lua'] = true + -- emmylua-nvim + lib_path[vim.fn.stdpath('data') .. '/lazy/emmylua-nvim'] = true + return lib_path +end + +local function lua_runtime_path() + local runtime_path = {} + table.insert(runtime_path, 'lua/?.lua') + table.insert(runtime_path, 'lua/?/init.lua') + + local lib_path = lua_lib_path() + for lib, _ in pairs(lib_path) do + table.insert(runtime_path, lib .. '/?.lua') + table.insert(runtime_path, lib .. '/?/init.lua') + end + + return runtime_path +end + +-- Read a file line by line and build a table from them +---@param file string +local function read_dict(file) + if not file then + return nil + end + local dict = {} + for line in io.lines(file) do + table.insert(dict, line) + end + return dict +end + +return { + beancount = { init_options = { journalFile = os.getenv('HOME') .. '/Documents/Ledger/ledger.beancount' } }, + lua_ls = { + settings = { + Lua = { + codeLens = { enable = true }, + diagnostics = { globals = { 'vim', 'it', 'describe' } }, + format = { enable = false }, + hint = { enable = true, arrayIndex = 'Disable', setType = true }, + runtime = { version = 'LuaJIT', path = lua_runtime_path() }, + completion = { callSnippet = 'Replace', keywordSnippet = 'Replace' }, + workspace = { checkThirdParty = false, library = lua_lib_path() }, + telemetry = { enable = false }, + }, + }, + }, + -- TODO: switch to pylyzer when it is more stable + pylsp = { + cmd = { servers_path .. '/pylsp/bin/pylsp' }, + settings = { + pylsp = { + plugins = { + autopep8 = { enabled = false }, + pydocstyle = { enabled = true }, + pylint = { enabled = true }, + }, + }, + }, + }, + -- NOTE: for Bazel setup (GOPACKAGESDRIVER), use neoconf.nvim + -- Ref: https://github.com/bazelbuild/rules_go/wiki/Editor-setup + gopls = { + cmd = { 'gopls', '-remote=auto', 'serve' }, + settings = { + gopls = { + gofumpt = true, + codelenses = { + gc_details = false, + generate = true, + regenerate_cgo = true, + run_govulncheck = true, + tidy = true, + upgrade_dependency = true, + vendor = true, + }, + hints = { + assignVariableTypes = true, + compositeLiteralFields = true, + compositeLiteralTypes = true, + constantValues = true, + functionTypeParameters = true, + parameterNames = true, + rangeVariableTypes = true, + }, + analyses = { + fieldalignment = true, + nilness = true, + shadow = true, + unusedparams = true, + unusedvariable = true, + unusedwrite = true, + useany = true, + }, + usePlaceholders = true, + staticcheck = true, + vulncheck = 'Imports', + directoryFilters = { + '-.git', + '-.vscode', + '-.vscode-test', + '-.idea', + '-node_modules', + '-plz-out', + '-.plz-cache', + '-.plz-http-cache', + '-bazel-bin', + '-bazel-out', + '-bazel-testlogs', + }, + }, + }, + }, + jsonls = { + cmd = { servers_path .. '/vscode/node_modules/.bin/vscode-json-language-server', '--stdio' }, + -- Schema catalog: https://www.schemastore.org/api/json/catalog.json + settings = { + json = { + schemas = require('schemastore').json.schemas(), + validate = { enable = true }, + }, + }, + }, + yamlls = { + cmd = { servers_path .. '/yamlls/node_modules/.bin/yaml-language-server', '--stdio' }, + settings = { + redhat = { telemetry = { enabled = false } }, + yaml = { + format = { + enable = true, + bracketSpacing = true, + printWidth = 120, + singleQuote = true, + }, + validate = true, + schemas = require('schemastore').yaml.schemas(), + schemaStore = { enable = false, url = "" }, + -- https://docs.gitlab.com/ee/ci/yaml/yaml_optimization.html#configure-your-ide-to-support-reference-tags + customTags = { '!reference sequence' }, + }, + }, + }, + texlab = { + settings = { + texlab = { + build = { onSave = true }, + forwardSearch = { + executable = 'zathura', + args = { '--synctex-forward', '%l:1:%f', '%p' }, + }, + }, + }, + }, + -- ltex = { + -- settings = { + -- ltex = { + -- dictionary = { + -- ['en-US'] = read_dict(vim.fn.stdpath('config') .. '/spell/en.utf-8.add'), + -- }, + -- completionEnabled = true, + -- }, + -- }, + -- }, + nil_ls = { + settings = { + ['nil'] = { + formatting = { + command = { 'nixpkgs-fmt' }, + }, + }, + }, + }, + -- volar = { + -- { cmd = { servers_path .. '/volar/node_modules/.bin/vue-language-server', '--stdio' } }, + -- filetypes = { 'typescript', 'javascript', 'javascriptreact', 'typescriptreact', 'vue' }, + -- on_new_config = function(config, root_dir) + -- local lsputil = require('lspconfig.util') + -- local function get_typescript_server_path(dir) + -- local global_ts = vim.fn.stdpath('data') .. '/lsp/volar/node_modules/typescript/lib' + -- local found_ts = '' + + -- if lsputil.search_ancestors(dir, function(path) + -- found_ts = lsputil.path.join(path, 'node_modules', 'typescript', 'lib') + -- if lsputil.path.exists(found_ts) then return path end + -- end) then + -- return found_ts + -- end + -- return global_ts + -- end + + -- config.init_options.typescript.tsdk = get_typescript_server_path(root_dir) + -- end, + -- }, + ansiblels = { cmd = { servers_path .. '/ansiblels/node_modules/.bin/ansible-language-server', '--stdio' } }, + bashls = { cmd = { servers_path .. '/bashls/node_modules/.bin/bash-language-server', 'start' } }, + cssls = { cmd = { servers_path .. '/vscode/node_modules/.bin/vscode-css-language-server', '--stdio' } }, + dockerls = { cmd = { servers_path .. '/dockerls/node_modules/.bin/docker-langserver', '--stdio' } }, + emmet_language_server = { cmd = { servers_path .. '/emmet_language_server/node_modules/.bin/emmet-language-server', '--stdio' } }, + html = { cmd = { servers_path .. '/vscode/node_modules/.bin/vscode-html-language-server', '--stdio' } }, + -- please = { cmd = { 'build_langserver', '--mode', 'stdio' } }, + ruff_lsp = { cmd = { servers_path .. '/ruff_lsp/bin/ruff-lsp' } }, + sqlls = { cmd = { servers_path .. '/sqlls/node_modules/.bin/sql-language-server', 'up', '--method', 'stdio' } }, + -- svelte = { cmd = { servers_path .. '/svelte/node_modules/.bin/svelteserver', '--stdio' } }, + tsserver = { cmd = { servers_path .. '/tsserver/node_modules/.bin/typescript-language-server', '--stdio' } }, + dhall_lsp_server = {}, + gleam = {}, + jsonnet_ls = {}, + marksman = {}, + nickel_ls = {}, + -- nixd = {}, + -- pylyzer = {}, + qmlls = {}, -- from qt6-qtdeclarative-dev package in Alpine + regols = {}, + biome = {}, + taplo = {}, + terraformls = {}, + typst_lsp = {}, + zls = {}, +} diff --git a/lua/user/plugins/lsp/navic.lua b/lua/user/plugins/lsp/navic.lua new file mode 100644 index 0000000..a105f59 --- /dev/null +++ b/lua/user/plugins/lsp/navic.lua @@ -0,0 +1,18 @@ +local lspkind_icons = require('user.config.vars').icons.kind + +local navic_icons = {} +for key, val in pairs(lspkind_icons) do + if val.hl ~= nil then + navic_icons[key] = val.icon .. ' ' + vim.api.nvim_set_hl(0, 'NavicIcons' .. key, { link = val.hl }) + end +end + +require('nvim-navic').setup { + icons = navic_icons, + highlight = true, + separator = '  ', + depth_limit = 0, + depth_limit_indicator = '..', + lazy_update_context = true, +} diff --git a/lua/user/plugins/ui/alpha.lua b/lua/user/plugins/ui/alpha.lua new file mode 100644 index 0000000..1e8247e --- /dev/null +++ b/lua/user/plugins/ui/alpha.lua @@ -0,0 +1,124 @@ +local header = { + type = 'text', + val = { + [[<-. (`-')_ (`-') _ (`-') _ <-. (`-') ]], + [[ \( OO) ) ( OO).-/ .-> _(OO ) (_) \(OO )_ ]], + [[,--./ ,--/ (,------.(`-')----. ,--.(_/,-.\ ,-(`-'),--./ ,-.)]], + [[| \ | | | .---'( OO).-. '\ \ / (_/ | ( OO)| `.' |]], + [[| . '| |)(| '--. ( _) | | | \ / / | | )| |'.'| |]], + [[| |\ | | .--' \| |)| |_ \ /_)(| |_/ | | | |]], + [[| | \ | | `---. ' '-' '\-'\ / | |'->| | | |]], + [[`--' `--' `------' `-----' `-' `--' `--' `--']], + }, + opts = { + position = 'center', + hl = 'DashboardHeader', + }, +} + +local footers = { + [[☆*:.。. o(≧▽≦)o .。.:*☆]], + [[°˖✧ ◝(⁰▿⁰)◜✧˖°]], + [[✿ ♬ ゚+.(。◡‿◡)♪.+ ゚♬ ✿ ]], + [[— ฅ/ᐠ. ̫ .ᐟ\ฅ —]], + [[/ᐠ_ ꞈ _ᐟ\ɴʏᴀ~]], + [[૮₍˶ •. • ⑅₎ა]], + [[︶꒷꒦︶ ๋࣭ ⭑ ૮₍˶• . • ⑅₎ა]], + [[。*゚.*.。(っ ᐛ )っ 𝖍𝖎]], + [[ₓ 。 𐐪₍ᐢ. ̫ .⑅ᐢ₎𐑂↝]], + [[ଘ(੭ *ˊᵕˋ)੭ * ੈ♡‧₊˚]], +} + +math.randomseed(os.time()) +local footer = { + type = 'text', + val = footers[math.random(#footers)], + opts = { + position = 'center', + hl = 'DashboardFooter', + }, +} + +local button = function(sc, txt, keybind, keybind_opts) + local sc_ = sc:gsub('%s', ''):gsub('SPC', '') + + local opts = { + position = 'center', + shortcut = sc, + cursor = 3, + width = 50, + align_shortcut = 'right', + hl = 'DashboardCenter', + hl_shortcut = 'DashboardShortcut', + } + if keybind then + keybind_opts = vim.F.if_nil(keybind_opts, { noremap = true, silent = true, nowait = true }) + opts.keymap = { 'n', sc_, keybind, keybind_opts } + end + + local function on_press() + local key = vim.api.nvim_replace_termcodes(sc_ .. '', true, false, true) + vim.api.nvim_feedkeys(key, 'normal', false) + end + + return { + type = 'button', + val = txt, + on_press = on_press, + opts = opts, + } +end + +local buttons = { + type = 'group', + val = { + button('SPC /', ' Live grep'), + button('SPC f m', ' Bookmarks'), + button('SPC f f', ' Find file'), + button('SPC f r', ' Recent files'), + button('SPC f p', ' Recent projects'), + button('SPC p l', ' Load last session'), + button('SPC SPC', ' Plugin manager'), + }, + opts = { spacing = 1 }, +} + +local loaded = { + type = 'text', + val = '', -- this is overwritten in the autocmd below + opts = { + position = 'center', + hl = 'DashboardFooter', + }, +} +-- Redraw the dashboard to get Lazy stats and overwrited 'loaded' value +vim.api.nvim_create_autocmd('User', { + pattern = 'LazyVimStarted', + callback = function() + local stats = require('lazy').stats() + loaded.val = '🎉 Neovim loaded ' + .. stats.count + .. ' plugins in ' + .. (math.floor(stats.startuptime * 100 + 0.5) / 100) + .. 'ms' + pcall(vim.cmd.AlphaRedraw) + end, +}) + +return { + layout = { + { type = 'padding', val = 7 }, + header, + { type = 'padding', val = 2 }, + buttons, + loaded, + { type = 'padding', val = 1 }, + footer, + { type = 'padding', val = 7 }, + }, + opts = { + margin = 5, + noautocmd = true, + redraw_on_resize = true, + }, +} diff --git a/lua/user/plugins/ui/cokeline.lua b/lua/user/plugins/ui/cokeline.lua new file mode 100644 index 0000000..b95ab94 --- /dev/null +++ b/lua/user/plugins/ui/cokeline.lua @@ -0,0 +1,135 @@ +local colors = require('user.themes.' .. vim.g.colors_name .. '.colors') +local mappings = require('cokeline.mappings') + +local components = { + separator = { + text = function(buffer) + return buffer.index ~= 1 and '┃' or '' + end, + fg = 'TabLineSel', + truncation = { priority = 2 }, + }, + space = { text = ' ', truncation = { priority = 2 } }, + devicon = { + text = function(buffer) + return (mappings.is_picking_focus() or mappings.is_picking_close()) and (buffer.pick_letter .. ' ') or (buffer.devicon.icon .. ' ') + end, + fg = function(buffer) + if mappings.is_picking_close() then + return buffer.is_focused and colors.yellow or colors.red + end + if mappings.is_picking_focus() then + return buffer.is_focused and colors.yellow or colors.blue + end + return buffer.devicon.color + end, + style = function() + return (mappings.is_picking_focus() or mappings.is_picking_close()) and 'bold,italic' or nil + end, + truncation = { priority = 3 }, + }, + unique_prefix = { + text = function(buffer) + return buffer.unique_prefix + end, + fg = 'Comment', + style = 'italic', + truncation = { priority = 4, direction = 'left' }, + }, + filename = { + text = function(buffer) + return buffer.filename .. ' ' + end, + style = function(buffer) + return buffer.is_focused and 'bold' or 'italic' + end, + truncation = { priority = 1, direction = 'left' }, + }, + file_readonly = { + text = function(buffer) + return (buffer.is_readonly or not vim.api.nvim_buf_get_option(buffer.number, 'modifiable')) and ' ' or '' + end, + fg = colors.red, + truncation = { priority = 3, direction = 'left' }, + }, + close_or_modified = { + text = function(buffer) + return buffer.is_modified and ' ' or ' ' + end, + fg = function(buffer) + return buffer.is_modified and colors.green or 'Comment' + end, + delete_buffer_on_left_click = true, + truncation = { priority = 3 }, + }, +} + +local buffer_width = 28 +local get_remaining_space = function(buffer) + local used_space = 0 + for _, component in pairs(components) do + used_space = used_space + vim.fn.strwidth( + (type(component.text) == 'string' and component.text) + or (type(component.text) == 'function' and component.text(buffer)) + ) + end + return math.max(0, buffer_width - used_space) +end +local left_padding = { + text = function(buffer) + local remaining_space = get_remaining_space(buffer) + return string.rep(' ', remaining_space / 2 + remaining_space % 2) + end, +} +local right_padding = { + text = function(buffer) + local remaining_space = get_remaining_space(buffer) + return string.rep(' ', remaining_space / 2) + end, +} + +return { + default_hl = { + fg = function(buffer) + return buffer.is_focused and 'Normal' or 'Comment' + end, + bg = 'NONE', + }, + fill_hl = 'TabLineFill', + rendering = { max_buffer_width = buffer_width }, + components = { + components.separator, + left_padding, + components.space, + components.devicon, + components.unique_prefix, + components.file_readonly, + components.filename, + right_padding, + components.close_or_modified, + }, + tabs = { + placement = 'left', + components = { + { + text = function(tabpage) + return string.format(" %s ", tabpage.number) + end, + on_click = function(_, _, _, _, tabpage) + tabpage:focus() + end, + highlight = function(tabpage) + return tabpage.is_active and 'TabLineSel' or 'TabLine' + end, + truncation = { priority = 2 }, + }, + }, + }, + rhs = { + { + text = '  ' .. vim.fn.fnamemodify(vim.fn.getcwd(), ':t') .. ' ', + highlight = 'TabLineSel', + truncation = { priority = 2 }, + }, + }, +} diff --git a/lua/user/plugins/ui/dressing.lua b/lua/user/plugins/ui/dressing.lua new file mode 100644 index 0000000..e637287 --- /dev/null +++ b/lua/user/plugins/ui/dressing.lua @@ -0,0 +1,21 @@ +local vars = require('user.config.vars') + +return { + input = { enabled = false }, -- avoid loading nvim-cmp needlessly. Also, noice.nvim has nice input UI already + select = { + backend = { 'telescope', 'builtin', 'nui' }, + nui = { + border = { style = vars.border }, + win_options = { winblend = 0 }, + }, + builtin = { + border = vars.border, + win_options = { winblend = 0 }, + mappings = { + ['q'] = 'Close', + [''] = 'Close', + [''] = 'Confirm', + }, + }, + }, +} diff --git a/lua/user/plugins/ui/heirline/components.lua b/lua/user/plugins/ui/heirline/components.lua new file mode 100644 index 0000000..266e11d --- /dev/null +++ b/lua/user/plugins/ui/heirline/components.lua @@ -0,0 +1,321 @@ +local M = {} +local conditions = require('heirline.conditions') +local utils = require('heirline.utils') +local misc_utils = require('user.util.misc') +local lsp_utils = require('user.util.lsp') +local vars = require('user.config.vars') + +M.align = { provider = '%=' } + +M.dummy = { provider = '▊ ', hl = { fg = 'blue' } } + +local vi_mode = { + n = { name = 'NORMAL', color = 'green' }, + no = { name = 'OP', color = 'green' }, + nov = { name = 'OP(v)', color = 'green' }, + noV = { name = 'OP(V)', color = 'green' }, + ['no\22'] = { name = 'OP(^V)', color = 'green' }, + niI = { name = 'NORMAL(I)', color = 'green' }, + niR = { name = 'NORMAL(R)', color = 'green' }, + niV = { name = 'NORMAL(V)', color = 'green' }, + nt = { name = 'NORMAL(T)', color = 'green' }, + ntT = { name = 'NORMAL(T)', color = 'green' }, + v = { name = 'VISUAL', color = 'yellow' }, + vs = { name = 'VISUAL(s)', color = 'yellow' }, + V = { name = 'V-LINE', color = 'yellow' }, + Vs = { name = 'V-LINE(s)', color = 'yellow' }, + ['\22'] = { name = 'V-BLOCK', color = 'yellow' }, + ['\22s'] = { name = 'V-BLOCK(s)', color = 'yellow' }, + s = { name = 'SELECT', color = 'orange' }, + S = { name = 'S-LINE', color = 'orange' }, + ['\19'] = { name = 'S-BLOCK', color = 'orange' }, + i = { name = 'INSERT', color = 'blue' }, + ic = { name = 'INSERT(c)', color = 'blue' }, + ix = { name = 'INSERT(x)', color = 'blue' }, + R = { name = 'REPLACE', color = 'red' }, + Rc = { name = 'REPLACE(c)', color = 'red' }, + Rx = { name = 'REPLACE(x)', color = 'red' }, + Rv = { name = 'REPLACE(v)', color = 'red' }, + Rvc = { name = 'REPLACE(vc)', color = 'red' }, + Rvx = { name = 'REPLACE(vx)', color = 'red' }, + c = { name = 'COMMAND', color = 'purple' }, + cv = { name = 'EX', color = 'purple' }, + r = { name = 'ENTER', color = 'cyan' }, + rm = { name = 'MORE', color = 'cyan' }, + ['r?'] = { name = 'CONFIRM', color = 'cyan' }, + ['!'] = { name = 'SHELL', color = 'fg' }, + t = { name = 'TERM', color = 'fg' }, +} +local get_vi_mode = function(spec) + local vals = {} + for k, v in pairs(vi_mode) do + vals[k] = v[spec] + end + return vals +end + +M.vi_mode = { + init = function(self) + self.mode = vim.fn.mode(1) + end, + static = { + mode_names = get_vi_mode('name'), + mode_colors = get_vi_mode('color'), + }, + provider = function(self) + return ' ' .. self.mode_names[self.mode] .. ' ' + end, + hl = function(self) + local mode = self.mode:sub(1, 1) + return { fg = self.mode_colors[mode], bold = true } + end, + update = { + 'ModeChanged', + pattern = '*:*', + callback = vim.schedule_wrap(function() + vim.cmd('redrawstatus') + end), + }, +} + +M.file_size = { + condition = misc_utils.buffer_not_empty, + provider = function() + local suffix = { 'b', 'k', 'M', 'G', 'T', 'P', 'E' } + local fsize = vim.fn.getfsize(vim.api.nvim_buf_get_name(0)) + fsize = fsize < 0 and 0 or fsize + if fsize == 0 then + return '' + end + if fsize < 1024 then + return fsize .. suffix[1] .. ' ' + end + local i = math.floor((math.log(fsize) / math.log(1024))) + return string.format('%.2g%s ', fsize / math.pow(1024, i), suffix[i + 1]) + end, + hl = { fg = 'blue', bold = true }, +} + +M.file_format = { + condition = misc_utils.buffer_not_empty, + provider = function() + local os = vim.bo.fileformat:upper() + local icon = '' + if os == 'UNIX' then + icon = ' ' + elseif os == 'MAC' then + icon = ' ' + else + icon = ' ' + end + return ' ' .. icon .. os + end, + hl = { fg = 'cyan' }, +} + +M.file_type = { + provider = function() + return ' ' .. string.upper(vim.bo.filetype) + end, + hl = { fg = 'blue', bold = true }, +} + +M.file_encoding = { + provider = function() + local enc = vim.bo.fenc ~= '' and vim.bo.fenc or vim.o.enc + return enc ~= 'utf-8' and (' ' .. enc:upper()) + end, + hl = { fg = 'fg' }, +} + +local file_common = { + init = function(self) + self.filename = vim.api.nvim_buf_get_name(0) + end, +} +local file_icon = { + init = function(self) + local filename = self.filename + local extension = vim.fn.fnamemodify(filename, ':e') + self.icon, self.icon_color = + require('nvim-web-devicons').get_icon_color(filename, extension, { default = true }) + end, + provider = function(self) + return self.icon and (self.icon .. ' ') + end, + hl = function(self) + return { fg = self.icon_color and self.icon_color or 'fg' } + end, +} +local file_name = { + provider = function(self) + if self.filename == '' then + return '[No Name]' + end + return vim.fn.fnamemodify(self.filename, ':p:t') + end, + hl = function() + if vim.bo.modified then + return { fg = 'cyan', bold = true } + end + return { fg = 'blue', bold = true } + end, +} +local file_flags = { + { + condition = function() + return vim.bo.modified + end, + provider = ' ', + hl = { fg = 'green' }, + }, + { + condition = function() + return not vim.bo.modifiable or vim.bo.readonly + end, + provider = ' ', + hl = { fg = 'red' }, + }, +} +M.file_info = utils.insert(file_common, file_icon, file_name, file_flags, { provider = '%<' }) + +M.ruler = { + provider = '%7(%l/%3L%):%2c %P', + hl = { fg = 'fg' }, +} + +M.lsp_servers = { + condition = conditions.lsp_attached, + update = { 'LspAttach', 'LspDetach' }, + provider = function() + return '  [' .. table.concat(lsp_utils.lsp_active_clients(), ', ') .. ']' + end, + hl = { fg = 'purple' }, +} + +M.navic = { + condition = function() + return require('nvim-navic').is_available() + end, + provider = function() + return require('nvim-navic').get_location() + end, + update = 'CursorMoved', +} + +M.diagnostics = { + condition = conditions.has_diagnostics, + update = { 'DiagnosticChanged', 'BufEnter' }, + init = function(self) + self.errors = #vim.diagnostic.get(0, { severity = vim.diagnostic.severity.ERROR }) + self.warnings = #vim.diagnostic.get(0, { severity = vim.diagnostic.severity.WARN }) + self.hints = #vim.diagnostic.get(0, { severity = vim.diagnostic.severity.HINT }) + self.infos = #vim.diagnostic.get(0, { severity = vim.diagnostic.severity.INFO }) + end, + { + provider = function(self) + return self.errors > 0 and (' ' .. vars.icons.notify.error .. ' ' .. self.errors) + end, + hl = { fg = utils.get_highlight('DiagnosticError').fg }, + }, + { + provider = function(self) + return self.warnings > 0 and (' ' .. vars.icons.notify.warn .. ' ' .. self.warnings) + end, + hl = { fg = utils.get_highlight('DiagnosticWarn').fg }, + }, + { + provider = function(self) + return self.hints > 0 and (' ' .. vars.icons.notify.hint .. ' ' .. self.hints) + end, + hl = { fg = utils.get_highlight('DiagnosticHint').fg }, + }, + { + provider = function(self) + return self.infos > 0 and (' ' .. vars.icons.notify.info .. ' ' .. self.infos) + end, + hl = { fg = utils.get_highlight('DiagnosticInfo').fg }, + }, +} + +M.code_action = { + condition = conditions.lsp_attached, + update = { 'CursorMoved', 'LspAttach' }, + provider = function() + if vim.b.has_code_action_text then + return ' ' .. vim.b.has_code_action_text + end + end, + hl = { fg = 'cyan', bold = true }, +} + +M.git = { + condition = conditions.is_git_repo, + init = function(self) + self.status_dict = vim.b.gitsigns_status_dict + self.has_changes = self.status_dict.added ~= 0 or self.status_dict.removed ~= 0 or self.status_dict.changed ~= 0 + end, + hl = { fg = 'green', bold = true }, + { + provider = function(self) + return '  ' .. self.status_dict.head + end, + }, + { + condition = function(self) + return self.has_changes + end, + provider = '(', + }, + { + provider = function(self) + local count = self.status_dict.added or 0 + return count > 0 and ('+' .. count) + end, + hl = { fg = utils.get_highlight('DiffAdd').fg }, + }, + { + provider = function(self) + local count = self.status_dict.removed or 0 + return count > 0 and ('-' .. count) + end, + hl = { fg = utils.get_highlight('DiffDelete').fg }, + }, + { + provider = function(self) + local count = self.status_dict.changed or 0 + return count > 0 and ('~' .. count) + end, + hl = { fg = utils.get_highlight('DiffChange').fg }, + }, + { + condition = function(self) + return self.has_changes + end, + provider = ')', + }, +} + +M.debugger = { + condition = function() + local session = require('dap').session() + return session ~= nil + end, + provider = function() + return '  ' .. require('dap').status() + end, + hl = { fg = 'purple', bold = true }, +} + +M.macro = { + condition = function() + return vim.fn.reg_recording() ~= '' and vim.o.cmdheight == 0 + end, + provider = function() + return ' @ Recording ' .. vim.fn.reg_recording() + end, + update = { 'RecordingEnter', 'RecordingLeave' }, + hl = { fg = 'green', bold = true }, +} + +return M diff --git a/lua/user/plugins/ui/heirline/init.lua b/lua/user/plugins/ui/heirline/init.lua new file mode 100644 index 0000000..1bdd382 --- /dev/null +++ b/lua/user/plugins/ui/heirline/init.lua @@ -0,0 +1,110 @@ +local conditions = require('heirline.conditions') +local components = require('user.plugins.ui.heirline.components') + +local excluded_buftype = { 'nofile', 'prompt', 'help', 'quickfix', 'terminal' } +local excluded_ft = { + 'NvimTree', + 'OverseerForm', + 'TelescopePrompt', + 'Trouble', + 'aerial', + 'aerial-nav', + 'alpha', + 'lazy', + 'qf', + 'toggleterm', + 'undotree', + 'dap-repl', + 'dapui_console', + 'dapui_watches', + 'dapui_stacks', + 'dapui_breakpoints', + 'dapui_scopes', +} + +require('heirline').setup { + statusline = { + hl = function() + if conditions.is_active() then + return 'StatusLine' + end + return 'StatusLineNC' + end, + fallthrough = false, + -- Statusline for special buffers + { + condition = function() + return conditions.buffer_matches { + buftype = excluded_buftype, + filetype = excluded_ft, + } + end, + { components.file_info, flexible = 1 }, + components.align, + { components.file_type, flexible = 3 }, + }, + -- Inactive statusline + { + condition = function() + return not conditions.is_active() + end, + components.dummy, + components.file_size, + { components.file_info, flexible = 1 }, + components.align, + { components.file_format, flexible = 2 }, + { components.file_encoding, flexible = 3 }, + { components.file_type, flexible = 3 }, + }, + -- Default statusline + { + components.dummy, + components.vi_mode, + components.file_size, + { components.file_info, flexible = 1 }, + { components.debugger, flexible = 3 }, + { components.code_action, flexible = 2 }, + { components.macro, flexible = 2 }, + components.align, + components.ruler, + { components.file_format, flexible = 2 }, + { components.file_encoding, flexible = 3 }, + { components.file_type, flexible = 3 }, + { components.git, flexible = 1 }, + }, + }, + winbar = { + hl = function() + if conditions.is_active() then + return 'WinBar' + end + return 'WinBarNC' + end, + fallthrough = false, + -- Inactive winbar + { + condition = function() + return not conditions.is_active() + end, + components.diagnostics, + components.align, + { components.lsp_servers, flexible = 1 }, + }, + -- Regular winbar + { + { components.navic, flexible = 2, { provider = '' } }, + components.align, + components.diagnostics, + { components.lsp_servers, flexible = 1 }, + }, + }, + opts = { + disable_winbar_cb = function(args) + return conditions.buffer_matches({ + buftype = excluded_buftype, + filetype = excluded_ft, + }, args.buf) + end, + colors = require('user.themes.' .. vim.g.colors_name .. '.colors'), + }, +} diff --git a/lua/user/plugins/ui/init.lua b/lua/user/plugins/ui/init.lua new file mode 100644 index 0000000..e29f98d --- /dev/null +++ b/lua/user/plugins/ui/init.lua @@ -0,0 +1,194 @@ +local tabline_provider = require('user.config.vars').tabline_provider + +return { + { + 'goolord/alpha-nvim', + event = 'VimEnter', + opts = function() + return require('user.plugins.ui.alpha') + end, + config = function(_, opts) + require('alpha').setup(opts) + + -- Disable tabline in Alpha buffer + local showtabline_val = vim.o.showtabline + if showtabline_val ~= 0 then + vim.api.nvim_create_autocmd('User', { + pattern = 'AlphaReady', + callback = function() + vim.opt.showtabline = 0 + vim.api.nvim_create_autocmd('BufUnload', { + buffer = 0, + once = true, + callback = function() + vim.opt.showtabline = showtabline_val + end, + }) + end, + }) + end + end, + }, + { + 'tiagovla/scope.nvim', + event = 'VeryLazy', + keys = { + { + 'fB', + function() + require('telescope').load_extension('scope') + vim.cmd.Telescope { args = { 'scope', 'buffers' }} + end, + desc = 'Scoped buffers', + }, + }, + config = true, + }, + { + 'nanozuki/tabby.nvim', + enabled = vim.o.showtabline ~= 0 and tabline_provider == 'tabby', + event = 'VeryLazy', + config = function() + require('user.plugins.ui.tabby') + end, + }, + { + 'willothy/nvim-cokeline', + enabled = vim.o.showtabline ~= 0 and tabline_provider == 'cokeline', + event = 'VeryLazy', + keys = { + { '[b', '(cokeline-focus-prev)', desc = 'Previous buffer' }, + { ']b', '(cokeline-focus-next)', desc = 'Next buffer' }, + { 'b', function() require('cokeline.mappings').pick('focus') end, desc = 'Pick buffer' }, + { 'q', function() require('cokeline.mappings').pick('close') end, desc = 'Close picked buffer' }, + }, + opts = function() + return require('user.plugins.ui.cokeline') + end, + }, + { + 'rebelot/heirline.nvim', + event = 'UIEnter', + config = function() + require('user.plugins.ui.heirline') + end, + }, + { + 'nvim-tree/nvim-tree.lua', + cmd = { 'NvimTreeToggle', 'NvimTreeFindFileToggle', 'NvimTreeFocus' }, + keys = { + { 'n', function() require('nvim-tree.api').tree.toggle(false, true) end, desc = 'NvimTree' }, + { 'N', function() require('nvim-tree.api').tree.focus() end, desc = 'NvimTree (focus)' }, + }, + opts = require('user.plugins.ui.tree'), + }, + { + 'petertriho/nvim-scrollbar', + event = 'BufReadPost', + opts = { + set_highlights = false, + show_in_active_only = true, + handle = { blend = 0 }, + handlers = { cursor = false }, + excluded_filetypes = { + 'alpha', + 'lazy', + 'notify', + 'aerial', + 'Trouble', + 'NvimTree', + 'qf', + 'prompt', + 'noice', + 'OverseerForm', + 'TelescopePrompt', + }, + }, + }, + { + 'folke/which-key.nvim', + event = 'VeryLazy', + config = function() + require('user.plugins.ui.which-key') + end, + }, + { + 'stevearc/dressing.nvim', + event = 'VeryLazy', + init = function() + ---@diagnostic disable-next-line: duplicate-set-field + vim.ui.select = function(...) + require('lazy').load({ plugins = { 'dressing.nvim' } }) + return vim.ui.select(...) + end + end, + opts = require('user.plugins.ui.dressing'), + }, + { + 'folke/noice.nvim', + event = 'VeryLazy', + dependencies = { + { + 'rcarriga/nvim-notify', + keys = { + { + 'uN', function() + require('notify').dismiss { silent = true, pending = true } + end, + desc = 'Delete all notifications', + }, + { + 'un', function() + require('telescope').load_extension('notify') + vim.cmd.Telescope { args = { 'notify' } } + end, + desc = 'Show all notifications', + }, + }, + opts = require('user.plugins.ui.notify') + }, + }, + keys = { + { '', function() require('noice').redirect(vim.fn.getcmdline()) end, mode = 'c', desc = 'Redirect Cmdline' }, + { 'ul', function() require('noice').cmd('last') end, desc = 'Noice Last Message' }, + { 'uh', function() require('noice').cmd('history') end, desc = 'Noice History' }, + { 'ua', function() require('noice').cmd('all') end, desc = 'Noice All' }, + { '', function() if not require('noice.lsp').scroll(4) then return '' end end, silent = true, expr = true, desc = 'Scroll forward', mode = {'i', 'n', 's'} }, + { '', function() if not require('noice.lsp').scroll(-4) then return '' end end, silent = true, expr = true, desc = 'Scroll backward', mode = {'i', 'n', 's'}}, + }, + opts = require('user.plugins.ui.noice'), + }, + { + 'folke/trouble.nvim', + cmd = { 'Trouble', 'TroubleToggle' }, + keys = { + { + '[q', + function() + if require('trouble').is_open() then + require('trouble').previous { skip_groups = true, jump = true } + else + vim.cmd.cprev() + end + end, + desc = 'Previous trouble/quickfix item', + }, + { + ']q', + function() + if require('trouble').is_open() then + require('trouble').next { skip_groups = true, jump = true } + else + vim.cmd.cnext() + end + end, + desc = 'Next trouble/quickfix item', + }, + { 'gL', 'TroubleToggle loclist', desc = 'Location list (Trouble)' }, + { 'gQ', 'TroubleToggle quickfix', desc = 'Quickfix (Trouble)' }, + }, + opts = function() + return require('user.plugins.ui.trouble') + end, + }, +} diff --git a/lua/user/plugins/ui/noice.lua b/lua/user/plugins/ui/noice.lua new file mode 100644 index 0000000..e64acfb --- /dev/null +++ b/lua/user/plugins/ui/noice.lua @@ -0,0 +1,59 @@ +local vars = require('user.config.vars') + +return { + lsp = { + -- `lsp_progress` builtin without progress bar + progress = { + enabled = false, + format = { + '{data.progress.percentage}% ', + { '{spinner} ', hl_group = 'NoiceLspProgressSpinner' }, + { '{data.progress.title} ', hl_group = 'NoiceLspProgressTitle' }, + { '{data.progress.client} ', hl_group = 'NoiceLspProgressClient' }, + }, + }, + override = { + ['cmp.entry.get_documentation'] = true, + ['vim.lsp.util.convert_input_to_markdown_lines'] = true, + -- ['vim.lsp.util.stylize_markdown'] = true, + }, + }, + presets = { command_palette = true, long_message_to_split = true }, + views = { + mini = { win_options = { winblend = 0 } }, + cmdline_popup = { border = { style = vars.border } }, + confirm = { border = { style = vars.border } }, + popup = { border = { style = vars.border } }, + popupmenu = { border = { style = vars.border } }, + hover = { + relative = 'cursor', + border = { style = vars.border }, + position = { row = 2, col = 2 }, + }, + }, + cmdline = { + format = { + -- `inc_rename` preset, but with different title + rename = { + pattern = '^:%s*IncRename%s+', + title = ' Rename ', + icon = '', + conceal = true, + opts = { + relative = 'cursor', + size = { min_width = 20 }, + position = { row = -3, col = 0 }, + }, + }, + }, + }, + routes = { + { + filter = { + event = 'msg_show', + find = '%d+L, %d+B', + }, + view = 'mini', + }, + }, +} diff --git a/lua/user/plugins/ui/notify.lua b/lua/user/plugins/ui/notify.lua new file mode 100644 index 0000000..de779fb --- /dev/null +++ b/lua/user/plugins/ui/notify.lua @@ -0,0 +1,24 @@ +local vars = require('user.config.vars') + +return { + level = vars.loglevel, + stages = 'slide', + on_open = function(win) + vim.api.nvim_win_set_config(win, { + border = vars.border, + focusable = false, + }) + end, + render = 'default', + timeout = 2000, + background_colour = 'NormalFloat', + minimum_width = 40, + icons = { + ERROR = vars.icons.notify.error .. ' ', + WARN = vars.icons.notify.warn .. ' ', + INFO = vars.icons.notify.info .. ' ', + DEBUG = vars.icons.notify.debug .. ' ', + TRACE = vars.icons.notify.trace .. ' ', + }, + top_down = true, +} diff --git a/lua/user/plugins/ui/tabby.lua b/lua/user/plugins/ui/tabby.lua new file mode 100644 index 0000000..02f18d5 --- /dev/null +++ b/lua/user/plugins/ui/tabby.lua @@ -0,0 +1,51 @@ +local theme = { + fill = 'TabLineFill', + head = 'TabLineSel', + current_tab = 'TabLineSel', + tab = 'TabLineFill', +} + +require('tabby.tabline').set(function(line) + return { + { + { '  ' .. vim.fn.fnamemodify(vim.fn.getcwd(), ':t') .. ' ', hl = theme.head }, + line.sep(' ', theme.head, theme.fill), + }, + line.tabs().foreach(function(tab) + local hl = tab.is_current() and theme.current_tab or theme.tab + return { + line.sep('', hl, theme.fill), + tab.is_current() and '󱓻' or '󱓼', + tab.number(), + line.sep('', hl, theme.fill), + hl = hl, + margin = ' ', + } + end), + line.spacer(), + line.wins_in_tab(line.api.get_current_tab()).foreach(function(win) + local name = win.buf_name() + local icon, color = require('nvim-web-devicons').get_icon_color(name, vim.fn.fnamemodify(name, ':e'), { default = true }) + local hl = win.is_current() and theme.current_tab or theme.tab + + return { + line.sep('', hl, theme.fill), + { + icon, + hl = { + fg = color, + bg = require('tabby.module.highlight').extract(hl).bg, + }, + }, + win.buf_name(), + win.buf().is_changed() and '' or '', + line.sep('', hl, theme.fill), + hl = hl, + margin = ' ', + } + end), + hl = theme.fill, + } +end, { + buf_name = { mode = 'shorten' }, +}) diff --git a/lua/user/plugins/ui/tree.lua b/lua/user/plugins/ui/tree.lua new file mode 100644 index 0000000..f248154 --- /dev/null +++ b/lua/user/plugins/ui/tree.lua @@ -0,0 +1,102 @@ +local vars = require('user.config.vars') + +return { + disable_netrw = true, + hijack_cursor = true, + respect_buf_cwd = true, + sync_root_with_cwd = true, + hijack_directories = { enable = false }, + diagnostics = { + enable = true, + icons = { + hint = vars.icons.notify.hint, + info = vars.icons.notify.info, + warning = vars.icons.notify.warn, + error = vars.icons.notify.error, + }, + }, + update_focused_file = { enable = true, update_root = true }, + system_open = { + cmd = vim.loop.os_uname().sysname == 'Darwin' and 'open' or 'xdg-open', + }, + filters = { + custom = { + '^\\.bzr$', + '^\\.git$', + '^\\.hg$', + '^\\.pijul$', + '^\\.svn$', + '^_dars$', + '^node_modules$', + '^bazel-out$', + '^bazel-bin$', + '^bazel-testlogs$', + '^plz-out$', + '^\\.plz-cache$', + '^\\.plz-http-cache$', + }, + }, + git = { ignore = false, timeout = 500 }, + view = { width = 35, side = 'left' }, + renderer = { + highlight_git = true, + indent_markers = { enable = true }, + icons = { + symlink_arrow = ' ➛ ', + glyphs = { + default = '', + symlink = '', + bookmark = '', + modified = '●', + folder = { + arrow_closed = '', + arrow_open = '', + default = '󰉋', + open = '󰝰', + empty = '', + empty_open = '', + symlink = '', + symlink_open = '', + }, + git = { + unstaged = '', + staged = '', + unmerged = '', + renamed = '', + untracked = '', + deleted = '', + ignored = '', + }, + }, + }, + special_files = { 'Cargo.toml', 'Makefile', 'README.md', 'package.json' }, + }, + actions = { + file_popup = { + open_win_config = { + border = vars.border, + }, + }, + open_file = { + resize_window = false, + window_picker = { + exclude = { + filetype = { + 'alpha', + 'lazy', + 'aerial', + 'OverseerForm', + 'TelescopePrompt', + 'notify', + 'undotree', + 'Trouble', + 'NvimTree', + 'diff', + 'qf', + }, + buftype = { 'terminal', 'nofile', 'help' }, + }, + }, + }, + }, +} diff --git a/lua/user/plugins/ui/trouble.lua b/lua/user/plugins/ui/trouble.lua new file mode 100644 index 0000000..c80637a --- /dev/null +++ b/lua/user/plugins/ui/trouble.lua @@ -0,0 +1,20 @@ +local icons = require('user.config.vars').icons + +return { + fold_open = ' ', + fold_closed = '', + action_keys = { + open_split = { '' }, + open_vsplit = { '' }, + open_tab = { '' }, + }, + auto_open = false, + auto_close = true, + signs = { + error = icons.notify.error, + warning = icons.notify.warn, + information = icons.notify.info, + hint = icons.notify.hint, + other = icons.notify.trace, + }, +} diff --git a/lua/user/plugins/ui/which-key.lua b/lua/user/plugins/ui/which-key.lua new file mode 100644 index 0000000..ff7a403 --- /dev/null +++ b/lua/user/plugins/ui/which-key.lua @@ -0,0 +1,57 @@ +local wk = require('which-key') +wk.setup { + plugins = { + spelling = { enabled = true, suggestions = 30 }, + presets = { + operators = false, + motions = false, + text_objects = false, + windows = true, + nav = true, + z = true, + g = true, + }, + }, + key_labels = { + [''] = 'SPC', + [''] = 'RET', + [''] = 'TAB', + }, + icons = { + breadcrumb = '»', + separator = '󰜴', + group = '+', + }, + window = { + border = require('user.config.vars').border, + position = 'bottom', + winblend = 0, + }, + layout = { spacing = 10, align = 'center' }, + ignore_missing = false, -- https://github.com/folke/which-key.nvim/issues/355 +} + +-- Register key group names after +wk.register({ + d = { name = 'Debug' }, + e = { name = 'Editor' }, + f = { + name = 'Finder', + s = { name = 'Symbols' }, + }, + g = { name = 'Git' }, + l = { name = 'LSP' }, + s = { name = 'Search/Replace' }, + t = { name = 'Tasks' }, + p = { name = 'Session' }, + q = { name = 'Quit' }, + u = { name = 'UI' }, + [''] = { name = 'Tab' }, +}, { prefix = '' }) + +wk.register({ + d = { name = 'Debug' }, + e = { name = 'Editor' }, + g = { name = 'Git' }, + s = { name = 'Search/Replace' }, +}, { prefix = '', mode = 'x' }) diff --git a/lua/user/plugins/util/carbon-now.lua b/lua/user/plugins/util/carbon-now.lua new file mode 100644 index 0000000..e826f9f --- /dev/null +++ b/lua/user/plugins/util/carbon-now.lua @@ -0,0 +1,24 @@ +local colors = require('user.themes.' .. vim.g.colors_name .. '.colors') + +--- Get Carbon theme name from the current Neovim theme +---@param name string +local function get_theme(name) + if name == 'nord' then + return 'Nord' + elseif name == 'onedark' then + return 'One Dark' + end + return 'VSCode' +end + +return { + options = { + theme = get_theme(vim.g.colors_name), + bg = colors.blue, + window_theme = 'none', + font_family = 'Cascadia Code', + watermark = false, + drop_shadow = true, + line_numbers = false, + }, +} diff --git a/lua/user/plugins/util/ccc.lua b/lua/user/plugins/util/ccc.lua new file mode 100644 index 0000000..23d14c4 --- /dev/null +++ b/lua/user/plugins/util/ccc.lua @@ -0,0 +1,38 @@ +local ccc = require('ccc') + +ccc.setup { + default_color = '#000000', + bar_char = '■', + point_char = '◇', + bar_len = 30, + win_opts = { + relative = 'cursor', + row = 1, + col = 1, + style = 'minimal', + border = require('user.config.vars').border, + }, + inputs = { + ccc.input.rgb, + ccc.input.hsl, + ccc.input.cmyk, + }, + outputs = { + ccc.output.hex, + ccc.output.hex_short, + ccc.output.css_rgb, + ccc.output.css_hsl, + }, + pickers = { + ccc.picker.hex, + ccc.picker.css_rgb, + ccc.picker.css_hsl, + }, + convert = { + { ccc.picker.hex, ccc.output.css_rgb }, + { ccc.picker.css_rgb, ccc.output.css_hsl }, + { ccc.picker.css_hsl, ccc.output.hex }, + }, + save_on_quit = false, + highlight_mode = 'bg', +} diff --git a/lua/user/plugins/util/git-conflict.lua b/lua/user/plugins/util/git-conflict.lua new file mode 100644 index 0000000..0c071db --- /dev/null +++ b/lua/user/plugins/util/git-conflict.lua @@ -0,0 +1,37 @@ +local gc = require('git-conflict') + +gc.setup { + disable_diagnostics = true, + default_mappings = false, + default_commands = false, +} + +vim.api.nvim_create_autocmd('User', { + pattern = 'GitConflictDetected', + once = true, + callback = function() + local bufnr = vim.api.nvim_get_current_buf() + local actions = { + ['Base change'] = 'base', + ['Both changes'] = 'both', + ['Current change'] = 'ours', + ['Incoming change'] = 'theirs', + ['None of the changes'] = 'none', + } + + vim.api.nvim_buf_create_user_command(bufnr, 'GitConflict', function() + vim.ui.select(vim.tbl_keys(actions), { prompt = 'Select a conflict action' }, function(choice) + if choice then gc.choose(actions[choice]) end + end) + end, { nargs = 0 }) + + vim.keymap.set('n', 'c', 'GitConflict', { + buffer = true, + noremap = true, + silent = true, + desc = 'Resolve git conflict', + }) + end, + desc = 'Custom user command for git-conflict', + group = vim.api.nvim_create_augroup('UserGitConflict', { clear = true }), +}) diff --git a/lua/user/plugins/util/gitsigns.lua b/lua/user/plugins/util/gitsigns.lua new file mode 100644 index 0000000..6807bd8 --- /dev/null +++ b/lua/user/plugins/util/gitsigns.lua @@ -0,0 +1,53 @@ +return { + signs = { + add = { text = '+' }, + change = { text = '~' }, + delete = { text = '_' }, + topdelete = { text = '‾' }, + changedelete = { text = '-' }, + untracked = { text = '+' }, + }, + current_line_blame_opts = { virt_text_pos = 'right_align' }, + preview_config = { border = require('user.config.vars').border }, + on_attach = function(bufnr) + local gs = package.loaded.gitsigns + local function map(mode, l, r, opts) + opts = vim.tbl_extend('force', { noremap = true, silent = true }, opts) + opts.buffer = bufnr + vim.keymap.set(mode, l, r, opts) + end + + -- stylua: ignore start + map('n', ']g', function() + if vim.wo.diff then + return ']g' + end + vim.schedule(function() + gs.next_hunk() + end) + return '' + end, { expr = true, desc = 'Next hunk' }) + map('n', '[g', function() + if vim.wo.diff then + return '[g' + end + vim.schedule(function() + gs.prev_hunk() + end) + return '' + end, { expr = true, desc = 'Previous hunk' }) + map({ 'n', 'x' }, 'gs', gs.stage_hunk, { desc = 'Stage hunk' }) + map({ 'n', 'x' }, 'gr', gs.reset_hunk, { desc = 'Reset hunk' }) + map('n', 'gp', gs.preview_hunk, { desc = 'Preview hunk' }) + map('n', 'gu', gs.undo_stage_hunk, { desc = 'Undo staged hunk' }) + map('n', 'gU', gs.reset_buffer_index, { desc = 'Reset buffer index' }) + map('n', 'gS', gs.stage_buffer, { desc = 'Stage all hunks in buffer' }) + map('n', 'gR', gs.reset_buffer, { desc = 'Reset all hunks in buffer' }) + map('n', 'gb', function() gs.blame_line { full = true } end, { desc = 'Blame current line' }) + map('n', 'gd', gs.diffthis, { desc = 'Show diff against current index' }) + map('n', 'gD', function() gs.diffthis('~') end, { desc = 'Show diff against last commit' }) + map('n', 'gz', gs.toggle_deleted, { desc = 'Toggle showing deleted hunk' }) + map({ 'o', 'x' }, 'ih', ':Gitsigns select_hunk', { desc = 'Select git hunk' }) + map('n', 'gh', function() gs.setqflist('attached') end, { desc = 'Show git hunks in attached buffers' }) + end, +} diff --git a/lua/user/plugins/util/init.lua b/lua/user/plugins/util/init.lua new file mode 100644 index 0000000..731053e --- /dev/null +++ b/lua/user/plugins/util/init.lua @@ -0,0 +1,342 @@ +local vars = require('user.config.vars') + +return { + { + 'echasnovski/mini.bufremove', + opts = { silent = true }, + config = function(_, opts) + require('mini.bufremove').setup(opts) + end, + }, + { + 'akinsho/toggleterm.nvim', + cmd = 'ToggleTerm', + keys = { + { [[]], 'ToggleTerm', desc = 'Terminal' }, + { [[]], 'ToggleTerm direction=float', desc = 'Float terminal' }, + }, + opts = { + shade_terminals = false, + float_opts = { + border = vars.border, + winblend = 0, + }, + winbar = { enabled = true }, + }, + }, + { + 'nvim-telescope/telescope.nvim', + cmd = 'Telescope', + keys = { + -- File pickers + { '/', 'Telescope live_grep', desc = 'Live grep' }, + { 'fe', 'Telescope file_browser', desc = 'File browser' }, + { 'ff', 'Telescope find_files', desc = 'Files' }, + -- Vim pickers + { ':', 'Telescope command_history', desc = 'Command history' }, + { 'fa', 'Telescope autocommands', desc = 'Auto commands' }, + { 'fb', 'Telescope buffers sort_mru=true sort_lastused=true', desc = 'Buffers' }, + { 'fh', 'Telescope help_tags', desc = 'Help pages' }, + { 'fk', 'Telescope keymaps', desc = 'Keymaps (normal mode)' }, + { 'fm', 'Telescope marks', desc = 'Marks' }, + { 'fo', 'Telescope vim_options', desc = 'Vim options' }, + { 'fr', 'Telescope oldfiles', desc = 'Recent files' }, + { 'fz', 'Telescope current_buffer_fuzzy_find', desc = 'Current buffer' }, + -- Symbols + { + 'fsg', + function() + require('telescope.builtin').symbols { sources = { 'gitmoji' }, prompt_title = 'Gitmoji' } + end, + desc = 'Gitmoji', + }, + { + 'fsj', + function() + require('telescope.builtin').symbols { sources = { 'emoji' }, prompt_title = 'Emoji' } + end, + desc = 'Emoji', + }, + { + 'fsk', + function() + require('telescope.builtin').symbols { sources = { 'kaomoji' }, prompt_title = 'Kaomoji' } + end, + desc = 'Kaomoji', + }, + { + 'fsn', + function() + require('telescope.builtin').symbols { sources = { 'nerd' }, prompt_title = 'Nerd-fonts' } + end, + desc = 'Nerd-fonts', + }, + -- Git pickers + { 'ga', 'Telescope git_stash', desc = 'Stash items' }, + { 'gm', 'Telescope git_branches', desc = 'Branches' }, + { 'gc', 'Telescope git_bcommits', desc = 'Buffer commits' }, + { 'gC', 'Telescope git_commits', desc = 'Commits' }, + }, + dependencies = { + 'nvim-telescope/telescope-file-browser.nvim', + { 'nvim-telescope/telescope-fzf-native.nvim', build = 'make' }, + { + 'nvim-telescope/telescope-symbols.nvim', + -- there is only the data/ directory to care about + init = function() + require('lazy.core.loader').disable_rtp_plugin('telescope-symbols.nvim') + end, + }, + }, + opts = function() + return require('user.plugins.util.telescope') + end, + config = function(_, opts) + local telescope = require('telescope') + telescope.setup(opts) + + -- NOTE: explicitly set after the setup so the options are applied correctly + telescope.load_extension('file_browser') + telescope.load_extension('fzf') + end, + }, + { + 'LukasPietzschmann/telescope-tabs', + keys = { + { 'f', function() require('telescope-tabs').list_tabs() end, desc = 'Tabs' }, + }, + opts = { + entry_formatter = function(tab_id, _, file_names, _, is_current) + local current_icon = is_current and ' ' or ' ' + if require('user.util.misc').has('tabby.nvim') then + local tab_name = require('tabby.feature.tab_name').get(tab_id) + return string.format('%d: %s%s', tab_id, current_icon, tab_name) + end + return string.format('%d: %s%s', tab_id, current_icon, table.concat(file_names, ', ')) + end, + entry_ordinal = function(tab_id, _, file_names) + if require('user.util.misc').has('tabby.nvim') then + return require('tabby.feature.tab_name').get(tab_id) + end + return table.concat(file_names, ' ') + end, + }, + }, + { + 'ahmedkhalf/project.nvim', + event = { 'BufReadPost', 'BufNewFile' }, + keys = { + { + 'fp', function() + local telescope = require('telescope') + telescope.load_extension('projects') + telescope.extensions.projects.projects() + end, + desc = 'Recent projects', + }, + }, + config = function() + require('user.plugins.util.project') + end, + }, + { + 'stevearc/overseer.nvim', + cmd = { + 'OverseerToggle', + 'OverseerBuild', + 'OverseerInfo', + 'OverseerRunCmd', + 'OverseerRun', + }, + keys = { + { 'tb', 'OverseerBuild', desc = 'Build task' }, + { 'ti', 'OverseerInfo', desc = 'Info' }, + { 'tl', 'OverseerToggle', desc = 'Task list' }, + { 'tr', 'OverseerRun', desc = 'Run task' }, + }, + opts = function() + return require('user.plugins.util.overseer') + end, + }, + { + 'stevearc/stickybuf.nvim', + event = 'BufReadPost', + cmd = { 'PinBuffer', 'PinBuftype', 'Unpin', 'PinFileType' }, + opts = { get_auto_pin = require('user.plugins.util.stickybuf') }, + }, + { + 'NeogitOrg/neogit', + cmd = 'Neogit', + keys = { + {'go', 'Neogit', desc = 'Neogit' }, + }, + dependencies = { + { + 'sindrets/diffview.nvim', + cmd = { 'DiffviewOpen', 'DiffviewFileHistory' }, + }, + }, + opts = { + signs = { + section = { '󰄾', '󰄼' }, + item = { '', '' }, + }, + disable_context_highlighting = true, + integrations = { diffview = true, telescope = true }, + telescope_sorter = function() + return require("telescope").extensions.fzf.native_fzf_sorter() + end, + }, + }, + { + 'lewis6991/gitsigns.nvim', + event = { 'BufNewFile', 'BufReadPost' }, + dependencies = 'plenary.nvim', + opts = require('user.plugins.util.gitsigns'), + }, + { + 'akinsho/git-conflict.nvim', + event = { 'BufReadPost', 'BufNewFile' }, + keys = { + { '[x', function() require('git-conflict').find_prev('ours') end, desc = 'Previous git conflict' }, + { ']x', function() require('git-conflict').find_next('ours') end, desc = 'Previous git conflict' }, + }, + config = function() + require('user.plugins.util.git-conflict') + end, + }, + { + 'cshuaimin/ssr.nvim', + keys = { + { 'ss', function() require('ssr').open() end, mode = { 'x', 'n' }, desc = 'Structural search/replace' }, + }, + opts = { border = vars.border }, + }, + { + 'nvim-pack/nvim-spectre', + keys = { + { 'so', function() require('spectre').open() end, desc = 'Search/Replace (Spectre)' }, + { 'so', function() require('spectre').open_visual() end, desc = 'Search/Replace selection (Spectre)', mode = 'x' }, + { 'sp', function() require('spectre').open_file_search() end, desc = 'Search/Replace in current file' }, + }, + opts = { find_engine = { rg = { args = vars.rg_args } } }, + }, + { + 'uga-rosa/ccc.nvim', + cmd = { 'CccPick', 'CccConvert', 'CccHighlighterToggle' }, + keys = { + { 'ec', 'CccPick', desc = 'Pick color' }, + { 'eC', 'CccPick', desc = 'Convert color under cursor' }, + { 'uc', 'CccHighlighterToggle', desc = 'Highlight color text' }, + }, + config = function() + require('user.plugins.util.ccc') + end, + }, + { + 'folke/persistence.nvim', + event = 'BufReadPre', + keys = { + { 'ps', function() require('persistence').save() end, desc = 'Save session for current directory' }, + { 'pd', function() require('persistence').stop() end, desc = 'Don\'t save current session' }, + { 'pr', function() require('persistence').load() end, desc = 'Restore session' }, + { 'pl', function() require('persistence').load { last = true } end, desc = 'Restore last session' }, + }, + opts = { options = vim.opt.sessionoptions:get() }, + }, + { + 'michaelb/sniprun', + build = 'cargo build --release', + cmd = { 'SnipRun', 'SnipInfo' }, + keys = { + { 'ex', 'SnipRun', desc = 'Execute current line' }, + { 'ex', 'SnipRun', desc = 'Execute selection', mode = 'x' }, + }, + opts = { + display = { 'NvimNotify' }, + display_options = { notification_timeout = 2000 }, + show_no_output = { + 'Classic', + 'NvimNotify', + 'TempFloatingWindow', + }, + }, + }, + { + 'rest-nvim/rest.nvim', + keys = { + { 'gu', function() require('rest-nvim').run() end, desc = 'cURL request under cursor' }, + }, + opts = { result_split_in_place = true }, + }, + { + 'mbbill/undotree', + cmd = 'UndotreeToggle', + keys = { + { 'u', 'UndotreeToggle', desc = 'Undotree' }, + }, + init = function() + vim.g.undotree_WindowLayout = 2 + vim.g.undotree_SplitWidth = 30 + vim.g.undotree_DiffpaneHeight = 10 + vim.g.undotree_SetFocusWhenToggle = 1 + vim.g.undotree_RelativeTimestamp = 1 + end, + }, + { + 'potamides/pantran.nvim', + cmd = 'Pantran', + keys = { + { 'er', function() return require('pantran').motion_translate() end, expr = true, desc = 'Translate (motion)' }, + { 'eR', function() return require('pantran').motion_translate() .. '_' end, expr = true, desc = 'Translate (cursor)' }, + { 'er', function() return require('pantran').motion_translate() end, expr = true, mode = 'x', desc = 'Translate' }, + }, + opts = function() + return require('user.plugins.util.pantran') + end, + }, + { + 'iamcco/markdown-preview.nvim', + build = 'cd app && pnpm install --prod', + cmd = { 'MarkdownPreviewToggle', 'MarkdownPreview' }, + keys = { + { + 'p', + ft = { 'markdown', 'rmd' }, + 'MarkdownPreviewToggle', + desc = 'Preview Markdown file', + }, + }, + opts = { + mkdp_browser = vim.loop.os_uname().sysname == 'Darwin' and 'open' or 'xdg-open', + mkdp_refresh_slow = 1, + mkdp_filetypes = { 'markdown', 'rmd' }, + mkdp_echo_preview_url = 0, + mkdp_preview_options = { disable_filename = 1 }, + }, + config = function(_, opts) + for key, val in pairs(opts) do + vim.g[key] = val + end + + vim.cmd('do FileType') + end, + }, + { + 'ellisonleao/carbon-now.nvim', + cmd = 'CarbonNow', + keys = { + { mode = { 'n', 'x' }, 'ui', 'CarbonNow', desc = 'Carbon' }, + }, + opts = function() + return require('user.plugins.util.carbon-now') + end, + }, + { + 'dstein64/vim-startuptime', + cmd = 'StartupTime', + init = function() + vim.g.startuptime_tries = 10 + end, + }, +} diff --git a/lua/user/plugins/util/overseer.lua b/lua/user/plugins/util/overseer.lua new file mode 100644 index 0000000..51b5c53 --- /dev/null +++ b/lua/user/plugins/util/overseer.lua @@ -0,0 +1,24 @@ +local win_opts = { + border = require('user.config.vars').border, + win_opts = { winblend = 0 }, +} + +return { + strategy = require('user.util.misc').has('toggleterm.nvim') and { + 'toggleterm', + hidden = false, + open_on_start = false, + use_shell = false, + } or 'terminal', + auto_detect_success_color = false, + task_list = { + default_detail = 2, + max_width = { 120, 0.3 }, + min_width = { 40, 0.2 }, + direction = 'right', + }, + form = win_opts, + confirm = win_opts, + task_win = win_opts, + dap = false, -- enable the integration manually +} diff --git a/lua/user/plugins/util/pantran.lua b/lua/user/plugins/util/pantran.lua new file mode 100644 index 0000000..e0af328 --- /dev/null +++ b/lua/user/plugins/util/pantran.lua @@ -0,0 +1,30 @@ +local border = require('user.config.vars').border + +local title_border +if border == 'single' or border == 'rounded' then + title_border = { '┤ ', ' ├' } +else + title_border = { '╣ ', ' ╠' } +end + +return { + default_engine = 'google', + command = { default_mode = 'interactive' }, + ui = { + width_percentage = 0.8, + height_percentage = 0.6, + }, + help = { separator = '  ' }, + select = { + prompt_prefix = ' ', + selection_caret = ' ', + }, + window = { + title_border = title_border, + window_config = { border = border }, + options = { + winhighlight = 'Normal:NormalFloat,SignColumn:NormalFloat,FloatBorder:FloatBorder', + }, + }, + controls = { updatetime = vim.opt.updatetime:get() }, +} diff --git a/lua/user/plugins/util/project.lua b/lua/user/plugins/util/project.lua new file mode 100644 index 0000000..6758126 --- /dev/null +++ b/lua/user/plugins/util/project.lua @@ -0,0 +1,16 @@ +require('project_nvim').setup { + patterns = { + '.bzr', + '.git', + '.hg', + '.pijul', + '.svn', + '.fslckout', + '_darcs', + '>Code', + 'Makefile', + 'package.json', + 'go.mod', + }, + show_hidden = true, +} diff --git a/lua/user/plugins/util/stickybuf.lua b/lua/user/plugins/util/stickybuf.lua new file mode 100644 index 0000000..8525bb5 --- /dev/null +++ b/lua/user/plugins/util/stickybuf.lua @@ -0,0 +1,34 @@ +local filetypes = { + 'aerial', + 'NvimTree', + 'neotest-summary', + 'startuptime', + 'toggleterm', + 'notify', + 'OverseerList', + 'gitcommit', + 'noice', + 'undotree', +} +local buftypes = { 'terminal', 'help', 'quickfix' } + +-- Essentially a modified version of require('stickybuf').should_auto_pin() +return function(bufnr) + local buftype = vim.bo[bufnr].buftype + local filetype = vim.bo[bufnr].filetype + local bufname = vim.api.nvim_buf_get_name(bufnr) + + if vim.tbl_contains(filetypes, filetype) then + return 'filetype' + elseif vim.tbl_contains(buftypes, buftype) then + return 'buftype' + elseif buftype == 'prompt' or vim.startswith(bufname, 'DAP ') then + return 'bufnr' + elseif bufname:match('Neogit.*Popup') then + return 'bufnr' + elseif filetype == 'NeogitStatus' or filetype == 'NeogitLog' or filetype == 'NeogitGitCommandHistory' then + if vim.fn.winnr('$') > 1 then + return 'filetype' + end + end +end diff --git a/lua/user/plugins/util/telescope.lua b/lua/user/plugins/util/telescope.lua new file mode 100644 index 0000000..87e82df --- /dev/null +++ b/lua/user/plugins/util/telescope.lua @@ -0,0 +1,137 @@ +local border_style = require('user.config.vars').border +local layout = require('user.config.vars').telescope_layout + +local borderchars = { + horizontal = { + single = { '─', '│', '─', '│', '┌', '┐', '┘', '└' }, + rounded = { '─', '│', '─', '│', '╭', '╮', '╯', '╰' }, + double = { '═', '║', '═', '║', '╔', '╗', '╝', '╚' }, + }, + bottom_pane = { + single = { + prompt = { '─', '│', ' ', '│', '┌', '┐', ' ', ' ' }, + results = { '─', ' ', '─', '│', '├', '┤', '─', '└' }, + preview = { '─', '│', '─', '│', '┌', '┤', '┘', '┴' }, + }, + rounded = { + prompt = { '─', '│', ' ', '│', '╭', '╮', ' ', ' ' }, + results = { '─', ' ', '─', '│', '├', '┤', '─', '╰' }, + preview = { '─', '│', '─', '│', '╭', '┤', '╯', '┴' }, + }, + double = { + prompt = { '═', '║', ' ', '║', '╔', '╗', ' ', ' ' }, + results = { '═', ' ', '═', '║', '╠', '╣', '═', '╚' }, + preview = { '═', '║', '═', '║', '╔', '╣', '╝', '╩' }, + }, + }, + cursor = { + single = { + prompt = { '─', '│', ' ', '│', '┌', '┐', ' ', ' ' }, + results = { '─', '│', '─', '│', '├', '┤', '╯', '╰' }, + preview = { '─', '│', '─', '│', '┌', '┐', '┘', '└' }, + }, + rounded = { + prompt = { '─', '│', ' ', '│', '╭', '╮', '│', '│' }, + results = { '─', '│', '─', '│', '├', '┤', '╯', '╰' }, + preview = { '─', '│', '─', '│', '╭', '╮', '╯', '╰' }, + }, + double = { + prompt = { '═', '║', ' ', '║', '╔', '╗', '║', '║' }, + results = { '═', '║', '═', '║', '╠', '╣', '╝', '╚' }, + preview = { '═', '║', '═', '║', '╔', '╗', '╝', '╚' }, + }, + }, +} + +local ignore_patterns = { + '^%.bzr/', + '^%.git/', + '^%.hg/', + '^%.pijul/', + '^%.svn/', + '^_darcs/', + '^node_modules/', + '^bazel%-out/', + '^bazel%-bin/', + '^bazel%-testlogs/', + '^plz%-out/', + '^%.plz%-cache/', + '^%.plz%-http%-cache/', +} + +return { + defaults = { + prompt_prefix = '  ', + selection_caret = ' ', + sorting_strategy = 'ascending', + mappings = { + i = { + [''] = require('trouble.providers.telescope').open_with_trouble, + [''] = require('trouble.providers.telescope').open_selected_with_trouble, + [''] = 'cycle_history_prev', + [''] = 'cycle_history_next', + }, + n = { + ['q'] = 'close', + }, + }, + dynamic_preview_title = true, + layout_strategy = layout, + layout_config = { + bottom_pane = { + height = 0.4, + preview_width = 0.6, + }, + horizontal = { + prompt_position = 'top', + preview_width = 0.6, + height = 0.9, + width = 0.9, + }, + cursor = { + width = 0.6, + height = function(self, _, max_lines) + local results, PADDING = #self.finder.results, 4 -- this represents the size of the telescope window + local LIMIT = math.floor(max_lines / 2) + return (results <= (LIMIT - PADDING) and results + PADDING or LIMIT) + end, + }, + }, + borderchars = borderchars[layout][border_style], + file_ignore_patterns = ignore_patterns, + }, + pickers = { + find_files = { + hidden = true, + follow = true, + }, + live_grep = { + additional_args = '--hidden', + -- ignore dependencies versions in lock files + file_ignore_patterns = vim.list_extend(vim.list_slice(ignore_patterns), { + '^go%.sum', + '^Cargo%.lock', + '^poetry%.lock', + '^package%-lock%.json', + '^yarn%.lock', + '^pnpm%-lock%.yaml', + '^flake%.lock', + }), + }, + registers = { + layout_strategy = 'cursor', + borderchars = borderchars.cursor[border_style], + layout_config = { width = 0.4 }, + }, + }, + extensions = { + file_browser = { + hidden = { + file_browser = true, + folder_browser = true, + }, + dir_icon = '󰉋', + }, + fzf = { override_generic_sorter = false }, + }, +} diff --git a/lua/user/themes/nord/colors.lua b/lua/user/themes/nord/colors.lua new file mode 100644 index 0000000..cf7dfe7 --- /dev/null +++ b/lua/user/themes/nord/colors.lua @@ -0,0 +1,22 @@ +local nord = { + black = '#2E3440', + grey1 = '#3B4252', + grey2 = '#434C5E', + grey3 = '#4C566A', + grey_bright = '#616E88', + fg = '#D8DEE9', + white1 = '#E5E9F0', + white2 = '#ECEFF4', + teal = '#8FBCBB', + cyan = '#88C0D0', + blue = '#81A1C1', + dark_blue = '#5E81AC', + red = '#BF616A', + orange = '#D08770', + yellow = '#EBCB8B', + green = '#A3BE8C', + purple = '#B48EAD', + highlight = '#7B88A1', +} + +return nord diff --git a/lua/user/themes/nord/groups/editor.lua b/lua/user/themes/nord/groups/editor.lua new file mode 100644 index 0000000..2ab5088 --- /dev/null +++ b/lua/user/themes/nord/groups/editor.lua @@ -0,0 +1,149 @@ +local c = require('user.themes.nord.colors') + +return { + Normal = { fg = c.fg }, + NormalFloat = { fg = c.fg }, + FloatBorder = { fg = c.white2 }, + ColorColumn = { bg = c.grey1 }, + Cursor = { fg = c.black, bg = c.fg }, + CursorIM = { fg = c.black, bg = c.white1 }, + CursorLine = { bg = c.grey1 }, + TermCursor = { link = 'Cursor' }, + TermCursorNC = { bg = c.grey1 }, + Underlined = { fg = c.green, underline = true }, + Ignore = { fg = c.grey1 }, + Error = { fg = c.fg, bg = c.red }, + LineNr = { fg = c.grey3 }, + MatchParen = { fg = c.cyan, bg = c.grey3 }, + NonText = { fg = c.highlight }, + Whitespace = { fg = c.highlight }, + EndOfBuffer = { fg = c.black }, -- hide filler lines with '~' completely + Pmenu = { fg = c.fg, bg = c.grey1 }, + PmenuSbar = { fg = c.fg, bg = c.grey1 }, + PmenuSel = { fg = c.cyan, bg = c.grey3, bold = true }, + PmenuThumb = { fg = c.cyan, bg = c.grey3, bold = true }, + SpecialKey = { fg = c.grey3 }, + SperllBad = { fg = c.red, sp = c.red, undercurl = true }, + SpellCap = { fg = c.yellow, sp = c.yellow, undercurl = true }, + SpellLocal = { fg = c.white1, sp = c.white1, undercurl = true }, + SpellRare = { fg = c.white2, sp = c.white2, undercurl = true }, + Visual = { bg = c.grey2 }, + VisualNOS = { bg = c.grey2 }, + + -- quickfix + QuickFixLine = { bg = c.grey2 }, + qfLineNr = { fg = c.yellow }, + + -- :checkhealth + healthError = { fg = c.red, bg = c.grey1 }, + healthSuccess = { fg = c.green, bg = c.grey1 }, + healthWarning = { fg = c.yellow, bg = c.grey1 }, + + -- gutter + CursorColumn = { bg = c.grey1 }, + CursorLineNr = { fg = c.fg }, + Folded = { fg = c.highlight, bg = c.grey1 }, + FoldColumn = { fg = c.grey3 }, + SignColumn = { fg = c.grey1 }, + + -- navigation + Directory = { fg = c.cyan }, + + -- prompt + MsgArea = { link = 'Normal' }, + ErrorMsg = { fg = c.fg, bg = c.red }, + ModeMsg = { fg = c.fg }, + MoreMsg = { fg = c.cyan }, + Question = { fg = c.fg }, + WarningMsg = { fg = c.black, bg = c.yellow }, + WildMenu = { fg = c.cyan, bg = c.grey1 }, + + -- statusline + Statusline = { fg = c.cyan, bg = c.grey1 }, + StatusLineNC = { fg = c.fg, bg = c.grey1 }, + + -- search + IncSearch = { fg = c.white2, bg = c.dark_blue, underline = true }, + Search = { fg = c.grey1, bg = c.cyan }, + CurSearch = { link = 'IncSearch' }, + -- :s/../../ + Substitute = { link = 'IncSearch' }, + + -- tabline + TabLine = { fg = c.fg, bg = c.grey3 }, + TabLineFill = { fg = c.fg, bg = c.grey1 }, + TabLineSel = { fg = c.black, bg = c.cyan }, + + -- winbar + WinBar = { fg = c.fg, bg = 'NONE' }, + WinBarNC = { link = 'WinBar' }, + + -- window + Title = { fg = c.fg }, + WinSeparator = { fg = c.grey_bright }, + + -- base syntax + Boolean = { fg = c.blue }, + Character = { fg = c.fg }, + Comment = { fg = c.grey_bright, italic = true }, + Conceal = { fg = 'NONE', bg = 'NONE' }, + Conditional = { fg = c.blue, bold = true }, + Constant = { fg = c.yellow }, + Debug = { fg = c.highlight }, + Define = { fg = c.blue }, + Delimiter = { fg = c.white2 }, + Exception = { fg = c.blue, bold = true }, + Float = { fg = c.purple }, + Function = { fg = c.cyan }, + Identifier = { fg = c.fg }, + Include = { fg = c.blue }, + Keyword = { fg = c.blue, bold = true }, + Label = { fg = c.purple }, + Number = { fg = c.purple }, + Operator = { fg = c.blue }, + PreProc = { fg = c.blue }, + Repeat = { fg = c.blue, bold = true }, + Special = { fg = c.fg }, + SpecialChar = { fg = c.yellow }, + SpecialComment = { fg = c.cyan, italic = true }, + Statement = { fg = c.blue }, + StorageClass = { fg = c.blue }, + String = { fg = c.green }, + Structure = { fg = c.blue }, + Tag = { fg = c.fg }, + Todo = { fg = c.yellow }, + Type = { fg = c.blue }, + Typedef = { fg = c.blue }, + Macro = { link = 'Define' }, + PreCondit = { link = 'PreProc' }, + Variable = { link = 'Identifier' }, + + -- diff + DiffAdd = { fg = c.green, bg = c.grey1 }, + DiffChange = { fg = c.yellow, bg = c.grey1 }, + DiffDelete = { fg = c.red, bg = c.grey1 }, + DiffText = { fg = c.blue, bg = c.grey1 }, + + -- NOTE: no TreeSitter grammars yet + -- asciidoc + asciidocAttributeEntry = { fg = c.purple, bold = true }, + asciidocAttributeList = { fg = c.purple, bold = true }, + asciidocAttributeRef = { fg = c.green, bold = true }, + asciidocHLabel = { fg = c.blue }, + asciidocListingBlock = { fg = c.teal }, + asciidocMacro = { fg = c.green }, + asciidocMacroAttributes = { fg = c.purple }, + asciidocOneLineTitle = { link = '@text.title' }, + asciidocPassthroughBlock = { fg = c.blue }, + asciidocTriplePlusPassthrough = { fg = c.teal }, + asciidocAdmonition = { link = 'Keyword' }, + asciidocBackslash = { link = 'Keyword' }, + asciidocQuotedBold = { bold = true }, + asciidocQuotedEmphasized = { italic = true }, + asciidocQuotedMonospaced = { fg = c.teal }, + asciidocQuotedMonospaced2 = { link = 'asciidocQuotedMonospaced' }, + asciidocQuotedUnconstrainedBold = { link = 'asciidocQuotedBold' }, + asciidocQuotedUnconstrainedEmphasized = { link = 'asciidocQuotedEmphasized' }, + asciidocURL = { link = '@text.uri' }, + asciidocEmail = { link = '@text.uri' }, +} diff --git a/lua/user/themes/nord/groups/lsp.lua b/lua/user/themes/nord/groups/lsp.lua new file mode 100644 index 0000000..a537672 --- /dev/null +++ b/lua/user/themes/nord/groups/lsp.lua @@ -0,0 +1,72 @@ +local c = require('user.themes.nord.colors') + +return { + -- LSP semantic tokens + ['@lsp.type.boolean'] = { link = '@boolean' }, + ['@lsp.type.builtinType'] = { link = '@type.builtin' }, + ['@lsp.type.class'] = { link = '@type' }, + ['@lsp.type.comment'] = { link = '@comment' }, + ['@lsp.type.enum'] = { link = '@type' }, + ['@lsp.type.enumMember'] = { link = '@constant' }, + ['@lsp.type.escapeSequence'] = { link = '@string.escape' }, + ['@lsp.type.formatSpecifier'] = { link = '@punctuation.special' }, + ['@lsp.type.field'] = { link = '@field' }, + ['@lsp.type.function'] = { link = '@function' }, + ['@lsp.type.generic'] = { link = '@text' }, + ['@lsp.type.interface'] = { link = '@type' }, + ['@lsp.type.keyword'] = { link = '@keyword' }, + ['@lsp.type.macro'] = { link = '@function.macro' }, + ['@lsp.type.method'] = { link = '@method' }, + ['@lsp.type.namespace'] = { link = '@namespace' }, + ['@lsp.type.number'] = { link = '@number' }, + ['@lsp.type.operator'] = { link = '@operator' }, + ['@lsp.type.parameter'] = { link = '@parameter' }, + ['@lsp.type.property'] = { link = '@property' }, + ['@lsp.type.selfKeyword'] = { link = '@variable.builtin' }, + ['@lsp.type.typeAlias'] = { link = '@type.definition' }, + ['@lsp.type.unresolvedReference'] = { link = '@error' }, + ['@lsp.type.typeParameter'] = { link = '@parameter' }, + ['@lsp.type.struct'] = { link = 'Structure' }, + ['@lsp.type.variable'] = { link = '@variable' }, + ['@lsp.typemod.enum.defaultLibrary'] = { link = '@type.builtin' }, + ['@lsp.typemod.enumMember.defaultLibrary'] = { link = '@constant.builtin' }, + ['@lsp.typemod.function.defaultLibrary'] = { link = '@function.builtin' }, + ['@lsp.typemod.macro.defaultLibrary'] = { link = '@function.builtin' }, + ['@lsp.typemod.method.defaultLibrary'] = { link = '@function.builtin' }, + ['@lsp.typemod.operator.injected'] = { link = '@operator' }, + ['@lsp.typemod.string.injected'] = { link = '@string' }, + ['@lsp.typemod.type.defaultLibrary'] = { link = '@type.builtin' }, + ['@lsp.typemod.variable.defaultLibrary'] = { link = '@variable.builtin' }, + ['@lsp.typemod.variable.injected'] = { link = '@variable' }, + ['@lsp.typemod.variable.static'] = { link = '@constant' }, + + -- LSP + DiagnosticError = { fg = c.red }, + DiagnosticWarn = { fg = c.yellow }, + DiagnosticInfo = { fg = c.blue }, + DiagnosticHint = { fg = c.cyan }, + DiagnosticUnderlineError = { sp = c.red, underline = true }, + DiagnosticUnderlineWarn = { sp = c.yellow, underline = true }, + DiagnosticUnderlineInfo = { sp = c.blue, underline = true }, + DiagnosticUnderlineHint = { sp = c.cyan, underline = true }, + DiagnosticVirtualTextError = { fg = c.red, italic = true }, + DiagnosticVirtualTextWarn = { fg = c.yellow, italic = true }, + DiagnosticVirtualTextInfo = { fg = c.blue, italic = true }, + DiagnosticVirtualTextHint = { fg = c.cyan, italic = true }, + DiagnosticFloatingError = { link = 'DiagnosticError' }, + DiagnosticFloatingWarn = { link = 'DiagnosticWarn' }, + DiagnosticFloatingInfo = { link = 'DiagnosticInfo' }, + DiagnosticFloatingHint = { link = 'DiagnosticHint' }, + DiagnosticSignError = { link = 'DiagnosticError' }, + DiagnosticSignWarn = { link = 'DiagnosticWarn' }, + DiagnosticSignInfo = { link = 'DiagnosticInfo' }, + DiagnosticSignHint = { link = 'DiagnosticHint' }, + LspReferenceText = { bg = c.grey1 }, + LspReferenceRead = { bg = c.grey1 }, + LspReferenceWrite = { bg = c.grey1 }, + LspCodeLens = { link = 'Comment' }, + LspCodeLensSeparator = { fg = c.cyan, italic = true }, + LspInlayHint = { link = 'Comment' }, + LspSignatureActiveParameter = { bg = c.grey2 }, + LspInfoBorder = { link = 'FloatBorder' }, +} diff --git a/lua/user/themes/nord/groups/plugins.lua b/lua/user/themes/nord/groups/plugins.lua new file mode 100644 index 0000000..a93c5b3 --- /dev/null +++ b/lua/user/themes/nord/groups/plugins.lua @@ -0,0 +1,301 @@ +local c = require('user.themes.nord.colors') +local util = require('user.util.color') + +return { + -- nvim-treesitter-context + TreesitterContext = { bg = c.grey1 }, + TreesitterContextLineNumber = { fg = c.fg, bg = c.grey1 }, + + -- nvim-cmp + CmpItemAbbr = { fg = c.fg }, + CmpItemAbbrMatch = { fg = c.yellow, bold = true }, + CmpItemAbbrMatchFuzzy = { fg = c.yellow, bold = true }, + CmpItemAbbrDeprecated = { fg = c.highlight, strikethrough = true }, + CmpItemKind = { fg = c.green, bold = true }, + CmpItemMenu = { fg = c.blue, bold = true }, + + -- tabbyml/tabby + TabbyCompletion = { link = 'Comment' }, + + -- LuaSnip + LuasnipChoiceNodeActive = { fg = c.orange }, + LuasnipInsertNodeActive = { fg = c.blue }, + + -- gitsigns.nvim + GitSignsAdd = { fg = c.green, bold = true }, + GitSignsAddNr = { fg = c.green }, + GitSignsAddInline = { fg = c.black, bg = c.green }, + GitSignsChange = { fg = c.yellow, bold = true }, + GitSignsChangeNr = { fg = c.yellow }, + GitSignsChangeInline = { fg = c.black, bg = c.yellow }, + GitSignsDelete = { fg = c.red, bold = true }, + GitSignsDeleteNr = { fg = c.red }, + GitSignsDeleteInline = { fg = c.black, bg = c.red }, + GitSignsCurrentLineBlame = { fg = c.grey_bright, bold = true, italic = true }, + + -- git-conflict.nvim + GitConflictCurrent = { bg = util.blend(c.green, c.black, 0.2) }, + GitConflictIncoming = { bg = util.blend(c.blue, c.black, 0.2) }, + GitConflictAncestor = { bg = util.blend(c.yellow, c.black, 0.2) }, + + -- rainbow-delimiters.nvim + RainbowDelimiterRed = { fg = c.red, bold = true }, + RainbowDelimiterOrange = { fg = c.orange, bold = true }, + RainbowDelimiterYellow = { fg = c.yellow, bold = true }, + RainbowDelimiterGreen = { fg = c.green, bold = true }, + RainbowDelimiterCyan = { fg = c.cyan, bold = true }, + RainbowDelimiterBlue = { fg = c.blue, bold = true }, + RainbowDelimiterViolet = { fg = c.purple, bold = true }, + + -- dashboard.nvim / alpha-nvim + DashboardHeader = { fg = c.cyan, bold = true }, + DashboardCenter = { fg = c.blue, bold = true }, + DashboardShortcut = { fg = c.green, bold = true }, + DashboardFooter = { fg = c.yellow, bold = true }, + + -- nvim-tree.lua + NvimTreeNormal = { fg = c.fg }, + NvimTreeIndentMarker = { fg = c.grey3 }, + NvimTreeFolderIcon = { fg = c.fg }, + NvimTreeRootFolder = { fg = c.teal, bold = true }, + NvimTreeFolderName = { fg = c.blue }, + NvimTreeEmptyFolderName = { fg = c.grey_bright }, + NvimTreeImageFile = { fg = c.yellow }, + NvimTreeExecFile = { fg = c.green }, + NvimTreeSpecialFile = { fg = c.cyan, underline = true }, + NvimTreeGitDirty = { fg = c.yellow }, + NvimTreeGitDeleted = { fg = c.red }, + NvimTreeWindowPicker = { fg = c.white2, bg = c.blue, bold = true }, + + -- which-key.nvim + WhichKey = { fg = c.green, bold = true }, + WhichKeyGroup = { fg = c.cyan }, + WhichKeyDesc = { fg = c.blue }, + WhichKeySeparator = { fg = c.grey_bright }, + WhichKeyFloat = { link = 'NormalFloat' }, + WhichKeyValue = { link = 'Comment' }, + + -- nvim-notify + NotifyERRORBorder = { fg = c.red }, + NotifyWARNBorder = { fg = c.yellow }, + NotifyINFOBorder = { fg = c.blue }, + NotifyDEBUGBorder = { fg = c.green }, + NotifyTRACEBorder = { fg = c.white2 }, + NotifyERRORIcon = { link = 'NotifyERRORBorder' }, + NotifyWARNIcon = { link = 'NotifyWARNBorder' }, + NotifyINFOIcon = { link = 'NotifyINFOBorder' }, + NotifyDEBUGIcon = { link = 'NotifyDEBUGBorder' }, + NotifyTRACEIcon = { link = 'NotifyTRACEBorder' }, + NotifyERRORTitle = { link = 'NotifyERRORBorder' }, + NotifyWARNTitle = { link = 'NotifyWARNBorder' }, + NotifyINFOTitle = { link = 'NotifyINFOBorder' }, + NotifyDEBUGTitle = { link = 'NotifyDEBUGBorder' }, + NotifyTRACETitle = { link = 'NotifyTRACEBorder' }, + NotifyERRORBody = { link = 'Normal' }, + NotifyWARNBody = { link = 'Normal' }, + NotifyINFOBody = { link = 'Normal' }, + NotifyDEBUGBody = { link = 'Normal' }, + NotifyTRACEBody = { link = 'Normal' }, + + -- nvim-scrollbar + ScrollbarHandle = { bg = c.grey1 }, + ScrollbarError = { fg = c.red, bold = true }, + ScrollbarErrorHandle = { fg = c.red, bg = c.grey1, bold = true }, + ScrollbarWarn = { fg = c.yellow, bold = true }, + ScrollbarWarnHandle = { fg = c.yellow, bg = c.grey1, bold = true }, + ScrollbarInfo = { fg = c.blue, bold = true }, + ScrollbarInfoHandle = { fg = c.blue, bg = c.grey1, bold = true }, + ScrollbarHint = { fg = c.cyan, bold = true }, + ScrollbarHintHandle = { fg = c.cyan, bg = c.grey1, bold = true }, + ScrollbarMisc = { fg = c.white2, bold = true }, + ScrollbarMiscHandle = { fg = c.white2, bg = c.grey1, bold = true }, + + -- indent-blankline.nvim + IblIndent = { fg = c.grey1 }, + IblScope = { fg = c.grey_bright }, + + -- headlines.nvim + -- NOTE: keep bg of these in sync with OrgHeadlineLevel{1..6} + Headline1 = { bg = util.blend(c.purple, c.black, 0.09), bold = true }, + Headline2 = { bg = util.blend(c.blue, c.black, 0.09), bold = true }, + Headline3 = { bg = util.blend(c.green, c.black, 0.09), bold = true }, + Headline4 = { bg = util.blend(c.yellow, c.black, 0.09), bold = true }, + Headline5 = { bg = util.blend(c.orange, c.black, 0.09), bold = true }, + Headline6 = { bg = util.blend(c.fg, c.black, 0.09), bold = true }, + Quote = { fg = c.grey_bright }, + CodeBlock = { bg = c.grey1 }, + Dash = { fg = c.blue, bold = true }, + + -- nvim-orgmode + OrgHeadlineLevel1 = { fg = c.purple, bold = true }, + OrgHeadlineLevel2 = { fg = c.blue, bold = true }, + OrgHeadlineLevel3 = { fg = c.green, bold = true }, + OrgHeadlineLevel4 = { fg = c.yellow, bold = true }, + OrgHeadlineLevel5 = { fg = c.orange, bold = true }, + OrgHeadlineLevel6 = { fg = c.fg, bold = true }, + OrgTODO = { fg = c.green, bold = true }, + OrgDONE = { fg = c.purple, bold = true }, + OrgTSCheckbox = { link = '@text.todo.unchecked' }, + OrgTSCheckboxChecked = { link = '@text.todo.checked' }, + OrgTSCheckboxHalfChecked = { fg = c.blue, bold = true }, + OrgAgendaDealine = { fg = c.red, bold = true }, + OrgAgendaScheduled = { fg = c.green, bold = true }, + OrgAgendaScheduledPast = { fg = c.blue, bold = true }, + + -- sniprun.nvim + SniprunVirtualTextOk = { bg = c.cyan, fg = c.black }, + SniprunFloatingWinOk = { fg = c.cyan }, + SniprunVirtualTextErr = { bg = c.orange, fg = c.black }, + SniprunFloatingWinErr = { fg = c.orange }, + + -- trouble.nvim + LspTroubleText = { fg = c.blue, bold = true }, + + -- telescope.nvim + TelescopeBorder = { fg = c.cyan }, + TelescopeTitle = { fg = c.black, bg = c.cyan, bold = true }, + TelescopeSelection = { fg = c.fg, bg = c.grey1 }, + TelescopeMultiSelection = { fg = c.fg, bg = c.grey1, bold = true }, + TelescopeSelectionCaret = { fg = c.red, bg = c.grey1, bold = true }, + TelescopeMatching = { fg = c.yellow, bold = true }, + + -- overseer.nvim + OverseerPENDING = { fg = c.purple, bold = true }, + OverseerRUNNING = { fg = c.blue, bold = true }, + OverseerSUCCESS = { fg = c.green, bold = true }, + OverseerCANCELED = { fg = c.yellow, bold = true }, + OverseerFAILURE = { fg = c.red, bold = true }, + OverseerTask = { fg = c.cyan }, + OverseerComponent = { fg = c.yellow }, + OverseerField = { fg = c.green }, + + -- neogit + NeogitBranch = { fg = c.purple, bold = true }, + NeogitRemote = { fg = c.green, bold = true }, + NeogitObjectId = { fg = c.teal }, + NeogitStash = { link = 'Comment' }, + NeogitFold = { fg = c.highlight, bold = true }, + NeogitRebaseDone = { link = 'Comment' }, + NeogitTagName = { link = 'NeogitBranch' }, + NeogitTagDistance = { fg = c.red, bold = true }, + NeogitSectionHeader = { fg = c.blue, bold = true }, + NeogitChangeModified = { fg = c.cyan, bold = true }, + NeogitChangeAdded = { fg = c.green, bold = true }, + NeogitChangeDeleted = { fg = c.red, bold = true }, + NeogitChangeRenamed = { fg = c.purple, bold = true }, + NeogitChangeUpdated = { fg = c.cyan, bold = true }, + NeogitChangeCopied = { fg = c.blue, bold = true }, + NeogitChangeBothModified = { fg = c.yellow, bold = true }, + NeogitChangeNewFile = { fg = c.green, bold = true }, + NeogitHunkHeader = { fg = c.yellow, bold = true }, + NeogitHunkHeaderHighlight = { fg = c.yellow, bg = util.blend(c.yellow, c.black, 0.09), bold = true }, + NeogitDiffContext = { fg = c.blue }, + NeogitDiffContextHighlight = { fg = c.blue, bg = util.blend(c.blue, c.black, 0.09) }, + NeogitDiffAdd = { fg = c.green }, + NeogitDiffAddHighlight = { fg = c.green, bg = util.blend(c.green, c.black, 0.09) }, + NeogitDiffDelete = { fg = c.red }, + NeogitDiffDeleteHighlight = { fg = c.red, bg = util.blend(c.red, c.black, 0.09) }, + NeogitDiffHeader = { fg = c.purple, bold = true }, + NeogitDiffHeaderHighlight = { fg = c.purple, bg = util.blend(c.purple, c.black, 0.09), bold = true }, + NeogitCursorLine = { link = 'CursorLine' }, + NeogitFilePath = { fg = c.teal, bold = true }, + NeogitCommitViewHeader = { fg = c.white2, bg = c.dark_blue, bold = true }, + NeogitGraphBlack = { fg = c.grey2 }, + NeogitGraphBlackBold = { fg = c.grey2, bold = true }, + NeogitGraphRed = { fg = c.red }, + NeogitGraphRedBold = { fg = c.red, bold = true }, + NeogitGraphGreen = { fg = c.green }, + NeogitGraphGreenBold = { fg = c.green, bold = true }, + NeogitGraphYellow = { fg = c.yellow }, + NeogitGraphYellowBold = { fg = c.yellow, bold = true }, + NeogitGraphBlue = { fg = c.blue }, + NeogitGraphBlueBold = { fg = c.blue, bold = true }, + NeogitGraphPurple = { fg = c.purple }, + NeogitGraphPurpleBold = { fg = c.purple, bold = true }, + NeogitGraphCyan = { fg = c.cyan }, + NeogitGraphCyanBold = { fg = c.cyan, bold = true }, + NeogitGraphWhite = { fg = c.white2 }, + NeogitGraphWhiteBold = { fg = c.white2, bold = true }, + NeogitGraphGray = { fg = c.highlight }, + NeogitGraphGrayBold = { fg = c.highlight, bold = true }, + NeogitGraphOrange = { fg = c.orange }, + NeogitSignatureGood = { fg = c.green, bold = true }, + NeogitSignatureBad = { fg = c.red, bold = true }, + NeogitSignatureMissing = { fg = c.yellow, bold = true }, + NeogitSignatureNone = { link = 'NeogitObjectId' }, + NeogitSignatureGoodUnknown = { fg = c.green, italic = true }, + NeogitSignatureGoodExpired = { link = 'NeogitSignatureGoodUnknown' }, + NeogitSignatureGoodExpiredKey = { link = 'NeogitSignatureGoodUnknown' }, + NeogitSignatureGoodRevokedKey = { fg = c.cyan, bold = true }, + NeogitNotificationInfo = { link = 'NotifyINFOTitle' }, + NeogitNotificationWarning = { link = 'NotifyWARNTitle' }, + NeogitNotificationError = { link = 'NotifyERRORTitle' }, + NeogitPopupSectionTitle = { fg = c.blue, bold = true }, + NeogitPopupBranchName = { link = 'NeogitBranch' }, + NeogitPopupBold = { link = '@text.strong' }, + NeogitPopupSwitchKey = { fg = c.green }, + NeogitPopupSwitchEnabled = { link = 'Comment' }, + NeogitPopupSwitchDisabled = { fg = c.yellow, italic = true }, + NeogitPopupOptionKey = { link = 'NeogitPopupSwitchKey' }, + NeogitPopupOptionEnabled = { link = 'NeogitPopupSwitchEnabled' }, + NeogitPopupOptionDisabled = { link = 'NeogitPopupSwitchDisabled' }, + NeogitPopupConfigKey = { link = 'NeogitPopupSwitchKey' }, + NeogitPopupConfigEnabled = { link = 'NeogitPopupSwitchEnabled' }, + NeogitPopupConfigDisabled = { link = 'NeogitPopupSwitchDisabled' }, + NeogitPopupActionKey = { link = 'NeogitPopupSwitchKey' }, + NeogitPopupActionEnabled = { link = 'NeogitPopupSwitchEnabled' }, + NeogitPopupActionDisabled = { link = 'NeogitPopupSwitchDisabled' }, + + -- flash.nvim + FlashBackdrop = { fg = c.grey_bright }, + FlashMatch = { fg = c.white2, bold = true }, + FlashLabel = { fg = c.white2, bg = c.red, bold = true }, + + -- nvim-navic + NavicText = { link = 'Normal' }, + NavicSeparator = { fg = c.cyan }, + + -- nvim-coverage + CoverageCovered = { fg = c.green }, + CoverageUncovered = { fg = c.red }, + CoveragePartial = { fg = c.yellow }, + CoverageSummaryHeader = { fg = c.purple, bold = true }, + CoverageSummaryPass = { fg = c.green, bold = true }, + CoverageSummaryFail = { fg = c.red, bold = true }, + + -- neotest + NeotestPassed = { fg = c.green }, + NeotestFailed = { fg = c.red }, + NeotestRunning = { fg = c.yellow }, + NeotestSkipped = { fg = c.cyan }, + NeotestNamespace = { fg = c.red, bold = true }, + NeotestFile = { fg = c.cyan }, + NeotestDir = { link = 'NeotestFile' }, + NeotestIndent = { link = 'NvimTreeIndentMarker' }, + NeotestExpandMarker = { link = 'NvimTreeIndentMarker' }, + NeotestAdapterName = { fg = c.purple }, + NeotestWinSelect = { fg = c.cyan, bold = true }, + NeotestMarked = { fg = c.blue, bold = true }, + NeotestTarget = { fg = c.red }, + NeotestWatching = { fg = c.yellow }, + + -- lazy.nvim + LazyDimmed = { link = 'Normal' }, + LazyProp = { fg = c.highlight, bold = true }, + LazyH1 = { fg = c.white2, bg = c.dark_blue, bold = true }, + LazyH2 = { fg = c.purple, bold = true }, + LazyButton = { fg = c.white2, bg = c.grey1 }, + LazyButtonActive = { link = 'LazyH1' }, + LazySpecial = { fg = c.white2 }, + LazyCommit = { fg = c.teal }, + LazyReasonImport = { fg = c.teal }, + LazyReasonRuntime = { fg = c.green }, + LazyReasonCmd = { fg = c.cyan, bold = true }, + LazyReasonEvent = { fg = c.yellow, bold = true }, + LazyReasonFt = { fg = c.purple, bold = true }, + LazyReasonKeys = { fg = c.green, bold = true }, + LazyReasonPlugin = { fg = c.blue, bold = true }, + LazyReasonRequire = { fg = c.teal, bold = true }, + LazyReasonSource = { fg = c.red, bold = true }, + LazyReasonStart = { fg = c.orange, bold = true }, +} diff --git a/lua/user/themes/nord/groups/treesitter.lua b/lua/user/themes/nord/groups/treesitter.lua new file mode 100644 index 0000000..018f473 --- /dev/null +++ b/lua/user/themes/nord/groups/treesitter.lua @@ -0,0 +1,82 @@ +local c = require('user.themes.nord.colors') + +-- :help nvim-treesitter-highlights +return { + ['@annotation'] = { fg = c.blue, italic = true }, + ['@attribute'] = { fg = c.purple }, + ['@boolean'] = { link = 'Boolean' }, + ['@character'] = { link = 'Character' }, + ['@character.special'] = { link = 'SpecialChar' }, + ['@comment'] = { link = 'Comment' }, + ['@conditional'] = { link = 'Conditional' }, + ['@constant'] = { link = 'Constant' }, + ['@constant.builtin'] = { fg = c.teal }, + ['@constant.macro'] = { fg = c.teal }, + ['@conceal'] = { link = 'Conceal' }, + ['@constructor'] = { fg = c.blue }, + ['@debug'] = { link = 'Debug' }, + ['@define'] = { link = 'Define' }, + ['@error'] = { fg = c.red }, + ['@exception'] = { link = 'Exception' }, + ['@field'] = { fg = c.teal }, + ['@float'] = { link = 'Float' }, + ['@function'] = { link = 'Function' }, + ['@function.call'] = { link = 'Function' }, + ['@function.builtin'] = { link = 'Function' }, + ['@function.macro'] = { fg = c.teal }, + ['@include'] = { link = 'Include' }, + ['@keyword'] = { link = 'Keyword' }, + ['@keyword.function'] = { link = 'Keyword' }, + ['@keyword.return'] = { link = 'Keyword' }, + ['@keyword.operator'] = { link = 'Keyword' }, + ['@label'] = { link = 'Label' }, + ['@method'] = { fg = c.teal }, + ['@method.call'] = { fg = c.teal, bold = true }, + ['@namespace'] = { fg = c.white1 }, + -- ['@none'] = { fg = 'NONE', bg = 'NONE' }, + ['@number'] = { link = 'Number' }, + ['@operator'] = { link = 'Operator' }, + ['@parameter'] = { fg = c.purple, italic = true }, + ['@parameter.reference'] = { fg = c.purple, italic = true }, + ['@preproc'] = { link = 'PreProc' }, + ['@property'] = { fg = c.teal }, + ['@punctuation.delimiter'] = { link = 'Delimiter' }, + ['@punctuation.bracket'] = { link = 'Delimiter' }, + ['@punctuation.special'] = { link = 'Delimiter' }, + ['@repeat'] = { link = 'Repeat' }, + ['@storageclass'] = { link = 'StorageClass' }, + ['@string'] = { link = 'String' }, + ['@string.regex'] = { fg = c.teal }, + ['@string.escape'] = { fg = c.purple }, + ['@string.special'] = { link = 'Special' }, + ['@symbol'] = { fg = c.purple }, + ['@tag'] = { link = 'Tag' }, + ['@tag.attribute'] = { fg = c.orange, italic = true }, + ['@tag.delimiter'] = { fg = c.purple }, + ['@text'] = { link = 'Normal' }, + ['@text.diff.add'] = { link = 'DiffAdd' }, + ['@text.diff.delete'] = { link = 'DiffDelete' }, + ['@text.strong'] = { bold = true }, + ['@text.emphasis'] = { italic = true }, + ['@text.underline'] = { underline = true }, + ['@text.strike'] = { strikethrough = true }, + ['@text.title'] = { fg = c.cyan, bold = true }, + ['@text.literal'] = { fg = c.green, italic = true }, + ['@text.uri'] = { fg = c.green, underline = true }, + ['@text.math'] = { fg = c.yellow }, + ['@text.reference'] = { fg = c.purple, italic = true }, + ['@text.environment'] = { fg = c.cyan }, + ['@text.environment.name'] = { fg = c.purple, bold = true }, + ['@text.todo'] = { link = 'Todo' }, + ['@text.note'] = { fg = c.blue, bold = true }, + ['@text.warning'] = { fg = c.yellow, bold = true }, + ['@text.danger'] = { fg = c.red, bold = true }, + ['@text.todo.unchecked'] = { fg = c.blue, bold = true }, + ['@text.todo.checked'] = { fg = c.green, bold = true }, + ['@type'] = { link = 'Type' }, + ['@type.builtin'] = { link = 'Type' }, + ['@type.qualifier'] = { fg = c.blue, bold = true, italic = true }, + ['@type.definition'] = { link = 'Typedef' }, + ['@variable'] = { link = 'Variable' }, + ['@variable.builtin'] = { link = 'Variable' }, +} diff --git a/lua/user/themes/nord/init.lua b/lua/user/themes/nord/init.lua new file mode 100644 index 0000000..5019163 --- /dev/null +++ b/lua/user/themes/nord/init.lua @@ -0,0 +1,31 @@ +local M = {} +local c = require('user.themes.nord.colors') + +M.termcolors = { + c.grey1, + c.red, + c.green, + c.yellow, + c.blue, + c.purple, + c.cyan, + c.white1, + c.grey_bright, + c.red, + c.green, + c.yellow, + c.blue, + c.purple, + c.teal, + c.white2, +} + +M.highlights = vim.tbl_extend( + 'force', + require('user.themes.nord.groups.editor'), + require('user.themes.nord.groups.treesitter'), + require('user.themes.nord.groups.lsp'), + require('user.themes.nord.groups.plugins') +) + +return M diff --git a/lua/user/themes/onedark/colors.lua b/lua/user/themes/onedark/colors.lua new file mode 100644 index 0000000..fa71c53 --- /dev/null +++ b/lua/user/themes/onedark/colors.lua @@ -0,0 +1,23 @@ +local onedark = { + black = '#282C34', + grey1 = '#3E4452', + grey2 = '#4B5263', + grey3 = '#5C6470', + grey_bright = '#73797E', + fg = '#ABB2BF', + white1 = '#BBC2CF', + white2 = '#DFDFDF', + teal = '#5699AF', + cyan = '#56B6C2', + blue = '#61AFEF', + dark_blue = '#2257A0', + red = '#E06C75', + dark_red = '#BE5046', + orange = '#D19A66', + yellow = '#E5C07B', + green = '#98C379', + purple = '#C678DD', + highlight = '#9CA0A4', +} + +return onedark diff --git a/lua/user/themes/onedark/init.lua b/lua/user/themes/onedark/init.lua new file mode 100644 index 0000000..5044d6f --- /dev/null +++ b/lua/user/themes/onedark/init.lua @@ -0,0 +1,3 @@ +local M = {} + +return M diff --git a/lua/user/util/color.lua b/lua/user/util/color.lua new file mode 100644 index 0000000..4056c29 --- /dev/null +++ b/lua/user/util/color.lua @@ -0,0 +1,82 @@ +local M = {} + +--- Convert Hexadecimal color string to list of decimal Red, Green, Blue values +---@param color string e.g. '#2e3440' +---@return table +local function hex2rgb(color) + color = string.lower(color) + return { + tonumber(color:sub(2, 3), 16), + tonumber(color:sub(4, 5), 16), + tonumber(color:sub(6, 7), 16), + } +end + +--- Lighten/Darken the colors with the amount provided +---@param hex string e.g. '#2e3440' +---@param alpha number a number in the range [-1, 1] +---@return string +function M.shade(hex, alpha) + vim.validate { + percent = { alpha, function(arg) return -1 <= arg and arg <= 1 end, 'a number in the range [-1, 1]' } + } + + local rgb = hex2rgb(hex) + local function shadeChannel(i) + return math.min(math.floor(math.max(0, rgb[i] * (1 + alpha))), 255) + end + + return string.format('#%02x%02x%02x', shadeChannel(1), shadeChannel(2), shadeChannel(3)) +end + +---@param foreground string foreground color +---@param background string background color +---@param alpha number number within range [-1, 1] +function M.blend(foreground, background, alpha) + vim.validate { + percent = { alpha, function(arg) return -1 <= arg and arg <= 1 end, 'a number in the range [-1, 1]' } + } + + local fg = hex2rgb(foreground) + local bg = hex2rgb(background) + + local blendChannel = function(i) + local ret = (alpha * fg[i] + (1 - alpha) * bg[i]) + return math.floor(math.min(math.max(0, ret), 255) + 0.5) + end + + return string.format('#%02x%02x%02x', blendChannel(1), blendChannel(2), blendChannel(3)) +end + +function M.darken(hex, alpha, bg) + return M.blend(hex, alpha, bg or '#000000') +end + +function M.lighten(hex, alpha, fg) + return M.blend(hex, alpha, fg or '#ffffff') +end + +--- Load defined Neovim theme inside lua/user/themes/ and setup highlights +---@param theme string +function M.load_theme(theme) + -- Reset + if vim.g.colors_name then + vim.cmd('highlight clear') + end + + -- Get theme specs + vim.g.colors_name = theme + local t = require('user.themes.' .. theme) + + -- Set base16 terminal colors + for index, val in ipairs(t.termcolors) do + vim.g['terminal_color_' .. (index - 1)] = val + end + + -- Load highlight groups + for group, val in pairs(t.highlights) do + vim.api.nvim_set_hl(0, group, val) + end +end + +return M diff --git a/lua/user/util/lsp.lua b/lua/user/util/lsp.lua new file mode 100644 index 0000000..13b456b --- /dev/null +++ b/lua/user/util/lsp.lua @@ -0,0 +1,37 @@ +local M = {} + +--- Return the list of active LSP clients +---@return table +function M.lsp_active_clients() + local clients = {} + + for _, client in ipairs(vim.lsp.get_active_clients { bufnr = 0 }) do + table.insert(clients, client.name) + end + + return clients +end + +-- Re-implement nvim-lightbulb naively +-- Update status text indicating whether a CodeAction exists at cursor position +function M.codeAction_on_attach() + local params = vim.lsp.util.make_range_params() + params.context = { diagnostics = vim.lsp.diagnostic.get_line_diagnostics() } + vim.lsp.buf_request_all(0, 'textDocument/codeAction', params, function(responses) + local has_actions = false + for _, resp in pairs(responses) do + if resp.result and not vim.tbl_isempty(resp.result) then + has_actions = true + break + end + end + + if has_actions then + vim.b.has_code_action_text = '󰌵 Code action' + else + vim.b.has_code_action_text = nil + end + end) +end + +return M diff --git a/lua/user/util/misc.lua b/lua/user/util/misc.lua new file mode 100644 index 0000000..bec6294 --- /dev/null +++ b/lua/user/util/misc.lua @@ -0,0 +1,52 @@ +local M = {} + +function M.buffer_not_empty() + if vim.fn.empty(vim.fn.expand('%:t')) ~= 1 then + return true + end + return false +end + +---@param plugin string +---@return boolean +function M.has(plugin) + return require('lazy.core.config').plugins[plugin] ~= nil +end + +--- Switch between defined values of an option +---@param silent boolean? +---@param values? {[1]:any, [2]:any} +function M.toggle(option, silent, values) + local util = require('lazy.core.util') + if values then + if vim.opt_local[option]:get() == values[1] then + vim.opt_local[option] = values[2] + else + vim.opt_local[option] = values[1] + end + return util.info('Set ' .. option .. ' to ' .. vim.opt_local[option]:get(), { title = 'Vim option' }) + end + + vim.opt_local[option] = not vim.opt_local[option]:get() + if not silent then + if vim.opt_local[option]:get() then + util.info('Enabled ' .. option, { title = 'Vim option' }) + else + util.warn('Disabled ' .. option, { title = 'Vim option' }) + end + end +end + +--- Return the 1st found matched file/directory names +---@param files string|string[] +---@return fun(src: string): nil|string +function M.root_has_file(files) + return function(src) + local found = vim.fs.find(files, { upward = true, path = src })[1] + if found then + return vim.fs.dirname(found) + end + end +end + +return M diff --git a/magefile.go b/magefile.go new file mode 100644 index 0000000..bd5e491 --- /dev/null +++ b/magefile.go @@ -0,0 +1,313 @@ +//go:build mage +// +build mage + +package main + +// NOTE: intentionally not handle errors (and just log.Fatal everywhere) to have nice terminal output. +// (output received errors via logrus instead of mage) + +import ( + "fmt" + "os" + "os/exec" + "path/filepath" + "sort" + "sync" + + "github.com/go-playground/validator/v10" + yaml "github.com/goccy/go-yaml" + "github.com/magefile/mage/mg" + log "github.com/sirupsen/logrus" +) + +type Target struct { + Type string `yaml:"type" validate:"required,oneof=python nodejs"` + Category string `yaml:"category" validate:"required,oneof=lsp dap lint"` + Pkgs []string `yaml:"pkgs,omitempty" validate:"omitempty,min=1,dive,min=1"` +} + +type Install mg.Namespace + +var Aliases = map[string]any{ + // Shorter task names to type + "i": Install.Pkg, + "i:lsp": Install.Lsp, + "i:dap": Install.Dap, + "i:lint": Install.Linters, + "i:all": Install.All, + + // `Install` is already registered as a type, so can't make an original func name of it + "install": Install.Pkg, +} + +var ( + inputFile = "pkgs.yaml" + validate = validator.New() +) + +func init() { + log.SetFormatter(&log.TextFormatter{ + DisableTimestamp: true, + DisableQuote: true, + }) + log.SetOutput(os.Stderr) + log.SetLevel(log.InfoLevel) +} + +// List lists installable targets +func List() { + targets, err := readTargets(inputFile) + if err != nil { + log.Fatal(err) + } + + // Categorize the list + categories := map[string][]string{ + "LSP servers": {}, + "DAP servers": {}, + "Linters/Formatters": {}, + } + + for target, spec := range targets { + switch spec.Category { + case "lsp": + categories["LSP servers"] = append(categories["LSP servers"], target) + case "dap": + categories["DAP servers"] = append(categories["DAP servers"], target) + case "lint": + categories["Linters/Formatters"] = append(categories["Linters/Formatters"], target) + } + } + + // Iterate over a list, to make the output stable + categoryList := []string{} + for name := range categories { + categoryList = append(categoryList, name) + } + sort.Strings(categoryList) + + for _, name := range categoryList { + fmt.Printf("* %s:\n", name) + sort.Strings(categories[name]) + for _, target := range categories[name] { + fmt.Printf(" - %s\n", target) + } + } +} + +// Pkg installs the specified target +func (Install) Pkg(target string) { + targets, err := readTargets(inputFile) + if err != nil { + log.Fatal(err) + } + + install(targets, target) +} + +// Lsp installs all LSP servers +func (Install) Lsp() { + targets, err := readTargets(inputFile) + if err != nil { + log.Fatal(err) + } + + log.Warn("Installing LSP servers") + + filterdTargets := filterTargets(targets, "lsp") + + var wg sync.WaitGroup + wg.Add(len(filterdTargets)) + + for target := range filterdTargets { + go func(t string) { + defer wg.Done() + install(filterdTargets, t) + }(target) + } + + wg.Wait() +} + +// Dap installs all DAP servers +func (Install) Dap() { + targets, err := readTargets(inputFile) + if err != nil { + log.Fatal(err) + } + + log.Warn("Installing DAP servers") + + filterdTargets := filterTargets(targets, "dap") + + var wg sync.WaitGroup + wg.Add(len(filterdTargets)) + + for target := range filterdTargets { + go func(t string) { + defer wg.Done() + install(filterdTargets, t) + }(target) + } + + wg.Wait() +} + +// Linters installs all linters and formatters +func (Install) Linters() { + targets, err := readTargets(inputFile) + if err != nil { + log.Fatal(err) + } + + log.Warn("Installing linters and formatters") + + filterdTargets := filterTargets(targets, "lint") + + var wg sync.WaitGroup + wg.Add(len(filterdTargets)) + + for target := range filterdTargets { + go func(t string) { + defer wg.Done() + install(filterdTargets, t) + }(target) + } + + wg.Wait() +} + +// All installs everything +func (Install) All() { + targets, err := readTargets(inputFile) + if err != nil { + log.Fatal(err) + } + + log.Warn("Installing everything") + + var wg sync.WaitGroup + wg.Add(len(targets)) + + for target := range targets { + go func(t string) { + defer wg.Done() + install(targets, t) + }(target) + } + + wg.Wait() +} + +// readTargets returns the list of installable targets from YAML input file +func readTargets(file string) (map[string]Target, error) { + input, err := os.ReadFile(file) + if err != nil { + return nil, err + } + + targets := map[string]Target{} + err = yaml.Unmarshal(input, &targets) + if err != nil { + return nil, err + } + + // Validate the received input before return it + for target, spec := range targets { + err = validate.Struct(spec) + if err != nil { + return nil, fmt.Errorf("[%s] invalid input: %v", target, err) + } + } + + return targets, nil +} + +// filterTargets returns the list of installable targets with category filter +func filterTargets(targets map[string]Target, category string) map[string]Target { + result := map[string]Target{} + for name, spec := range targets { + if spec.Category == category { + result[name] = spec + } + } + return result +} + +// install installs the specified target if it existed inside the provided target list +func install(targets map[string]Target, target string) { + targetLog := log.WithField("target", target) + + spec, ok := targets[target] + if !ok { + targetLog.Fatal("invalid target") + } + + targetLog.Warn("Installing target") + + // Create the parent directory + data_dir, ok := os.LookupEnv("XDG_DATA_HOME") + if !ok { + data_dir = filepath.Join(os.Getenv("HOME"), ".local", "share") + } + install_dir := filepath.Join(data_dir, "nvim", spec.Category, target) + err := os.MkdirAll(install_dir, 0o755) + if err != nil { + targetLog.Fatal(err) + } + + // Use the target's name as its package name, if not specified + if len(spec.Pkgs) == 0 { + spec.Pkgs = append(spec.Pkgs, target) + } + + switch spec.Type { + case "nodejs": + pnpm, err := exec.LookPath("pnpm") + if err != nil { + log.Fatal(err) + } + + for _, pkg := range spec.Pkgs { + cmd := [3]string{pnpm, "add", fmt.Sprintf("%s@latest", pkg)} + targetLog.Infof("%s %s %s", cmd[0], cmd[1], cmd[2]) + + execCmd := exec.Command(cmd[0], cmd[1:]...) + execCmd.Dir = install_dir + execCmd.Stdout = os.Stdout + execCmd.Stderr = os.Stderr + if err := execCmd.Run(); err != nil { + targetLog.Fatalf("failed to install %s: %v", pkg, err) + } + } + case "python": + python, err := exec.LookPath("python3") + if err != nil { + log.Fatal(err) + } + + // Always upgrade pip along the way + spec.Pkgs = append(spec.Pkgs, "pip") + + // Create a venv directory to install the target's packages into + execCmd := exec.Command(python, "-m", "venv", install_dir) + execCmd.Stdout = os.Stdout + execCmd.Stderr = os.Stderr + if err := execCmd.Run(); err != nil { + targetLog.Fatalf("failed to create venv: %v", err) + } + + for _, pkg := range spec.Pkgs { + cmd := [4]string{filepath.Join("bin", "pip3"), "install", "-U", pkg} + targetLog.Infof("%s %s %s %s", cmd[0], cmd[1], cmd[2], cmd[3]) + + execCmd = exec.Command(cmd[0], cmd[1:]...) + execCmd.Dir = install_dir + execCmd.Stdout = os.Stdout + execCmd.Stderr = os.Stderr + if err := execCmd.Run(); err != nil { + targetLog.Fatalf("failed to install %s: %v", pkg, err) + } + } + } +} diff --git a/pkgs.yaml b/pkgs.yaml new file mode 100644 index 0000000..dd77ced --- /dev/null +++ b/pkgs.yaml @@ -0,0 +1,98 @@ +--- +# DAP servers ==================================== + +debugpy: + category: dap + type: python + +# LSP servers ==================================== + +ansiblels: &nodejs_lsp + category: lsp + type: nodejs + pkgs: ['@ansible/ansible-language-server'] + +awk_ls: + <<: *nodejs_lsp + pkgs: [awk-language-server] + +bashls: + <<: *nodejs_lsp + pkgs: [bash-language-server] + +dockerls: + <<: *nodejs_lsp + pkgs: [dockerfile-language-server-nodejs] + +emmet_language_server: + <<: *nodejs_lsp + pkgs: ['@olrtg/emmet-language-server'] + +pylsp: + category: lsp + type: python + pkgs: + - python-lsp-server[all] + # - pyls-flake8 + # - pylsp-mypy + # - pyls-isort + # - pyls-memestra + - pylsp-rope + # - python-lsp-black + +ruff_lsp: + category: lsp + type: python + pkgs: [ruff-lsp] + +sqlls: + <<: *nodejs_lsp + pkgs: [sql-language-server] + +svelte: + <<: *nodejs_lsp + pkgs: [svelte-language-server] + +tailwindcss: + <<: *nodejs_lsp + pkgs: ['@tailwindcss/language-server'] + +tsserver: + <<: *nodejs_lsp + pkgs: + - typescript + - typescript-language-server + +volar: + <<: *nodejs_lsp + pkgs: + - typescript + - '@vue/language-server' + +vscode: + <<: *nodejs_lsp + pkgs: [vscode-langservers-extracted] + +yamlls: + <<: *nodejs_lsp + pkgs: ['yaml-language-server'] + +# Linters & Formatters =========================== + +codespell: &python_lint + category: lint + type: python + +cspell: &nodejs_lint + category: lint + type: nodejs + +proselint: *python_lint + +sql_formatter: + <<: *nodejs_lint + pkgs: [sql-formatter] + +sqlfluff: *python_lint + +textlint: *nodejs_lint diff --git a/spell/en.utf-8.add b/spell/en.utf-8.add new file mode 100644 index 0000000..851aedd --- /dev/null +++ b/spell/en.utf-8.add @@ -0,0 +1,17 @@ +docker +podman +nerdctl +kubernetes +k0s +k3s +k8s +k9s +nftables +linux +vim +nvim +neovim +Neovim +dotfiles +lua +yaml