diff --git a/home/features/neovim/config/lua/my/init.lua b/home/features/neovim/config/lua/my/init.lua index 74d201a..4e3b81b 100644 --- a/home/features/neovim/config/lua/my/init.lua +++ b/home/features/neovim/config/lua/my/init.lua @@ -1,11 +1,14 @@ local M = {} function M.setup() + vim.opt.runtimepath:append(vim.g.nix_extra_runtime) + local tempest = require("my.tempest") + local nix = require("nix") -- Import my other files - require("my.options").setup() + tempest.configureMany(nix.pre) require("my.keymaps").setup() require("my.lazy").setup() - require("my.abbreviations").setup() + tempest.configureMany(nix.post) end return M diff --git a/home/features/neovim/config/lua/my/keymaps.lua b/home/features/neovim/config/lua/my/keymaps.lua index fae8005..76faee8 100644 --- a/home/features/neovim/config/lua/my/keymaps.lua +++ b/home/features/neovim/config/lua/my/keymaps.lua @@ -55,92 +55,11 @@ end -- }}} function M.setup() - -- {{{ Free up q and Q - M.move("q", "yq", { desc = "Record macro" }) - M.move("Q", "yQ") - -- }}} - -- {{{ Easier access to - M.move("", "a", { desc = "[A]lternate file" }) - -- }}} - -- {{{ Quit all buffers - M.nmap("Q", ":wqa", "Save all files and [q]uit") - -- }}} - -- {{{ Replace word in file - M.nmap("rw", ":%s//", "[R]eplace [w]ord in file") - -- }}} - -- {{{ Toggle settings - M.nmap( - "sw", - require("my.helpers.wrapMovement").toggle, - "toggle word [w]rap" - ) - -- }}} -- {{{ Text objects M.delimitedTextobject("q", '"', "[q]uotes") M.delimitedTextobject("a", "'", "[a]postrophes") M.delimitedTextobject("r", "[", "squa[r]e brackets") -- }}} - -- {{{Diagnostic keymaps - M.nmap("[d", vim.diagnostic.goto_prev, "Goto previous [d]iagnostic") - M.nmap("]d", vim.diagnostic.goto_next, "Goto next [d]iagnostic") - M.move("J", "qj") - M.nmap("J", vim.diagnostic.open_float, "Open current diagnostic") - M.nmap("D", vim.diagnostic.setloclist, "[D]iagnostic loclist") - -- }}} - -- {{{ Chords (save, clipboard) - -- Different chords get mapped to f keys by a custom script of mine. - -- In the future, I might get this on my keyboard firmware. - vim.keymap.set({ "i", "v" }, "", "", { desc = "Exit insert mode" }) -- Exit inset mode using *jk* - vim.keymap.set({ "n", "v" }, "", '"+', { desc = "Use global clipboard" }) -- Use global clipboard with *cp* - M.nmap("", "silent write", "Save current file", true) -- Save using *ji* - -- }}} - -- {{{ Shift-Enter for not continuing the current comment - -- This does not preserve intendation. Not sure what a better solution would look like. - vim.keymap.set("i", "", function() - vim.paste({ "", "" }, -1) - end, { desc = "Insert newline without continuing the current comment" }) - -- }}} - -- {{{ Allow quiting basic buffers with "qq" - vim.api.nvim_create_autocmd("FileType", { - pattern = { "help" }, - group = vim.api.nvim_create_augroup("BasicBufferQuitting", {}), - callback = function(event) - vim.keymap.set( - "n", - "qq", - "close", - { buffer = event.buf, silent = true, desc = "[q]uit current buffer" } - ) - end, - }) - -- }}} - -- {{{ Winblend - vim.api.nvim_create_autocmd("FileType", { - pattern = { "*" }, - group = vim.api.nvim_create_augroup("WinblendSettings", {}), - callback = function() - vim.opt.winblend = 0 - end, - }) - -- }}} - -- {{{ Manage cmdheight - vim.api.nvim_create_autocmd("CmdlineEnter", { - group = vim.api.nvim_create_augroup("SetCmdheightCmdlineEnter", {}), - callback = function() - if not vim.g.inside_venn then - vim.opt.cmdheight = 1 - end - end, - }) - vim.api.nvim_create_autocmd("CmdlineLeave", { - group = vim.api.nvim_create_augroup("SetCmdheightCmdlineLeave", {}), - callback = function() - if not vim.g.inside_venn then - vim.opt.cmdheight = 0 - end - end, - }) - -- }}} end return M diff --git a/home/features/neovim/config/lua/my/lazy.lua b/home/features/neovim/config/lua/my/lazy.lua index d8bd7fd..0772aa0 100644 --- a/home/features/neovim/config/lua/my/lazy.lua +++ b/home/features/neovim/config/lua/my/lazy.lua @@ -7,8 +7,8 @@ end function M.setup() require("lazy").setup({ importFrom("my.plugins"), - importFrom("nix.plugins"), importFrom("my.plugins.themes"), + unpack(require("nix").lazy), }, { defaults = { lazy = true }, install = { diff --git a/home/features/neovim/config/lua/my/tempest.lua b/home/features/neovim/config/lua/my/tempest.lua index bc930c9..9f4bb87 100644 --- a/home/features/neovim/config/lua/my/tempest.lua +++ b/home/features/neovim/config/lua/my/tempest.lua @@ -119,17 +119,26 @@ end function M.configure(opts, context) if type(opts) == "function" then - return M.configure(opts(context), context) + opts = opts(context) end - if type(opts.mk_context) == "function" then - context = opts.mk_context(context) + if type(opts) ~= "table" then + -- TODO: throw + return + end + + if type(opts.mkContext) == "function" then + context = opts.mkContext(context) end if type(opts.vim) == "table" then recursive_assign(opts.vim, vim) end + if type(opts.keys) == "function" then + opts.keys = opts.keys(context) + end + if type(opts.keys) == "table" then local keys = opts.keys @@ -143,6 +152,10 @@ function M.configure(opts, context) end end + if type(opts.autocmds) == "function" then + opts.autocmds = opts.autocmds(context) + end + if type(opts.autocmds) == "table" then local autocmds = opts.autocmds @@ -180,8 +193,10 @@ function M.configure(opts, context) end end -M.lazy = function(lazy, opts, spec) - return M.configure(spec, { lazy = lazy, opts = opts }) +function M.configureMany(specs, context) + for _, spec in ipairs(specs) do + M.configure(spec, context) + end end -- }}} -- {{{ Neovim env handling @@ -230,29 +245,46 @@ end -- {{{ Fixup lazy spec generated by nix function M.prepareLazySpec(spec) for _, module in ipairs(spec) do - if spec.tempest ~= nil then - spec.config = function(lazy, opts) - M.configure(spec.tempest, { lazy = lazy, opts = opts }) + if module.package ~= nil then + module[1] = module.package + module.package = nil + end + + local configType = type(module.config) + if configType == "function" or configType == "table" then + local previousConfig = module.config + module.config = function(lazy, opts) + M.configure(previousConfig, { lazy = lazy, opts = opts }) end end - if spec.dependencies ~= nil then - spec.dependencies = spec.dependencies.lua - end - - if spec.keys ~= nil then - local keys = spec.keys - if spec.keys.mapping ~= nil then - keys = { keys } + if module.keys ~= nil then + if type(module.keys) == "string" or module.keys.mapping ~= nil then + module.keys = { module.keys } end - for _, key in ipairs(keys) do - key[1] = key.mapping - if key.mode ~= nil then - key.mode = H.string_chars(key.mode) + for _, key in ipairs(module.keys) do + if type(key) ~= "string" then + key[1] = key.mapping + key.mapping = nil + if key.mode ~= nil then + key.mode = H.string_chars(key.mode) + end + if key.action ~= nil then + key[2] = key.action + key.action = nil + end end end end + + if type(module.cond) == "table" then + local final = true + for _, cond in ipairs(module.cond) do + final = final and cond + end + module.cond = final + end end end -- }}} diff --git a/home/features/neovim/default.nix b/home/features/neovim/default.nix index d510990..b9b2ecf 100644 --- a/home/features/neovim/default.nix +++ b/home/features/neovim/default.nix @@ -1,5 +1,1267 @@ { upkgs, pkgs, lib, config, inputs, ... }: let + korora = inputs.korora.lib; + nlib = import ../../../modules/common/korora-neovim.nix + { + inherit lib korora; + } + { + languageServerModule = "my.plugins.lspconfig"; + tempestModule = "my.tempest"; + }; + + generated = nlib.generateConfig (lib.fix (self: { + # {{{ Pre-plugin config + pre = { + # {{{ General options + "0:general-options".vim = { + g = { + # Disable filetype.vim + do_filetype_lua = true; + did_load_filetypes = false; + + # Set leader + mapleader = " "; + }; + + opt = { + # Basic options + joinspaces = false; # No double spaces with join (mapped to qj in my config) + list = true; # Show some invisible characters + cmdheight = 0; # Hide command line when it's not getting used + + # tcqj are there by default, and "r" automatically continues comments on enter + formatoptions = "tcqjr"; + + scrolloff = 4; # Starts scrolling 4 lines from the edge of the screen + termguicolors = true; # True color support + + wrap = false; # Disable line wrap (by default) + wildmode = [ "list" "longest" ]; # Command-line completion mode + completeopt = [ "menu" "menuone" "noselect" ]; + + undofile = true; # persist undos!! + + # {{{ Line numbers + number = true; # Show line numbers + relativenumber = true; # Relative line numbers + # }}} + # {{{ Indents + expandtab = true; # Use spaces for the tab char + shiftwidth = 2; # Size of an indent + tabstop = 2; # Size of tab character + shiftround = true; # When using < or >, rounds to closest multiple of shiftwidth + smartindent = true; # Insert indents automatically + # }}} + # {{{ Casing + ignorecase = true; # Ignore case + smartcase = true; # Do not ignore case with capitals + # }}} + # {{{ Splits + splitbelow = true; # Put new windows below current + splitright = true; # Put new windows right of current + # }}} + # {{{ Folding + foldmethod = "marker"; # use {{{ }}} for folding + foldcolumn = "1"; # show column with folds on the left + # }}} + }; + + # Disable pseudo-transparency; + autocmds = { + event = "FileType"; + group = " WinblendSettings"; + action.vim.opt.winblend = 0; + }; + }; + # }}} + # {{{ Misc keybinds + "1:misc-keybinds" = { + # {{{ Global keybinds + keys = + let + # {{{ Keybind helpers + nmap = mapping: action: desc: + { inherit mapping action desc; }; + unmap = mapping: + { inherit mapping; action = ""; }; + dmap = mapping: action: desc: { + inherit mapping desc; + action = nlib.lua "vim.diagnostic.${action}"; + }; + # }}} + in + [ + # {{{ Free up q and Q + (nmap "" "q" "Record macro") + (nmap "" "Q" "Repeat last recorded macro") + (unmap "q") + (unmap "Q") + # }}} + # {{{ Chords + # Different chords get remapped to f-keys by my slambda config. + # See [my slambda config](../../../hosts/nixos/common/optional/services/slambda.nix) for details. + # + # Exit insert mode using *jk* + { + mode = "iv"; + mapping = ""; + action = ""; + desc = "Exit insert mode"; + } + # Use global clipboard using *cp* + { + mode = "nv"; + mapping = ""; + action = ''"+''; + desc = "Use global clipboard"; + } + # Save using *ji* + (nmap "" "silent write" "Save current file") + # }}} + # {{{ Newline without comments + { + mode = "i"; + mapping = ""; + action = nlib.thunk /* lua */ '' + vim.paste({ "", "" }, -1) + ''; + desc = "Insert newline without continuing the current comment"; + } + { + mode = "i"; + mapping = ""; + # This is a bit scuffed and might not work for all languages + action = "norm O"; + desc = "Insert newline above without continuing the current comment"; + } + # }}} + # {{{ Diagnostics + (dmap "[d" "goto_prev" "Goto previous [d]iagnostic") + (dmap "]d" "goto_next" "Goto next [d]iagnostic") + (dmap "J" "open_float" "Open current diagnostic") + (dmap "D" "setloclist" "[D]iagnostic loclist") + { + mapping = "qj"; + action = "J"; + desc = "join lines"; + } + # }}} + # {{{ Other misc keybinds + (nmap "a" "" "[A]lternate file") + (unmap "") + (nmap "Q" ":wqa" "Save all files and [q]uit") + (nmap "rw" ":%s//" "[R]eplace [w]ord in file") + (nmap "sw" + (nlib.lua ''require("my.helpers.wrapMovement").toggle'') + "toggle word [w]rap") + # }}} + ]; + # }}} + # {{{ Exit certain buffers with qq + autocmds = { + event = "FileType"; + pattern = [ "help" ]; + group = "BasicBufferQuitting"; + action.keys = { + mapping = "qq"; + action = "close"; + desc = "[q]uit current buffer"; + }; + }; + # }}} + }; + # }}} + # {{{ Manage cmdheight + "2:manage-cmdheight".autocmds = [ + { + event = "CmdlineEnter"; + group = "SetCmdheightCmdlineEnter"; + action.vim.opt.cmdheight = 1; + } + { + event = "CmdlineLeave"; + group = "SetCmdheightCmdlineLeave"; + action.vim.opt.cmdheight = 0; + } + ]; + # }}} + }; + # }}} + # {{{ Plugins + lazy = { + # {{{ libraries + # {{{ plenary + plenary = { + package = "nvim-lua/plenary.nvim"; + # Autoload when running tests + cmd = [ "PlenaryBustedDirectory" "PlenaryBustedFile" ]; + }; + # }}} + # {{{ nui + nui.package = "MunifTanjim/nui.nvim"; + # }}} + # {{{ web-devicons + web-devicons.package = "nvim-tree/nvim-web-devicons"; + # }}} + # {{{ Scrap + scrap = { + package = "mateiadrielrafael/scrap.nvim"; + + event = "InsertEnter"; + config.setup."my.abbreviations" = true; + }; + # }}} + # }}} + # {{{ ui + # {{{ nvim-tree + nvim-tree = { + package = "kyazdani42/nvim-tree.lua"; + + cond = nlib.blacklist [ "vscode" "firenvim" ]; + config = true; + + keys.mapping = ""; + keys.desc = "Toggle [n]vim-tree"; + keys.action = "NvimTreeToggle"; + }; + # }}} + # {{{ mini.statusline + mini-statusline = { + package = "echasnovski/mini.statusline"; + name = "mini.statusline"; + dependencies.lua = [ self.lazy.web-devicons.package ]; + + cond = nlib.blacklist [ "vscode" "firenvim" ]; + lazy = false; + + opts.content.inactive = nlib.thunk /* lua */ '' + require("mini.statusline").combine_groups({ + { hl = "MiniStatuslineFilename", strings = { vim.fn.expand("%:t") } }, + }) + ''; + + opts.content.active = nlib.thunk /* lua */ '' + local st = require("mini.statusline"); + local mode, mode_hl = st.section_mode({ trunc_width = 120 }) + local git = st.section_git({ trunc_width = 75 }) + local diagnostics = st.section_diagnostics({ trunc_width = 75 }) + + return st.combine_groups({ + { hl = mode_hl, strings = { mode } }, + { hl = "MiniStatuslineDevinfo", strings = { git } }, + { hl = "MiniStatuslineFilename", strings = { vim.fn.expand("%:t") } }, + "%=", -- End left alignment + { hl = "MiniStatuslineFilename", strings = { diagnostics } }, + { hl = "MiniStatuslineDevinfo", strings = { vim.bo.filetype } }, + }) + ''; + }; + # }}} + # {{{ mini.files + mini-files = { + package = "echasnovski/mini.files"; + name = "mini.files"; + dependencies.lua = [ self.lazy.web-devicons.package ]; + + cond = nlib.blacklist [ "vscode" "firenvim" ]; + keys = { + mapping = ""; + desc = "[S]earch [F]iles"; + action = nlib.thunk /* lua */ '' + local files = require("mini.files") + if not files.close() then + files.open(vim.api.nvim_buf_get_name(0)) + files.reveal_cwd() + end + ''; + }; + + opts.windows.preview = false; + opts.mappings.go_in_plus = "l"; + }; + # }}} + # {{{ winbar + winbar = { + package = "fgheng/winbar.nvim"; + + cond = nlib.blacklist [ "vscode" "firenvim" ]; + event = "BufReadPost"; + + opts.enabled = true; + # TODO: blacklist harpoon, NeogitStatus + }; + # }}} + # {{{ harpoon + harpoon = { + package = "ThePrimeagen/harpoon"; + keys = + let goto = key: index: { + desc = "Goto harpoon file ${toString index}"; + mapping = "${key}"; + action = nlib.thunk + /* lua */ ''require("harpoon.ui").nav_file(${toString index})''; + }; + in + [ + { + desc = "Add file to [h]arpoon"; + mapping = "H"; + action = nlib.thunk + /* lua */ ''require("harpoon.mark").add_file()''; + } + { + desc = "Toggle harpoon quickmenu"; + mapping = ""; + action = nlib.thunk + /* lua */ ''require("harpoon.ui").toggle_quick_menu()''; + } + (goto "q" 1) + (goto "w" 2) + (goto "e" 3) + (goto "r" 4) + (goto "a" 5) + (goto "s" 6) + (goto "d" 7) + (goto "f" 8) + (goto "z" 9) + ]; + }; + # }}} + # {{{ neogit + neogit = { + package = "TimUntersberger/neogit"; + dependencies.lua = [ self.lazy.plenary.package ]; + + cond = nlib.blacklist [ "vscode" "firenvim" ]; + cmd = "Neogit"; # We sometimes spawn this directly from fish using a keybind + keys = { + mapping = ""; + action = "Neogit"; + desc = "Open neo[g]it"; + }; + + opts = true; # Here so the tempest runtime will call .setup + config.autocmds = { + event = "FileType"; + pattern = "NeogitStatus"; + group = "NeogitStatusDisableFolds"; + action.vim.opt.foldenable = false; + }; + }; + # }}} + # {{{ telescope + telescope = { + package = "nvim-telescope/telescope.nvim"; + version = "0.1.x"; + cond = nlib.blacklist "vscode"; + + # {{{ Dependencies + dependencies = { + nix = [ pkgs.ripgrep ]; + lua = [ + self.lazy.plenary.package + { + # We want a prebuilt version of this plugin + dir = pkgs.vimPlugins.telescope-fzf-native-nvim; + name = "telescope-fzf-native"; + } + ]; + }; + # }}} + # {{{ Keymaps + keys = + let + keymap = mapping: action: desc: { + inherit mapping desc; + action = "Telescope ${action} theme=ivy"; + }; + + findFilesByExtension = mapping: extension: tag: + keymap + "f${mapping}" + "find_files find_command=rg,--files,--glob=**/*.${extension}" + "Find ${tag} files"; + in + [ + (keymap "" "find_files" "File finder [p]alette") + (keymap "d" "diagnostics" "[D]iagnostics") + (keymap "" "live_grep" "[F]ind in project") + (keymap "t" "builtin" "[T]elescope pickers") + # {{{ Files by extension + (findFilesByExtension "tx" "tex" "[t]ex") + (findFilesByExtension "ts" "ts" "[t]ypescript") + (findFilesByExtension "ty" "typ" "[t]ypst") + (findFilesByExtension "l" "lua" "[l]ua") + (findFilesByExtension "n" "nix" "[n]ua") + (findFilesByExtension "p" "purs" "[p]urescript") + (findFilesByExtension "h" "hs" "[h]askell") + (findFilesByExtension "e" "elm" "[e]lm") + (findFilesByExtension "r" "rs" "[r]ust") + # }}} + ]; + # }}} + # {{{ Disable folds in telescope windows + config.autocmds = { + event = "FileType"; + pattern = "TelescopeResults"; + group = "TelescopeResultsDisableFolds"; + action.vim.opt.foldenable = false; + }; + # }}} + # {{{ Load fzf extension + config.callback = nlib.thunk /* lua */ '' + require("telescope").load_extension("fzf") + ''; + # }}} + # {{{ Options + opts.defaults.mappings.i."" = "which_key"; + opts.pickers.find_files.hidden = true; + opts.extensions.fzf = { + fuzzy = true; + override_generic_sorter = true; + override_file_sorter = true; + }; + # }}} + }; + # }}} + # }}} + # {{{ visual + # The line between `ui` and `visual` is a bit rought. I currenlty mostly judge + # it by vibe. + # {{{ indent-blankline + indent-blankline = { + package = "lukas-reineke/indent-blankline.nvim"; + main = "ibl"; + config = true; + + cond = nlib.blacklist "vscode"; + event = "BufReadPost"; + }; + # }}} + # {{{ live-command + # Live command preview for commands like :norm + live-command = { + package = "smjonas/live-command.nvim"; + version = "remote"; # https://github.com/smjonas/live-command.nvim/pull/29 + main = "live-command"; + + event = "CmdlineEnter"; + opts.commands.Norm.cmd = "norm"; + opts.commands.G.cmd = "g"; + + keys = { + mode = "v"; + mapping = "N"; + action = ":Norm "; + desc = "Map lines in [n]ormal mode"; + }; + }; + # }}} + # {{{ fidget + fidget = { + package = "j-hui/fidget.nvim"; + tag = "legacy"; + + cond = nlib.blacklist "vscode"; + event = "BufReadPre"; + config = true; + }; + # }}} + # {{{ treesitter + treesitter = { + # REASON: more grammars + dir = upkgs.vimPlugins.nvim-treesitter.withAllGrammars; + dependencies.lua = [ "nvim-treesitter/nvim-treesitter-textobjects" ]; + dependencies.nix = [ pkgs.tree-sitter ]; + + cond = nlib.blacklist "vscode"; + event = "BufReadPost"; + + #{{{ Highlighting + opts.highlight = { + enable = true; + disable = [ "kotlin" ]; # This one seemed a bit broken + additional_vim_regex_highlighting = false; + }; + #}}} + # {{{ Textobjects + opts.textobjects = { + #{{{ Select + select = { + enable = true; + lookahead = true; + keymaps = { + # You can use the capture groups defined in textobjects.scm + af = "@function.outer"; + "if" = "@function.inner"; + ac = "@class.outer"; + ic = "@class.inner"; + }; + }; + #}}} + #{{{ Move + move = { + enable = true; + set_jumps = true; # whether to set jumps in the jumplist + goto_next_start = { + "]f" = "@function.outer"; + "]t" = "@class.outer"; + }; + goto_next_end = { + "]F" = "@function.outer"; + "]T" = "@class.outer"; + }; + goto_previous_start = { + "[f" = "@function.outer"; + "[t" = "@class.outer"; + }; + goto_previous_end = { + "[F" = "@function.outer"; + "[T" = "@class.outer"; + }; + }; + #}}} + }; + # }}} + opts.indent.enable = true; + }; + # }}} + # {{{ treesitter context + # Show context at the of closing delimiters + treesitter-virtual-context = { + package = "haringsrob/nvim_context_vt"; + dependencies.lua = [ "treesitter" ]; + + cond = nlib.blacklist "vscode"; + event = "BufReadPost"; + }; + + # show context at top of file + treesitter-top-context = { + package = "nvim-treesitter/nvim-treesitter-context"; + dependencies.lua = [ "treesitter" ]; + + cond = nlib.blacklist "vscode"; + event = "BufReadPost"; + opts.enable = true; + }; + # }}} + # }}} + # {{{ editing {{{ text navigation + # {{{ flash + flash = { + package = "folke/flash.nvim"; + + cond = nlib.blacklist "vscode"; + keys = + let keybind = mode: mapping: action: desc: { + inherit mapping desc mode; + action = nlib.thunk /* lua */ ''require("flash").${action}()''; + }; + in + [ + (keybind "nxo" "s" "jump" "Flash") + (keybind "nxo" "S" "treesitter" "Flash Treesitter") + (keybind "o" "r" "remote" "Remote Flash") + (keybind "ox" "R" "treesitter_search" "Treesitter Search") + (keybind "c" "" "toggle" "Toggle Flash Search") + ]; + + # Disable stuff like f/t/F/T + opts.modes.char.enabled = false; + }; + # }}} + # {{{ ftft (quickscope but written in lua) + ftft = { + package = "gukz/ftFT.nvim"; + + cond = nlib.blacklist "vscode"; + keys = [ "f" "F" "t" "T" ]; + config = true; + }; + # }}} + # }}} + # {{{ clipboard-image + clipboard-image = { + package = "postfen/clipboard-image.nvim"; + + cond = nlib.blacklist "firenvim"; + cmd = "PasteImg"; + + keys = { + mapping = "p"; + action = "PasteImg"; + desc = "[P]aste image from clipboard"; + }; + + opts.default.img_name = nlib.import ./plugins/clipboard-image.lua "img_name"; + opts.tex = { + img_dir = [ "%:p:h" "img" ]; + affix = "\\includegraphics[width=\\textwidth]{%s}"; + }; + opts.typst = { + img_dir = [ "%:p:h" "img" ]; + affix = ''#image("%s", width: 100)''; + }; + }; + # }}} + # {{{ lastplace + lastplace = { + package = "ethanholz/nvim-lastplace"; + + cond = nlib.blacklist "vscode"; + event = "BufReadPre"; + + opts.lastplace_ignore_buftype = [ "quickfix" "nofile" "help" ]; + }; + # }}} + # {{{ undotree + undotree = { + package = "mbbill/undotree"; + + cond = nlib.blacklist "vscode"; + cmd = "UndotreeToggle"; + keys = { + mapping = "u"; + action = "UndoTreeToggle"; + desc = "[U]ndo tree"; + }; + }; + # }}} + # {{{ ssr (structured search & replace) + ssr = { + package = "cshuaimin/ssr.nvim"; + + cond = nlib.blacklist "vscode"; + keys = { + mode = "nx"; + mapping = "rt"; + action = nlib.thunk /* lua */ ''require("ssr").open()''; + desc = "[r]eplace [t]emplate"; + }; + + opts.keymaps.replace_all = ""; + }; + # }}} + # {{{ edit-code-block (edit injections in separate buffers) + edit-code-block = { + package = "dawsers/edit-code-block.nvim"; + dependencies.lua = [ "treesitter" ]; + main = "ecb"; + + cond = nlib.blacklist "vscode"; + config = true; + keys = { + mapping = "e"; + action = "EditCodeBlock"; + desc = "[e]dit injection"; + }; + }; + # }}} + # {{{ mini.comment + mini-comment = { + package = "echasnovski/mini.comment"; + name = "mini.comment"; + + config = true; + keys = [ + { mapping = "gc"; mode = "nxv"; } + "gcc" + ]; + }; + # }}} + # {{{ mini.surround + mini-surround = { + package = "echasnovski/mini.surround"; + name = "mini.surround"; + + keys = lib.flatten [ + # ^ doing the whole `flatten` thing to lie to my formatter + { mapping = "s"; mode = "nv"; } + [ "d" "f" "F" "h" "r" ] + ]; + + # {{{ Keymaps + opts.mappings = { + add = "s"; # Add surrounding in Normal and Visul modes + delete = "d"; # Delete surrounding + find = "f"; # Find surrounding (to the right) + find_left = "F"; # Find surrounding (to the left) + highlight = "h"; # Highlight surrounding + replace = "r"; # Replace surrounding + update_n_lines = ""; # Update `n_lines` + }; + # }}} + # {{{ Custom surroundings + opts.custom_surroundings = + let mk = balanced: input: left: right: { + input = [ + input + (if balanced + then "^.%s*().-()%s*.$" + else "^.().*().$") + ]; + output = { inherit left right; }; + }; + in + { + b = mk true "%b()" "(" ")"; + B = mk true "%b{}" "{" "}"; + r = mk true "%b[]" "[" "]"; + q = mk false "\".-\"" "\"" "\""; + a = mk false "'.-'" "'" "'"; + }; + # }}} + }; + # }}} + # {{{ mini.operators + mini-operators = { + package = "echasnovski/mini.operators"; + name = "mini.operators"; + + config = true; + keys = + let operator = key: [ + { mapping = "g${key}"; mode = "nv"; } + "g${key}${key}" + ]; + in + lib.flatten [ + (operator "=") + (operator "x") + (operator "r") + (operator "s") + ]; + }; + # }}} + # {{{ luasnip + # snippeting engine + luasnip = + let reload = /* lua */ ''require("luasnip.loaders.from_vscode").lazy_load()''; + in + { + package = "L3MON4D3/LuaSnip"; + version = "v2"; + + cond = nlib.blacklist "vscode"; + config = nlib.thunk reload; + + # {{{ Keybinds + keys = [ + { + mapping = "rs"; + action = nlib.thunk reload; + desc = "[R]eload [s]nippets"; + } + { + mode = "i"; + expr = true; + mapping = ""; + action = nlib.thunk /* lua */ '' + local luasnip = require("luasnip") + + if not luasnip.jumpable(1) then + return "" + end + + vim.schedule(function() + luasnip.jump(1) + end) + + return "" + ''; + desc = "Jump to next snippet tabstop"; + } + { + mode = "i"; + mapping = ""; + action = nlib.thunk /* lua */ '' + require("luasnip").jump(-1) + ''; + desc = "Jump to previous snippet tabstop"; + } + ]; + # }}} + }; + # }}} + # }}} + # {{{ ide + # {{{ conform + conform = { + package = "stevearc/conform.nvim"; + + cond = nlib.blacklist "vscode"; + event = "BufReadPost"; + + opts.format_on_save.lsp_fallback = true; + opts.formatters_by_ft = let prettier = [ [ "prettierd" "prettier" ] ]; in + { + lua = [ "stylua" ]; + python = [ "ruff_format" ]; + + javascript = prettier; + typescript = prettier; + javascriptreact = prettier; + typescriptreact = prettier; + html = prettier; + css = prettier; + markdown = prettier; + }; + }; + # }}} + # {{{ neoconf + neoconf = { + package = "folke/neoconf.nvim"; + + cmd = "Neoconf"; + + opts.import = { + vscode = true; # local .vscode/settings.json + coc = false; # global/local coc-settings.json + nlsp = false; # global/local nlsp-settings.nvim json settings + }; + }; + # }}} + # {{{ null-ls + null-ls = { + package = "jose-elias-alvarez/null-ls.nvim"; + dependencies.lua = [ "neovim/nvim-lspconfig" ]; + + cond = nlib.blacklist "vscode"; + event = "BufReadPre"; + + opts = nlib.thunk /* lua */ '' + local p = require("null-ls") + return { + on_attach = require("my.plugins.lspconfig").on_attach, + sources = { + p.builtins.diagnostics.ruff + } + } + ''; + }; + # }}} + # {{{ gitsigns + gitsigns = { + package = "lewis6991/gitsigns.nvim"; + + cond = nlib.blacklist [ "vscode" "firenvim" ]; + event = "BufReadPost"; + + opts.on_attach = nlib.tempest { + mkContext = nlib.lua /* lua */ + "function(bufnr) return { bufnr = bufnr } end"; + keys = + let + prefix = m: "h${m}"; + gs = "package.loaded.gitsigns"; + + # {{{ nmap helper + nmap = mapping: action: desc: { + inherit desc; + mapping = prefix "mapping"; + action = "${gs}.action"; + }; + # }}} + # {{{ exprmap helper + exprmap = mapping: action: desc: { + inherit mapping desc; + action = nlib.thunk /* lua */ '' + if vim.wo.diff then + return "${mapping}" + end + + vim.schedule(function() + ${gs}.${action}() + end) + + return "" + ''; + expr = true; + }; + # }}} + in + [ + # {{{ navigation + (exprmap "]c" "next_hunk" "Navigate to next hunk") + (exprmap "[c" "prev_hunk" "Navigate to previous hunk") + # }}} + # {{{ actions + (nmap "s" "stage_hunk" "[s]tage hunk") + (nmap "r" "reset_hunk" "[s]tage hunk") + (nmap "S" "stage_buffer" "[s]tage hunk") + (nmap "u" "undo_stage_hunk" "[s]tage hunk") + (nmap "R" "reset_buffer" "[s]tage hunk") + (nmap "p" "preview_hunk" "[s]tage hunk") + (nmap "d" "diffthis" "[s]tage hunk") + { + mapping = prefix "D"; + action = nlib.thunk '' + ${gs}.diffthis("~") + ''; + desc = "[d]iff file (?)"; + } + { + mapping = prefix "b"; + action = nlib.thunk '' + ${gs}.blame_line({ full = true }) + ''; + desc = "[b]lame line"; + } + # }}} + # {{{ Toggles + (nmap "tb" "toggle_current_line_blame" "[t]oggle line [b]laming") + (nmap "td" "toggle_deleted" "[t]oggle [d]eleted") + # }}} + # {{{ visual mappings + { + mode = "v"; + mapping = prefix "s"; + action = nlib.thunk /* lua */ '' + ${gs}.stage_hunk({ vim.fn.line("."), vim.fn.line("v") }) + ''; + desc = "stage visual hunk"; + } + { + mode = "v"; + mapping = prefix "r"; + action = nlib.thunk /* lua */ '' + ${gs}.reset_hunk({ vim.fn.line("."), vim.fn.line("v") }) + ''; + desc = "reset visual hunk"; + } + # }}} + ]; + }; + }; + # }}} + # {{{ cmp + cmp = { + package = "hrsh7th/nvim-cmp"; + dependencies.lua = [ + # {{{ Completion sources + "hrsh7th/cmp-nvim-lsp" + "hrsh7th/cmp-buffer" + "hrsh7th/cmp-emoji" + "hrsh7th/cmp-cmdline" + "hrsh7th/cmp-path" + "saadparwaiz1/cmp_luasnip" + "dmitmel/cmp-digraphs" + # }}} + "onsails/lspkind.nvim" # show icons in lsp completion menus + self.lazy.luasnip.package + ]; + + cond = nlib.blacklist "vscode"; + event = [ "InsertEnter" "CmdlineEnter" ]; + config = nlib.import ./plugins/cmp.lua "config"; + }; + # }}} + # }}} + # {{{ language support + # {{{ haskell support + haskell-tools = { + package = "mrcjkb/haskell-tools.nvim"; + dependencies.lua = [ self.lazy.plenary.package ]; + version = "^2"; + + cond = nlib.blacklist "vscode"; + ft = [ "haskell" "lhaskell" "cabal" "cabalproject" ]; + + config.vim.g.haskell_tools = { + hls = { + on_attach = nlib.lua /* lua */ ''require("my.plugins.lspconfig").on_attach''; + settings.haskell = { + formattingProvider = "fourmolu"; + + # This seems to work better with custom preludes + # See this issue https://github.com/fourmolu/fourmolu/issues/357 + plugin.fourmolu.config.external = true; + }; + }; + + # I think this wasn't showing certain docs as I expected (?) + tools.hover.enable = false; + }; + }; + # }}} + # {{{ rust support + # {{{ rust-tools + rust-tools = { + package = "simrat39/rust-tools.nvim"; + dependencies.nix = [ pkgs.rust-analyzer pkgs.rustfmt ]; + + cond = nlib.blacklist "vscode"; + ft = "rust"; + + opts.server.on_attach = nlib.customLanguageServerOnAttach { + keys = { + mapping = "lc"; + action = "RustOpenCargo"; + desc = "Open [c]argo.toml"; + }; + }; + }; + # }}} + # {{{ crates + crates = { + package = "saecki/crates.nvim"; + dependencies.lua = [ self.lazy.plenary.package ]; + + cond = nlib.blacklist "vscode"; + event = "BufReadPost Cargo.toml"; + + # {{{ Set up null_ls source + opts.null_ls = { + enabled = true; + name = "crates"; + }; + # }}} + + config.autocmds = [ + # {{{ Load cmp source on insert + { + event = "InsertEnter"; + group = "CargoCmpSource"; + pattern = "Cargo.toml"; + action = nlib.thunk /* lua */ '' + require("cmp").setup.buffer({ sources = { { name = "crates" } } }) + ''; + } + # }}} + # {{{ Load keybinds on attach + { + event = "BufReadPost"; + group = "CargoKeybinds"; + pattern = "Cargo.toml"; + # # {{{ Register which-key info + # action.callback = nlib.contextThunk /* lua */ '' + # require("which-key").register({ + # ["lc"] = { + # name = "[l]ocal [c]rates", + # bufnr = context.bufnr + # }, + # }) + # ''; + # }}} + + action.keys = _: + let + # {{{ Keymap helpers + keymap = mapping: action: desc: { + inherit mapping desc; + action = nlib.lua /* lua */ ''require("crates").${action}''; + }; + + keyroot = "lc"; + # }}} + in + # {{{ Keybinds + [ + (keymap "${keyroot}t" "toggle" "[c]rates [t]oggle") + (keymap "${keyroot}r" "reload" "[c]rates [r]efresh") + + (keymap "${keyroot}H" "open_homepage" "[c]rate [H]omephage") + (keymap "${keyroot}R" "open_repository" "[c]rate [R]epository") + (keymap "${keyroot}D" "open_documentation" "[c]rate [D]ocumentation") + (keymap "${keyroot}C" "open_crates_io" "[c]rate [C]rates.io") + + (keymap "${keyroot}v" "show_versions_popup" "[c]rate [v]ersions") + (keymap "${keyroot}f" "show_features_popup" "[c]rate [f]eatures") + (keymap "${keyroot}d" "show_dependencies_popup" "[c]rate [d]eps") + (keymap "K" "show_popup" "[c]rate popup") + ]; + # }}} + } + # }}} + ]; + }; + # }}} + # }}} + # {{{ lean support + lean = { + package = "Julian/lean.nvim"; + name = "lean"; + dependencies.lua = [ + self.lazy.plenary.package + "neovim/nvim-lspconfig" + ]; + + cond = nlib.blacklist "vscode"; + ft = "lean"; + + opts = { + abbreviations = { + builtin = true; + cmp = true; + }; + + lsp = { + on_attach = nlib.lua /* lua */ ''require("my.plugins.lspconfig").on_attach''; + capabilites = nlib.lua /* lua */ ''require("my.plugins.lspconfig").capabilities''; + }; + + lsp3 = false; # We don't want the lean 3 language server! + mappings = true; + }; + }; + # }}} + # {{{ idris support + idris = { + package = "ShinKage/idris2-nvim"; + name = "idris"; + dependencies.lua = [ + self.lazy.nui.package + "neovim/nvim-lspconfig" + ]; + + cond = nlib.blacklist "vscode"; + ft = [ "idris2" "lidris2" "ipkg" ]; + + opts = { + client.hover.use_split = true; + serve.on_attach = nlib.customLanguageServerOnAttach { + # {{{ Keymaps + keys = + let keymap = mapping: action: desc: { + inherit desc; + mapping = "i${mapping}"; + action = nlib.lua /* lua */ ''require("idris2.code_action").${action}''; + }; + in + [ + (keymap "C" "make_case" "Make [c]ase") + (keymap "L" "make_lemma" "Make [l]emma") + (keymap "c" "add_clause" "Add [c]lause") + (keymap "e" "expr_search" "[E]xpression search") + (keymap "d" "generate_def" "Generate [d]efinition") + (keymap "s" "case_split" "Case [s]plit") + (keymap "h" "refine_hole" "Refine [h]ole") + ]; + # }}} + }; + }; + }; + # }}} + # {{{ github actions + github-actions = { + package = "yasuhiroki/github-actions-yaml.vim"; + + cond = nlib.blacklist "vscode"; + ft = [ "yml" "yaml" ]; + }; + # }}} + # {{{ typst support + typst = { + package = "kaarmu/typst.vim"; + dependencies.nix = [ pkgs.typst-lsp pkgs.typst-fmt ]; + + cond = nlib.blacklist "vscode"; + ft = "typst"; + }; + # }}} + # {{{ hyprland + hyprland = { + package = "theRealCarneiro/hyprland-vim-syntax"; + + cond = nlib.blacklist "vscode"; + ft = "hypr"; + + config.autocmds = { + event = "BufRead"; + group = "DetectHyprlandConfig"; + pattern = "hyprland.conf"; + action.vim.opt.ft = "hypr"; + }; + }; + # }}} + # }}} + # {{{ external + # These plugins integrate neovim with external services + # {{{ wakatime + wakatime = { + package = "wakatime/vim-wakatime"; + dependencies.nix = [ pkgs.wakatime ]; + + cond = nlib.blacklist [ "vscode" "firenvim" ]; + event = "BufReadPost"; + }; + # }}} + # {{{ discord rich presence + discord-rich-presence = { + package = "andweeb/presence.nvim"; + main = "presence"; + + cond = nlib.blacklist [ "vscode" "firenvim" ]; + event = "BufReadPost"; + config = true; + }; + # }}} + # {{{ gitlinker + # generate permalinks for code + gitlinker = + let mapping = "yg"; + in + { + package = "ruifm/gitlinker.nvim"; + dependencies.lua = [ self.lazy.plenary.package ]; + + cond = nlib.blacklist [ "vscode" "firenvim" ]; + opts.mappings = mapping; + keys = mapping; + }; + # }}} + # {{{ paperplanes + # export to pastebin like services + paperlanes = { + package = "rktjmp/paperplanes.nvim"; + cmd = "PP"; + opts.provider = "paste.rs"; + }; + # }}} + # {{{ obsidian + obsidian = + let vault = "${config.xdg.userDirs.extraConfig.XDG_PROJECTS_DIR}/stellar-sanctum"; + in + { + package = "epwalsh/obsidian.nvim"; + dependencies.lua = [ self.lazy.plenary.package ]; + + cond = [ + (nlib.blacklist [ "vscode" "firenvim" ]) + (nlib.lua /* lua */ "vim.loop.cwd() == ${nlib.encode vault}") + ]; + event = "VeryLazy"; + + keys.mapping = ""; + keys.action = "ObsidianQuickSwitch"; + + opts = { + dir = vault; + notes_subdir = "chaos"; + daily_notes = { + folder = "daily"; + date_format = "%Y-%m-%d"; + }; + + completion = { + nvim_cmp = true; + min_chars = 2; + new_notes_location = "current_dir"; + prepend_note_id = true; + }; + + mappings = { }; + disable_frontmatter = true; + }; + }; + # }}} + # }}} + }; + # }}} + })); + # {{{ extraPackages extraPackages = with pkgs; [ # Language servers @@ -33,13 +1295,15 @@ let # Latex setup # texlive.combined.scheme-full # Latex stuff # python38Packages.pygments # required for latex syntax highlighting - ] ++ config.satellite.neovim.generated.dependencies; + ] ++ generated.dependencies; # }}} # {{{ extraRuntime - extraRuntimePaths = [ - # Experimental nix module generation - config.satellite.neovim.generated.lazySingleFile - ]; + # Experimental nix module generation + generatedConfig = (config.satellite.lib.lua.writeFile + "lua/nix" "init" + generated.lua); + + extraRuntimePaths = [ generatedConfig ]; extraRuntimeJoinedPaths = pkgs.symlinkJoin { name = "nixified-neovim-lua-modules"; @@ -103,20 +1367,17 @@ let extraArgs = "--set GIT_DISCOVERY_ACROSS_FILESYSTEM 1"; }; # }}} - - nlib = config.satellite.neovim.lib; - lazy = config.satellite.neovim.lazy; in { + satellite.lua.styluaConfig = ../../../stylua.toml; + # {{{ Basic config # We still want other modules to know that we are using neovim! satellite.toggles.neovim.enable = true; xdg.configFile.nvim.source = config.satellite.dev.path "home/features/neovim/config"; - home.sessionVariables = { - EDITOR = "nvim"; - NVIM_GENERATED_RUNTIME = extraRuntimeJoinedPaths; - }; + home.sessionVariables.EDITOR = "nvim"; + home.file.".nvim_nix_runtime".source = generatedConfig; home.packages = [ neovim @@ -171,1074 +1432,6 @@ in }; }; # }}} - # {{{ Plugins - satellite.lua.styluaConfig = ../../../stylua.toml; - satellite.neovim.runtime = { - languageServerOnAttach = "my.plugins.lspconfig"; - tempest = "my.tempest"; - }; - - # {{{ libraries - # {{{ plenary - satellite.neovim.lazy.plenary = { - package = "nvim-lua/plenary.nvim"; - # Autoload when running tests - cmd = [ "PlenaryBustedDirectory" "PlenaryBustedFile" ]; - }; - # }}} - # {{{ nui - satellite.neovim.lazy.nui.package = "MunifTanjim/nui.nvim"; - # }}} - # {{{ web-devicons - satellite.neovim.lazy.web-devicons.package = "nvim-tree/nvim-web-devicons"; - # }}} - # {{{ Scrap - satellite.neovim.lazy.scrap.package = "mateiadrielrafael/scrap.nvim"; - # }}} - # }}} - # {{{ ui - # {{{ nvim-tree - satellite.neovim.lazy.nvim-tree = { - package = "kyazdani42/nvim-tree.lua"; - - env.blacklist = [ "vscode" "firenvim" ]; - setup = true; - - keys.mapping = ""; - keys.desc = "Toggle [n]vim-tree"; - keys.action = "NvimTreeToggle"; - }; - # }}} - # {{{ mini.statusline - satellite.neovim.lazy.mini-statusline = { - package = "echasnovski/mini.statusline"; - name = "mini.statusline"; - dependencies.lua = [ lazy.web-devicons.package ]; - - env.blacklist = [ "vscode" "firenvim" ]; - lazy = false; - - opts.content.inactive = nlib.thunk /* lua */ '' - require("mini.statusline").combine_groups({ - { hl = "MiniStatuslineFilename", strings = { vim.fn.expand("%:t") } }, - }) - ''; - - opts.content.active = nlib.thunk /* lua */ '' - local st = require("mini.statusline"); - local mode, mode_hl = st.section_mode({ trunc_width = 120 }) - local git = st.section_git({ trunc_width = 75 }) - local diagnostics = st.section_diagnostics({ trunc_width = 75 }) - - return st.combine_groups({ - { hl = mode_hl, strings = { mode } }, - { hl = "MiniStatuslineDevinfo", strings = { git } }, - { hl = "MiniStatuslineFilename", strings = { vim.fn.expand("%:t") } }, - "%=", -- End left alignment - { hl = "MiniStatuslineFilename", strings = { diagnostics } }, - { hl = "MiniStatuslineDevinfo", strings = { vim.bo.filetype } }, - }) - ''; - }; - # }}} - # {{{ mini.files - satellite.neovim.lazy.mini-files = { - package = "echasnovski/mini.files"; - name = "mini.files"; - dependencies.lua = [ lazy.web-devicons.package ]; - - env.blacklist = [ "vscode" "firenvim" ]; - keys = { - mapping = ""; - desc = "[S]earch [F]iles"; - action = nlib.thunk /* lua */ '' - local files = require("mini.files") - if not files.close() then - files.open(vim.api.nvim_buf_get_name(0)) - files.reveal_cwd() - end - ''; - }; - - opts.windows.preview = false; - opts.mappings.go_in_plus = "l"; - }; - # }}} - # {{{ winbar - satellite.neovim.lazy.winbar = { - package = "fgheng/winbar.nvim"; - - env.blacklist = [ "vscode" "firenvim" ]; - event = "BufReadPost"; - - opts.enabled = true; - # TODO: blacklist harpoon, NeogitStatus - }; - # }}} - # {{{ harpoon - satellite.neovim.lazy.harpoon = { - package = "ThePrimeagen/harpoon"; - keys = - let goto = key: index: { - desc = "Goto harpoon file ${toString index}"; - mapping = "${key}"; - action = nlib.thunk - /* lua */ ''require("harpoon.ui").nav_file(${toString index})''; - }; - in - [ - { - desc = "Add file to [h]arpoon"; - mapping = "H"; - action = nlib.thunk - /* lua */ ''require("harpoon.mark").add_file()''; - } - { - desc = "Toggle harpoon quickmenu"; - mapping = ""; - action = nlib.thunk - /* lua */ ''require("harpoon.ui").toggle_quick_menu()''; - } - (goto "q" 1) - (goto "w" 2) - (goto "e" 3) - (goto "r" 4) - (goto "a" 5) - (goto "s" 6) - (goto "d" 7) - (goto "f" 8) - (goto "z" 9) - ]; - }; - # }}} - # {{{ neogit - satellite.neovim.lazy.neogit = { - package = "TimUntersberger/neogit"; - dependencies.lua = [ lazy.plenary.package ]; - - env.blacklist = [ "vscode" "firenvim" ]; - cmd = "Neogit"; # We sometimes spawn this directly from fish using a keybind - keys = { - mapping = ""; - action = "Neogit"; - desc = "Open neo[g]it"; - }; - - opts = true; # Here so the tempest runtime will call .setup - setup.autocmds = { - event = "FileType"; - pattern = "NeogitStatus"; - group = "NeogitStatusDisableFolds"; - action.vim.opt.foldenable = false; - }; - }; - # }}} - # {{{ telescope - satellite.neovim.lazy.telescope = { - package = "nvim-telescope/telescope.nvim"; - version = "0.1.x"; - env.blacklist = "vscode"; - - # {{{ Dependencies - dependencies = { - nix = [ pkgs.ripgrep ]; - lua = [ - lazy.plenary.package - { - # We want a prebuilt version of this plugin - dir = pkgs.vimPlugins.telescope-fzf-native-nvim; - name = "telescope-fzf-native"; - } - ]; - }; - # }}} - # {{{ Keymaps - keys = - let - keymap = mapping: action: desc: { - inherit mapping desc; - action = "Telescope ${action} theme=ivy"; - }; - - findFilesByExtension = mapping: extension: tag: - keymap - "f${mapping}" - "find_files find_command=rg,--files,--glob=**/*.${extension}" - "Find ${tag} files"; - in - [ - (keymap "" "find_files" "File finder [p]alette") - (keymap "d" "diagnostics" "[D]iagnostics") - (keymap "" "live_grep" "[F]ind in project") - (keymap "t" "builtin" "[T]elescope pickers") - # {{{ Files by extension - (findFilesByExtension "tx" "tex" "[t]ex") - (findFilesByExtension "ts" "ts" "[t]ypescript") - (findFilesByExtension "ty" "typ" "[t]ypst") - (findFilesByExtension "l" "lua" "[l]ua") - (findFilesByExtension "n" "nix" "[n]ua") - (findFilesByExtension "p" "purs" "[p]urescript") - (findFilesByExtension "h" "hs" "[h]askell") - (findFilesByExtension "e" "elm" "[e]lm") - (findFilesByExtension "r" "rs" "[r]ust") - # }}} - ]; - # }}} - # {{{ Disable folds in telescope windows - setup.autocmds = { - event = "FileType"; - pattern = "TelescopeResults"; - group = "TelescopeResultsDisableFolds"; - action.vim.opt.foldenable = false; - }; - # }}} - # {{{ Load fzf extension - setup.callback = nlib.thunkString /* lua */ '' - require("telescope").load_extension("fzf") - ''; - # }}} - # {{{ Options - opts.defaults.mappings.i."" = "which_key"; - opts.pickers.find_files.hidden = true; - opts.extensions.fzf = { - fuzzy = true; - override_generic_sorter = true; - override_file_sorter = true; - }; - # }}} - }; - # }}} - # }}} - # {{{ visual - # The line between `ui` and `visual` is a bit rought. I currenlty mostly judge - # it by vibe. - # {{{ indent-blankline - satellite.neovim.lazy.indent-blankline = { - package = "lukas-reineke/indent-blankline.nvim"; - main = "ibl"; - setup = true; - - env.blacklist = "vscode"; - event = "BufReadPost"; - }; - # }}} - # {{{ live-command - # Live command preview for commands like :norm - satellite.neovim.lazy.live-command = { - package = "smjonas/live-command.nvim"; - version = "remote"; # https://github.com/smjonas/live-command.nvim/pull/29 - main = "live-command"; - - event = "CmdlineEnter"; - opts.commands.Norm.cmd = "norm"; - opts.commands.G.cmd = "g"; - - keys = { - mode = "v"; - mapping = "N"; - action = ":Norm "; - desc = "Map lines in [n]ormal mode"; - }; - }; - # }}} - # {{{ fidget - satellite.neovim.lazy.fidget = { - package = "j-hui/fidget.nvim"; - tag = "legacy"; - - env.blacklist = "vscode"; - event = "BufReadPre"; - setup = true; - }; - # }}} - # {{{ treesitter - satellite.neovim.lazy.treesitter = { - # REASON: more grammars - dir = upkgs.vimPlugins.nvim-treesitter.withAllGrammars; - dependencies.lua = [ "nvim-treesitter/nvim-treesitter-textobjects" ]; - dependencies.nix = [ pkgs.tree-sitter ]; - - env.blacklist = "vscode"; - event = "BufReadPost"; - - #{{{ Highlighting - opts.highlight = { - enable = true; - disable = [ "kotlin" ]; # This one seemed a bit broken - additional_vim_regex_highlighting = false; - }; - #}}} - # {{{ Textobjects - opts.textobjects = { - #{{{ Select - select = { - enable = true; - lookahead = true; - keymaps = { - # You can use the capture groups defined in textobjects.scm - af = "@function.outer"; - "if" = "@function.inner"; - ac = "@class.outer"; - ic = "@class.inner"; - }; - }; - #}}} - #{{{ Move - move = { - enable = true; - set_jumps = true; # whether to set jumps in the jumplist - goto_next_start = { - "]f" = "@function.outer"; - "]t" = "@class.outer"; - }; - goto_next_end = { - "]F" = "@function.outer"; - "]T" = "@class.outer"; - }; - goto_previous_start = { - "[f" = "@function.outer"; - "[t" = "@class.outer"; - }; - goto_previous_end = { - "[F" = "@function.outer"; - "[T" = "@class.outer"; - }; - }; - #}}} - }; - # }}} - opts.indent.enable = true; - }; - # }}} - # {{{ treesitter context - # Show context at the of closing delimiters - satellite.neovim.lazy.treesitter-virtual-context = { - package = "haringsrob/nvim_context_vt"; - dependencies.lua = [ lazy.treesitter.name ]; - - env.blacklist = "vscode"; - event = "BufReadPost"; - }; - - # show context at top of file - satellite.neovim.lazy.treesitter-top-context = { - package = "nvim-treesitter/nvim-treesitter-context"; - dependencies.lua = [ lazy.treesitter.name ]; - - env.blacklist = "vscode"; - event = "BufReadPost"; - opts.enable = true; - }; - # }}} - # }}} - # {{{ editing {{{ text navigation - # {{{ flash - satellite.neovim.lazy.flash = { - package = "folke/flash.nvim"; - - env.blacklist = "vscode"; - keys = - let keybind = mode: mapping: action: desc: { - inherit mapping desc mode; - action = nlib.thunk /* lua */ ''require("flash").${action}()''; - }; - in - [ - (keybind "nxo" "s" "jump" "Flash") - (keybind "nxo" "S" "treesitter" "Flash Treesitter") - (keybind "o" "r" "remote" "Remote Flash") - (keybind "ox" "R" "treesitter_search" "Treesitter Search") - (keybind "c" "" "toggle" "Toggle Flash Search") - ]; - - # Disable stuff like f/t/F/T - opts.modes.char.enabled = false; - }; - # }}} - # {{{ ftft (quickscope but written in lua) - satellite.neovim.lazy.ftft = { - package = "gukz/ftFT.nvim"; - - env.blacklist = "vscode"; - keys = [ "f" "F" "t" "T" ]; - setup = true; - }; - # }}} - # }}} - # {{{ clipboard-image - satellite.neovim.lazy.clipboard-image = { - package = "postfen/clipboard-image.nvim"; - - env.blacklist = "firenvim"; - cmd = "PasteImg"; - - keys = { - mapping = "p"; - action = "PasteImg"; - desc = "[P]aste image from clipboard"; - }; - - opts.default.img_name = nlib.import ./plugins/clipboard-image.lua "img_name"; - opts.tex = { - img_dir = [ "%:p:h" "img" ]; - affix = "\\includegraphics[width=\\textwidth]{%s}"; - }; - opts.typst = { - img_dir = [ "%:p:h" "img" ]; - affix = ''#image("%s", width: 100)''; - }; - }; - # }}} - # {{{ lastplace - satellite.neovim.lazy.lastplace = { - package = "ethanholz/nvim-lastplace"; - - env.blacklist = "vscode"; - event = "BufReadPre"; - - opts.lastplace_ignore_buftype = [ "quickfix" "nofile" "help" ]; - }; - # }}} - # {{{ undotree - satellite.neovim.lazy.undotree = { - package = "mbbill/undotree"; - - env.blacklist = "vscode"; - cmd = "UndotreeToggle"; - keys = { - mapping = "u"; - action = "UndoTreeToggle"; - desc = "[U]ndo tree"; - }; - }; - # }}} - # {{{ ssr (structured search & replace) - satellite.neovim.lazy.ssr = { - package = "cshuaimin/ssr.nvim"; - - env.blacklist = "vscode"; - keys = { - mode = "nx"; - mapping = "rt"; - action = nlib.thunk /* lua */ ''require("ssr").open()''; - desc = "[r]eplace [t]emplate"; - }; - - opts.keymaps.replace_all = ""; - }; - # }}} - # {{{ edit-code-block (edit injections in separate buffers) - satellite.neovim.lazy.edit-code-block = { - package = "dawsers/edit-code-block.nvim"; - dependencies.lua = [ lazy.treesitter.name ]; - main = "ecb"; - - env.blacklist = "vscode"; - setup = true; - keys = { - mapping = "e"; - action = "EditCodeBlock"; - desc = "[e]dit injection"; - }; - }; - # }}} - # {{{ mini.comment - satellite.neovim.lazy.mini-comment = { - package = "echasnovski/mini.comment"; - name = "mini.comment"; - - setup = true; - keys = [ - { mapping = "gc"; mode = "nxv"; } - "gcc" - ]; - }; - # }}} - # {{{ mini.surround - satellite.neovim.lazy.mini-surround = { - package = "echasnovski/mini.surround"; - name = "mini.surround"; - - keys = lib.flatten [ - # ^ doing the whole `flatten` thing to lie to my formatter - { mapping = "s"; mode = "nv"; } - [ "d" "f" "F" "h" "r" ] - ]; - - # {{{ Keymaps - opts.mappings = { - add = "s"; # Add surrounding in Normal and Visul modes - delete = "d"; # Delete surrounding - find = "f"; # Find surrounding (to the right) - find_left = "F"; # Find surrounding (to the left) - highlight = "h"; # Highlight surrounding - replace = "r"; # Replace surrounding - update_n_lines = ""; # Update `n_lines` - }; - # }}} - # {{{ Custom surroundings - opts.custom_surroundings = - let mk = balanced: input: left: right: { - input = [ - input - (if balanced - then "^.%s*().-()%s*.$" - else "^.().*().$") - ]; - output = { inherit left right; }; - }; - in - { - b = mk true "%b()" "(" ")"; - B = mk true "%b{}" "{" "}"; - r = mk true "%b[]" "[" "]"; - q = mk false "\".-\"" "\"" "\""; - a = mk false "'.-'" "'" "'"; - }; - # }}} - }; - # }}} - # {{{ mini.operators - satellite.neovim.lazy.mini-operators = { - package = "echasnovski/mini.operators"; - name = "mini.operators"; - - setup = true; - keys = - let operator = key: [ - { mapping = "g${key}"; mode = "nv"; } - "g${key}${key}" - ]; - in - lib.flatten [ - (operator "=") - (operator "x") - (operator "r") - (operator "s") - ]; - }; - # }}} - # {{{ luasnip - # snippeting engine - satellite.neovim.lazy.luasnip = - let reload = /* lua */ ''require("luasnip.loaders.from_vscode").lazy_load()''; - in - { - package = "L3MON4D3/LuaSnip"; - version = "v2"; - - env.blacklist = "vscode"; - setup.callback = nlib.thunk reload; - - # {{{ Keybinds - keys = [ - { - mapping = "rs"; - action = nlib.thunk reload; - desc = "[R]eload [s]nippets"; - } - { - mode = "i"; - expr = true; - mapping = ""; - action = nlib.thunk /* lua */ '' - local luasnip = require("luasnip") - - if not luasnip.jumpable(1) then - return "" - end - - vim.schedule(function() - luasnip.jump(1) - end) - - return "" - ''; - desc = "Jump to next snippet tabstop"; - } - { - mode = "i"; - mapping = ""; - action = nlib.thunk /* lua */ '' - require("luasnip").jump(-1) - ''; - desc = "Jump to previous snippet tabstop"; - } - ]; - # }}} - }; - # }}} - # }}} - # {{{ ide - # {{{ conform - satellite.neovim.lazy.conform = { - package = "stevearc/conform.nvim"; - - env.blacklist = "vscode"; - event = "BufReadPost"; - - opts.format_on_save.lsp_fallback = true; - opts.formatters_by_ft = let prettier = [ [ "prettierd" "prettier" ] ]; in - { - lua = [ "stylua" ]; - python = [ "ruff_format" ]; - - javascript = prettier; - typescript = prettier; - javascriptreact = prettier; - typescriptreact = prettier; - html = prettier; - css = prettier; - markdown = prettier; - }; - }; - # }}} - # {{{ neoconf - satellite.neovim.lazy.neoconf = { - package = "folke/neoconf.nvim"; - - cmd = "Neoconf"; - - opts.import = { - vscode = true; # local .vscode/settings.json - coc = false; # global/local coc-settings.json - nlsp = false; # global/local nlsp-settings.nvim json settings - }; - }; - # }}} - # {{{ null-ls - satellite.neovim.lazy.null-ls = { - package = "jose-elias-alvarez/null-ls.nvim"; - dependencies.lua = [ "neovim/nvim-lspconfig" ]; - - env.blacklist = "vscode"; - event = "BufReadPre"; - - opts = nlib.thunk /* lua */ '' - local p = require("null-ls") - return { - on_attach = require("my.plugins.lspconfig").on_attach, - sources = { - p.builtins.diagnostics.ruff - } - } - ''; - }; - # }}} - # {{{ gitsigns - satellite.neovim.lazy.gitsigns = { - package = "lewis6991/gitsigns.nvim"; - - env.blacklist = [ "vscode" "firenvim" ]; - event = "BufReadPost"; - - opts.on_attach = nlib.tempest { - mkContext = /* lua */ ''function(bufnr) return { bufnr = bufnr } end''; - keys = - let - prefix = m: "h${m}"; - gs = "package.loaded.gitsigns"; - - # {{{ nmap helper - nmap = mapping: action: desc: { - inherit desc; - mapping = prefix "mapping"; - action = "${gs}.action"; - }; - # }}} - # {{{ exprmap helper - exprmap = mapping: action: desc: { - inherit mapping desc; - action = nlib.thunk /* lua */ '' - if vim.wo.diff then - return "${mapping}" - end - - vim.schedule(function() - ${gs}.${action}() - end) - - return "" - ''; - expr = true; - }; - # }}} - in - [ - # {{{ navigation - (exprmap "]c" "next_hunk" "Navigate to next hunk") - (exprmap "[c" "prev_hunk" "Navigate to previous hunk") - # }}} - # {{{ actions - (nmap "s" "stage_hunk" "[s]tage hunk") - (nmap "r" "reset_hunk" "[s]tage hunk") - (nmap "S" "stage_buffer" "[s]tage hunk") - (nmap "u" "undo_stage_hunk" "[s]tage hunk") - (nmap "R" "reset_buffer" "[s]tage hunk") - (nmap "p" "preview_hunk" "[s]tage hunk") - (nmap "d" "diffthis" "[s]tage hunk") - { - mapping = prefix "D"; - action = nlib.thunk '' - ${gs}.diffthis("~") - ''; - desc = "[d]iff file (?)"; - } - { - mapping = prefix "b"; - action = nlib.thunk '' - ${gs}.blame_line({ full = true }) - ''; - desc = "[b]lame line"; - } - # }}} - # {{{ Toggles - (nmap "tb" "toggle_current_line_blame" "[t]oggle line [b]laming") - (nmap "td" "toggle_deleted" "[t]oggle [d]eleted") - # }}} - # {{{ visual mappings - { - mode = "v"; - mapping = prefix "s"; - action = nlib.thunk /* lua */ '' - ${gs}.stage_hunk({ vim.fn.line("."), vim.fn.line("v") }) - ''; - desc = "stage visual hunk"; - } - { - mode = "v"; - mapping = prefix "r"; - action = nlib.thunk /* lua */ '' - ${gs}.reset_hunk({ vim.fn.line("."), vim.fn.line("v") }) - ''; - desc = "reset visual hunk"; - } - # }}} - ]; - }; - }; - # }}} - # {{{ cmp - satellite.neovim.lazy.cmp = { - package = "hrsh7th/nvim-cmp"; - dependencies.lua = [ - # {{{ Completion sources - "hrsh7th/cmp-nvim-lsp" - "hrsh7th/cmp-buffer" - "hrsh7th/cmp-emoji" - "hrsh7th/cmp-cmdline" - "hrsh7th/cmp-path" - "saadparwaiz1/cmp_luasnip" - "dmitmel/cmp-digraphs" - # }}} - "onsails/lspkind.nvim" # show icons in lsp completion menus - lazy.luasnip.package - ]; - - env.blacklist = "vscode"; - event = [ "InsertEnter" "CmdlineEnter" ]; - setup = (nlib.import ./plugins/cmp.lua "config").value; - }; - # }}} - # }}} - # {{{ language support - # {{{ haskell support - satellite.neovim.lazy.haskell-tools = { - package = "mrcjkb/haskell-tools.nvim"; - dependencies.lua = [ lazy.plenary.package ]; - version = "^2"; - - env.blacklist = "vscode"; - ft = [ "haskell" "lhaskell" "cabal" "cabalproject" ]; - - setup.vim.g.haskell_tools = { - hls = { - on_attach = nlib.lua /* lua */ ''require("my.plugins.lspconfig").on_attach''; - settings.haskell = { - formattingProvider = "fourmolu"; - - # This seems to work better with custom preludes - # See this issue https://github.com/fourmolu/fourmolu/issues/357 - plugin.fourmolu.config.external = true; - }; - }; - - # I think this wasn't showing certain docs as I expected (?) - tools.hover.enable = false; - }; - }; - # }}} - # {{{ rust support - # {{{ rust-tools - satellite.neovim.lazy.rust-tools = { - package = "simrat39/rust-tools.nvim"; - dependencies.nix = [ pkgs.rust-analyzer pkgs.rustfmt ]; - - env.blacklist = "vscode"; - ft = "rust"; - - opts.server.on_attach = nlib.languageServerOnAttach { - keys = { - mapping = "lc"; - action = "RustOpenCargo"; - desc = "Open [c]argo.toml"; - }; - }; - }; - # }}} - # {{{ crates - satellite.neovim.lazy.crates = { - package = "saecki/crates.nvim"; - dependencies.lua = [ lazy.plenary.package ]; - - env.blacklist = "vscode"; - event = "BufReadPost Cargo.toml"; - - # {{{ Set up null_ls source - opts.null_ls = { - enabled = true; - name = "crates"; - }; - # }}} - - setup.autocmds = [ - # {{{ Load cmp source on insert - { - event = "InsertEnter"; - group = "CargoCmpSource"; - pattern = "Cargo.toml"; - action = nlib.thunkString /* lua */ '' - require("cmp").setup.buffer({ sources = { { name = "crates" } } }) - ''; - } - # }}} - # {{{ Load keybinds on attach - { - event = "BufReadPost"; - group = "CargoKeybinds"; - pattern = "Cargo.toml"; - # # {{{ Register which-key info - # action.callback = nlib.contextThunk /* lua */ '' - # require("which-key").register({ - # ["lc"] = { - # name = "[l]ocal [c]rates", - # bufnr = context.bufnr - # }, - # }) - # ''; - # }}} - - action.keys = - let - # {{{ Keymap helpers - keymap = mapping: action: desc: { - inherit mapping desc; - action = nlib.lua /* lua */ ''require("crates").${action}''; - }; - - keyroot = "lc"; - # }}} - in - # {{{ Keybinds - [ - (keymap "${keyroot}t" "toggle" "[c]rates [t]oggle") - (keymap "${keyroot}r" "reload" "[c]rates [r]efresh") - - (keymap "${keyroot}H" "open_homepage" "[c]rate [H]omephage") - (keymap "${keyroot}R" "open_repository" "[c]rate [R]epository") - (keymap "${keyroot}D" "open_documentation" "[c]rate [D]ocumentation") - (keymap "${keyroot}C" "open_crates_io" "[c]rate [C]rates.io") - - (keymap "${keyroot}v" "show_versions_popup" "[c]rate [v]ersions") - (keymap "${keyroot}f" "show_features_popup" "[c]rate [f]eatures") - (keymap "${keyroot}d" "show_dependencies_popup" "[c]rate [d]eps") - (keymap "K" "show_popup" "[c]rate popup") - ]; - # }}} - } - # }}} - ]; - }; - # }}} - # }}} - # {{{ lean support - satellite.neovim.lazy.lean = { - package = "Julian/lean.nvim"; - name = "lean"; - dependencies.lua = [ - lazy.plenary.package - "neovim/nvim-lspconfig" - ]; - - env.blacklist = "vscode"; - ft = "lean"; - - opts = { - abbreviations = { - builtin = true; - cmp = true; - }; - - lsp = { - on_attach = nlib.lua /* lua */ ''require("my.plugins.lspconfig").on_attach''; - capabilites = nlib.lua /* lua */ ''require("my.plugins.lspconfig").capabilities''; - }; - - lsp3 = false; # We don't want the lean 3 language server! - mappings = true; - }; - }; - # }}} - # {{{ idris support - satellite.neovim.lazy.idris = { - package = "ShinKage/idris2-nvim"; - name = "idris"; - dependencies.lua = [ - lazy.nui.package - "neovim/nvim-lspconfig" - ]; - - env.blacklist = "vscode"; - ft = [ "idris2" "lidris2" "ipkg" ]; - - opts = { - client.hover.use_split = true; - serve.on_attach = nlib.languageServerOnAttach { - # {{{ Keymaps - keys = - let keymap = mapping: action: desc: { - inherit desc; - mapping = "i${mapping}"; - action = nlib.lua /* lua */ ''require("idris2.code_action").${action}''; - }; - in - [ - (keymap "C" "make_case" "Make [c]ase") - (keymap "L" "make_lemma" "Make [l]emma") - (keymap "c" "add_clause" "Add [c]lause") - (keymap "e" "expr_search" "[E]xpression search") - (keymap "d" "generate_def" "Generate [d]efinition") - (keymap "s" "case_split" "Case [s]plit") - (keymap "h" "refine_hole" "Refine [h]ole") - ]; - # }}} - }; - }; - }; - # }}} - # {{{ github actions - satellite.neovim.lazy.github-actions = { - package = "yasuhiroki/github-actions-yaml.vim"; - - env.blacklist = "vscode"; - ft = [ "yml" "yaml" ]; - }; - # }}} - # {{{ typst support - satellite.neovim.lazy.typst = { - package = "kaarmu/typst.vim"; - dependencies.nix = [ pkgs.typst-lsp pkgs.typst-fmt ]; - - env.blacklist = "vscode"; - ft = "typst"; - }; - # }}} - # {{{ hyprland - satellite.neovim.lazy.hyprland = { - package = "theRealCarneiro/hyprland-vim-syntax"; - - env.blacklist = "vscode"; - ft = "hypr"; - - setup.autocmds = { - event = "BufRead"; - group = "DetectHyprlandConfig"; - pattern = "hyprland.conf"; - action.vim.opt.ft = "hypr"; - }; - }; - # }}} - # }}} - # {{{ external - # These plugins integrate neovim with external services - # {{{ wakatime - satellite.neovim.lazy.wakatime = { - package = "wakatime/vim-wakatime"; - dependencies.nix = [ pkgs.wakatime ]; - - env.blacklist = [ "vscode" "firenvim" ]; - event = "BufReadPost"; - }; - # }}} - # {{{ discord rich presence - satellite.neovim.lazy.discord-rich-presence = { - package = "andweeb/presence.nvim"; - main = "presence"; - - env.blacklist = [ "vscode" "firenvim" ]; - event = "BufReadPost"; - setup = true; - }; - # }}} - # {{{ gitlinker - # generate permalinks for code - satellite.neovim.lazy.gitlinker = - let mapping = "yg"; - in - { - package = "ruifm/gitlinker.nvim"; - dependencies.lua = [ lazy.plenary.package ]; - - env.blacklist = [ "vscode" "firenvim" ]; - opts.mappings = mapping; - keys = mapping; - }; - # }}} - # {{{ paperplanes - # export to pastebin like services - satellite.neovim.lazy.paperlanes = { - package = "rktjmp/paperplanes.nvim"; - cmd = "PP"; - opts.provider = "paste.rs"; - }; - # }}} - # {{{ obsidian - satellite.neovim.lazy.obsidian = - let vault = "${config.xdg.userDirs.extraConfig.XDG_PROJECTS_DIR}/stellar-sanctum"; - in - { - package = "epwalsh/obsidian.nvim"; - dependencies.lua = [ lazy.plenary.package ]; - - env.blacklist = [ "vscode" "firenvim" ]; - cond = nlib.lua /* lua */ "vim.loop.cwd() == ${nlib.encode vault}"; - event = "VeryLazy"; - - keys.mapping = ""; - keys.action = "ObsidianQuickSwitch"; - - opts = { - dir = vault; - notes_subdir = "chaos"; - daily_notes = { - folder = "daily"; - date_format = "%Y-%m-%d"; - }; - - completion = { - nvim_cmp = true; - min_chars = 2; - new_notes_location = "current_dir"; - prepend_note_id = true; - }; - - mappings = { }; - disable_frontmatter = true; - }; - }; - # }}} - # }}} - # }}} # {{{ Persistence satellite.persistence.at.state.apps.neovim.directories = [ ".local/state/nvim" diff --git a/modules/common/korora-lua.nix b/modules/common/korora-lua.nix index 205a0a9..6cc4b65 100644 --- a/modules/common/korora-lua.nix +++ b/modules/common/korora-lua.nix @@ -2,52 +2,78 @@ let k = korora; - # {{{ Lua encoders # {{{ Helpers helpers = rec { - xor = a: b: (a || b) && (!a || !b); - implies = a: b: !a || b; - hasProp = obj: p: (obj.${p} or null) != null; - propXor = a: b: obj: xor (hasProp obj a) (hasProp obj b); + removeEmptyLines = str: + lib.pipe str [ + (lib.splitString "\n") + (lib.lists.filter + (line: lib.any + (c: c != " ") + (lib.stringToCharacters line))) + (lib.concatStringsSep "\n") + ]; + + # {{{ Verification helpers + toPretty = lib.generators.toPretty { indent = " "; }; + withError = cond: err: if cond then null else err; + hasProp = obj: p: obj ? ${p}; + propExists = p: obj: + helpers.withError + (helpers.hasProp obj p) + "Expected property ${p}"; + propXor = a: b: obj: + helpers.withError + ((hasProp obj a) != (hasProp obj b)) + "Expected only one of the properties ${a} and ${b} in object ${toPretty obj}"; propOnlyOne = props: obj: - 1 == lib.count (prop: obj ? prop); - propImplies = a: b: obj: implies (hasProp obj a) (hasProp obj b); + helpers.withError + (1 == lib.count (hasProp obj) props) + "Expected exactly one property from the list ${toPretty props} in object ${toPretty obj}"; + propImplies = a: b: obj: + helpers.withError + ((hasProp obj a) -> (hasProp obj b)) + "Property ${a} should imply property ${b} in object ${toPretty obj}"; mkVerify = checks: obj: + assert lib.all lib.isFunction checks; let - results = lib.lists.map checks obj; + results = lib.lists.map (c: c obj) checks; errors = lib.lists.filter (v: v != null) results; in + assert lib.all lib.isString errors; if errors == [ ] then null else let prettyErrors = lib.lists.map (s: "\n- ${s}") errors; in - "Multiple errors occured: ${prettyErrors}"; - + "Multiple errors occured: ${lib.concatStrings prettyErrors}"; + # }}} + # {{{ Custom types intersection = l: r: k.typedef' "${l.name} ∧ ${r.name}" (helpers.mkVerify [ l r ]); - dependentAttrsOf = - name: mkType: - let - typeError = name: v: "Expected type '${name}' but value '${toPretty v}' is of type '${typeOf v}'"; - addErrorContext = context: error: if error == null then null else "${context}: ${error}"; - - withErrorContext = addErrorContext "in ${name} value"; - in - k.typedef' name - (v: - if ! lib.isAttrs v then - typeError name v - else - withErrorContext - (mkVerify - (lib.mapAttrsToList (k: _: mkType k) v) - v)); - + # dependentAttrsOf = + # name: mkType: + # let + # typeError = name: v: "Expected type '${name}' but value '${toPretty v}' is of type '${typeOf v}'"; + # addErrorContext = context: error: if error == null then null else "${context}: ${error}"; + # + # withErrorContext = addErrorContext "in ${name} value"; + # in + # k.typedef' name + # (v: + # if ! lib.isAttrs v then + # typeError name v + # else + # withErrorContext + # (mkVerify + # (lib.mapAttrsToList (k: _: mkType k) v) + # v)); + # }}} + # {{{ Encoding helpers mkRawLuaObject = chunks: '' { @@ -55,238 +81,83 @@ let } ''; + encodeString = given: ''"${lib.escape ["\"" "\\"] (toString given)}"''; + mkAttrName = s: let # These list *are* incomplete - forbiddenChars = lib.stringToCharacters "<>[]{}()'\".,;"; + forbiddenChars = lib.stringToCharacters "<>[]{}()'\".,:;"; keywords = [ "if" "then" "else" "do" "for" "local" "" ]; in if lib.any (c: lib.hasInfix c s) forbiddenChars || lib.elem s keywords then - "[${m.string s}]" + "[${helpers.encodeString s}]" else s; - + # }}} }; # }}} - - # We provide a custom set of helpers for generating lua code for nix.enable - # - # An encoder is a function from some nix value to a string containing lua code. - # This object provides combinators for writing such encoders. - m = { - # {{{ General helpers - typed = type: toLua: type // { - unsafeToLua = toLua; - toLua = v: toLua (type.check v); - override = a: m.typed (type.override a) toLua; - }; - withDefault = type: default: type // { - default.value = default; - }; - typedWithDefault = type: toLua: default: - m.withDefault (m.typed type toLua) default; - unsafe = type: type // { toLua = type.unsafeToLua; }; - untyped = m.typed k.any; - untype = type: m.untyped type.toLua; - withName = name: type: type // { inherit name; }; # TODO: should we use override here? - # `const` is mostly useful together with `bind`. See the lua encoder for - # lazy modules for example usage. - const = code: m.untyped (_: code); - # Conceptually, this is the monadic bind operation for encoders. - # This implementation is isomoprhic to that of the reader monad in haskell. - bind = name: higherOrder: - m.typed - (k.typedef' name (v: (higherOrder v).verify v)) - (given: (higherOrder given).toLua given); - # This is probably the most useful combinnator defined in this entire object. - # Most of the combinators in the other categories are based on this. - conditional = predicate: caseTrue: caseFalse: - let base = m.bind - m.bind - "${caseTrue.name} ∨ ${caseFalse.name}" - (given: if predicate given then caseTrue else caseFalse); + # {{{ This function takes a nix value and encodes it to a lua string. + isLuaLiteral = given: lib.isAttrs given && given.__luaEncoderTag or null == "lua"; + encodeWithDepth = depth: given: + let recurse = encodeWithDepth depth; in + if lib.isString given || lib.isDerivation given || lib.isPath given then + helpers.encodeString given + else if lib.isInt given || lib.isFloat given then + toString given + else if lib.isBool given then + if given then "true" else "false" + else if given == null then + "nil" + else if lib.isList given then + helpers.mkRawLuaObject (lib.lists.map recurse given) + else if lib.isAttrs given && given.__luaEncoderTag or null == "foldedList" then + let + makeFold = name: value: '' + -- {{{ ${name} + ${recurse value}, + -- }}}''; + folds = lib.mapAttrsToList makeFold given.value; in - if caseTrue ? default then - m.withDefault base caseTrue.default - else if caseFalse ? default then - m.withDefault base caseFalse.default - else - base; - try = caseTrue: caseFalse: - let base = m.bind - "${caseTrue.name} ∨ ${caseFalse.name}" - (given: if caseTrue.verify given == null then m.unsafe caseTrue else caseFalse); - in - if caseTrue ? default then - m.withDefault base caseTrue.default - else if caseFalse ? default then - m.withDefault base caseFalse.default - else - base; - oneOf = lib.foldr m.try - (m.bottom (v: "No variants matched for value ${builtins.toJSON v}")); - # This is simply left-composition of functions - cmap = f: t: - m.typed - (lib.typedef' t.name (v: t.verify (f v))) - (given: t.toLua (f given)); - # This is simply right-composition of functions - map = f: t: m.typed t (given: f (t.toLua given)); - filter = predicate: type: given: - m.conditional predicate type (m.untype m.nil); - # This is mostly useful for debugging - trace = message: m.cmap (f: lib.traceSeq message (lib.traceVal f)); - bottom = mkMessage: m.typed (m.typedef "⊥" mkMessage) lib.id; - # }}} - # {{{ Base types - string = m.typed k.string (given: ''"${lib.escape ["\"" "\\"] (toString given)}"''); - bool = m.typed k.bool (bool: if bool then "true" else "false"); - integer = m.typed k.int toString; - float = m.typed k.float toString; - number = m.typed k.number toString; - ignored = type: m.typed type (_: "nil"); - nil = m.typedWithDefault - (k.typedef "null" (v: v == null)) - (_: "nil") - null; - stringOr = m.try m.string; - boolOr = m.try m.bool; - numberOr = m.try m.number; - nullOr = m.try m.nil; - anything = m.withName "⊤" (m.oneOf [ - m.markedLuaCode # Lua code expressions have priority over attrsets - m.string - m.number - m.bool - m.null - (m.listOf m.anything) - (m.attrsetOf m.anything) - ]); - # }}} - # {{{ Lua code - identity = m.typed k.string lib.id; - markedLuaCode = - m.typed - (k.struct "marked lua code" { - value = k.string; - __luaEncoderTag = k.enum "lua encoder tag" [ "lua" ]; - }) - (obj: obj.value); - # This is the most rudimentary (and currently only) way of handling paths. - luaImport = tag: - m.typed (k.typedef "path" lib.isPath) - (path: "dofile(${m.string "${path}"}).${tag}"); - # Accepts both tagged and untagged strings of lua code. - luaString = m.try m.markedLuaCode m.identity; - # This simply combines the above combinators into one. - luaCode = tag: m.try (m.luaImport tag) m.luaString; - # }}} - # {{{ Operators - conjunction = left: right: given: - m.typed (helpers.intersection left right) ( - let - l = left.toLua given; - r = right.toLua given; - in - if l == "nil" then r - else if r == "nil" then l - else "${l} and ${r}" - ); - all = lib.foldr m.conjunction m.nil; - # Similar to `all` but takes in a list and - # treats every element as a condition. - allIndices = name: type: - m.bind name - (g: lib.pipe g [ - (lib.lists.imap0 - (i: _: - m.cmap - (builtins.elemAt i) - type)) - m.all - ]); - # }}} - # {{{ Lists - listOf = type: list: - m.typedWithDefault - (k.listOf type) - (helpers.mkRawLuaObject (lib.lists.map type.toLua list)) - [ ]; - listOfOr = type: m.try (m.listOf type); - # Returns nil when given empty lists - tryNonemptyList = type: - m.typedWithDefault - (k.listOf type) - (m.filter - (l: l != [ ]) - (m.listOf type)) - [ ]; - oneOrMany = type: m.listOfOr type type; - # Can encode: - # - zero values as nil - # - one value as itself - # - multiple values as a list - zeroOrMany = type: m.nullOr (m.oneOrMany type); - # Coerces non list values to lists of one element. - oneOrManyAsList = type: m.listOfOr type (m.map (e: [ e ]) type); - # Coerces lists of one element to said element. - listAsOneOrMany = type: - m.cmap - (l: if lib.length l == 1 then lib.head l else l) - (m.oneOrMany type); - # }}} - # {{{ Attrsets - attrsetOf = type: - m.typed (k.attrsOf type) - (object: - helpers.mkRawLuaObject (lib.mapAttrsToList - (name: value: - let result = type.toLua value; - in - lib.optionalString (result != "nil") - "${helpers.mkAttrName name} = ${result}" - ) - object + '' + { + ${lib.concatStringsSep "\n" folds} + }'' + else if isLuaLiteral given then + given.value + else if lib.isAttrs given then + helpers.mkRawLuaObject + (lib.mapAttrsToList + (name: value: + let result = recurse value; + in + lib.optionalString (result != "nil") + "${helpers.mkAttrName name} = ${result}" ) - ); - # This is the most general combinator provided in this section. - # - # We accept: - # - order of props that should be interpreted as list elements - # - spec of props that should be interpreted as list elements - # - record of props that should be interpreted as attribute props - attrset = name: listOrder: spec: attrset: - m.cmap - (given: lib.mapAttrs - (key: type: - if given ? ${key} then - given.${key} - else - type.default or null) - spec) - (m.typed (k.struct name spec) ( - let - listChunks = lib.lists.map - (attr: - let result = spec.${attr}.toLua (attrset.${attr} or null); - in - lib.optionalString (result != "nil") result - ) - listOrder; - objectChunks = lib.mapAttrsToList - (attr: type: - let result = type.toLua (attrset.${attr} or null); - in - lib.optionalString (!(lib.elem attr listOrder) && result != "nil") - "${helpers.mkAttrName attr} = ${result}" - ) - spec; - in - helpers.mkRawLuaObject (listChunks ++ objectChunks) - )); - withAttrsCheck = type: verify: - type.override { inherit verify; }; - # }}} - }; + given) + else if lib.isFunction given then + let + argNames = [ "context" "inner_context" "local_context" ]; + secretArg = + if depth >= builtins.length argNames + then "arg_${depth}" + else builtins.elemAt argNames depth; + body = given secretArg; + encoded = encodeWithDepth (depth + 1) body; + encodedBody = + if isLuaLiteral body + then encoded + else "return ${encoded}"; + in + if lib.hasInfix secretArg encoded + then '' + function(${secretArg}) + ${encodedBody} + end'' + else '' + function() + ${encodedBody} + end'' + else builtins.throw "Cannot encode value ${helpers.toPretty given}"; # }}} + encode = encodeWithDepth 0; in -m // { inherit helpers; } +{ inherit encode helpers; } diff --git a/modules/common/korora-neovim.nix b/modules/common/korora-neovim.nix index 6a07d68..d5c1a02 100644 --- a/modules/common/korora-neovim.nix +++ b/modules/common/korora-neovim.nix @@ -1,36 +1,234 @@ -attrs@{ lib, ... }: +attrs@{ lib, korora, ... }: let e = import ./korora-lua.nix attrs; + k = korora; h = e.helpers; - m = { - lazyModule = e.withAttrsCheck - (e.attrset "lazy module" [ "package" ] { - package = e.nullOr e.string; - dir = e.nullOr e.type; - version = e.nullOr e.string; - tag = e.nullOr e.string; - name = e.nullOr e.string; - main = e.nullOr e.string; - lazy = e.nullOr e.bool; - dependencies = e.tryNonemptyList (e.stringOr m.lazyModule); - config = e.anything; - cond = e.nullOr (e.bind (value: e.all [ - # TODO: we want a zip here - (e.nullOr (e.luaCode "cond")) - ])); - init = e.nullOr (e.luaCode "init"); - event = e.zeroOrMany e.string; - cmd = e.zeroOrMany e.string; - ft = e.zeroOrMany e.string; - keys = e.listOf e.anything; - passthrough = e.anything; - opts = e.anything; - }) - (h.mkVerify [ + struct = name: props: verify: (k.struct name props).override { + total = false; + unknown = false; + verify = h.mkVerify verify; + }; + + lazyType = name: mkType: k.typedef' name (v: (mkType true).verify v); + + types = { + # {{{ Helper types + oneOrMany = type: k.union [ type (k.listOf type) ]; + luaValue = k.any; + strictLuaLiteral = (k.struct "lua literal" { + value = k.string; + __luaEncoderTag = k.enum "lua literal tag" [ "lua" ]; + }).override { unknown = false; }; + derivation = k.typedef "derivation" lib.isDerivation; + path = k.typedef "path" lib.isPath; + functionCheckedWith = arg: type: + k.typedef' + "${h.toPretty arg} -> ${type.name}" + (f: + if lib.isFunction f + then type.verify (f arg) + else "Expected function, but got ${h.toPretty f} instead"); + luaEagerOrLazy = type: k.union [ type (types.functionCheckedWith "" type) ]; + luaLiteral = types.luaEagerOrLazy types.strictLuaLiteral; + # }}} + # {{{ Lazy key + lazyKey = struct "lazy key" + { + mapping = k.string; + action = k.union [ types.luaLiteral k.string ]; + mode = k.string; + desc = k.string; + expr = k.bool; + ft = types.oneOrMany k.string; + } + [ (h.propExists "mapping") ]; + # }}} + # {{{ Lazy module + lazyModule = lazyType "actually lazy lazy module" (_: struct "lazy module" + { + package = k.string; + dir = k.union [ k.string types.derivation types.path ]; + version = k.string; + tag = k.string; + name = k.string; + main = k.string; + lazy = k.bool; + dependencies = struct "lazy dependencies" + { + lua = k.listOf (k.union [ k.string types.lazyModule ]); + nix = k.listOf types.derivation; + } + [ ]; + cond = types.oneOrMany types.luaLiteral; + init = types.luaLiteral; + config = k.union [ types.luaLiteral k.bool types.tempestConfig ]; + event = types.oneOrMany k.string; + cmd = types.oneOrMany k.string; + ft = types.oneOrMany k.string; + keys = types.oneOrMany (k.union [ k.string types.lazyKey ]); + passthrough = types.luaValue; + opts = types.luaValue; + } + [ (h.propOnlyOne [ "dir" "package" ]) (h.propImplies "tag" "package") (h.propImplies "version" "package") ]); + # }}} + # {{{ Tempest key + tempestKey = struct "tempest key" + { + mapping = k.string; + action = k.union [ types.luaLiteral k.string ]; + mode = k.string; + desc = k.string; + expr = k.bool; + silent = k.bool; + ft = types.oneOrMany k.string; + buffer = k.union [ k.bool k.number types.luaLiteral ]; + } + [ + (h.propExists "mapping") + (h.propExists "action") + ]; + # }}} + # {{{ Tempest autocmd + tempestAutocmd = struct "tempest autocommand" + { + event = types.oneOrMany k.string; + pattern = types.oneOrMany k.string; + group = k.string; + action = k.union [ types.tempestConfig types.luaLiteral ]; + } + [ + (h.propExists "event") + (h.propExists "group") + (h.propExists "action") + ]; + # }}} + # {{{ Tempest config + tempestConfig = lazyType "lazy tempest config" (_: struct "tempest config" + { + vim = types.luaValue; + callback = k.union [ k.function types.luaLiteral ]; + setup = k.attrsOf types.luaValue; + keys = types.luaEagerOrLazy (types.oneOrMany types.tempestKey); + autocmds = types.luaEagerOrLazy (types.oneOrMany types.tempestAutocmd); + mkContext = types.luaLiteral; + } + [ ]); + # }}} + # {{{ Neovim env + neovimEnv = k.enum "neovim env" + [ "neovide" "firenvim" "vscode" ]; + # }}} + # {{{ Neovim config + neovimConfig = struct "neovim configuration" + { + pre = k.attrsOf types.tempestConfig; + lazy = k.attrsOf types.lazyModule; + post = k.attrsOf types.tempestConfig; + } [ ]; + # }}} }; + + hasType = type: value: + let err = type.verify value; in + lib.assertMsg (err == null) err; + + mkLib = { tempestModule, languageServerModule }: + assert hasType k.string tempestModule; + assert hasType k.string languageServerModule; + rec { + inherit (e) encode; + # {{{ Common generation helpers + lua = value: assert hasType k.string value; + { inherit value; __luaEncoderTag = "lua"; }; + import = path: tag: + assert lib.isPath path; + assert hasType k.string tag; + lua "dofile(${encode (toString path)}).${tag}"; + foldedList = value: assert hasType k.attrs value; + { inherit value; __luaEncoderTag = "foldedList"; }; + thunk = code: _: lua code; + tempest = given: context: lua '' + D.tempest.configure( + ${encode given}, + ${context} + ) + ''; + customLanguageServerOnAttach = given: + assert hasType types.tempestConfig given; + lua /* lua */ '' + function(client, bufnr) + D.tempest.configure(${encode given}, + { client = client; bufnr = bufnr; }) + + D.language_server.on_attach(client, bufnr) + end + ''; + blacklist = given: + assert hasType (types.oneOrMany types.neovimEnv) given; + lua /* lua */ '' + D.tempest.blacklist(${encode given}) + ''; + whitelist = given: + assert hasType (types.oneOrMany types.neovimEnv) given; + lua /* lua */ '' + D.tempest.whitelist(${encode given}) + ''; + # }}} + # {{{ Main config generation entrypoint + generateConfig = rawConfig: + assert hasType types.neovimConfig rawConfig; + let + config = { lazy = { }; pre = { }; post = { }; } // rawConfig; + collectNixDeps = lazyModule: + if lazyModule ? dependencies then + let + nix = lazyModule.dependencies.nix or [ ]; + other = lib.pipe (lazyModule.dependencies.lua or [ ]) [ + (lib.lists.map collectNixDeps) + lib.lists.flatten + ]; + in + nix ++ other + else + [ ]; + dependencies = lib.pipe config.lazy [ + (lib.mapAttrsToList (_: collectNixDeps)) + lib.lists.flatten + ]; + processedLazyModules = + lib.mapAttrs + (name: module: { inherit name; } // module // { + dependencies = (module.dependencies or { }).lua or null; + }) + config.lazy; + + luaConfig = '' + local M = {} + local D = { + tempest = require(${encode tempestModule}), + langauge_server = require(${encode languageServerModule}) + } + + -- {{{ Pre-plugin config + M.pre = ${encode (foldedList config.pre)} + -- }}} + -- {{{ Lazy modules + M.lazy = ${encode (foldedList processedLazyModules)} + D.tempest.prepareLazySpec(M.lazy) + -- }}} + -- {{{ Post-plugin config + M.post = ${encode (foldedList config.post)} + -- }}} + + return M + ''; + in + { inherit dependencies; lua = luaConfig; }; + # }}} + }; in -m +mkLib diff --git a/modules/common/lua-encoders.nix b/modules/common/lua-encoders.nix index d621bb3..e40abed 100644 --- a/modules/common/lua-encoders.nix +++ b/modules/common/lua-encoders.nix @@ -196,7 +196,7 @@ in let destination = "${path}/${name}.lua"; unformatted = pkgs.writeText "raw-lua-${name}" '' - -- ❄️ This file was generated using nix ^~^ + -- ❄️ I was generated using nix ^~^ ${text} ''; in