diff --git a/modules/nixvim/keymaps/default.nix b/modules/nixvim/keymaps/default.nix new file mode 100644 index 0000000..d4daabd --- /dev/null +++ b/modules/nixvim/keymaps/default.nix @@ -0,0 +1,430 @@ +{ lib, ... }: +let + inherit (lib) + map + mapAttrsToList + toUpper + substring + mapCartesianProduct + toLower + ; +in +{ + globals = { + mapleader = " "; + maplocalleader = "\\"; + }; + + keymaps = + # Disable Arrow Key Movement - - - - - - - - - - - - - - - - - - - - - - - - + (map + (d: { + mode = [ + "i" + "n" + ]; + key = d; + action = ""; + }) + [ + "" + "" + "" + "" + ] + ) + + # Better Up/Down - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + ++ (mapAttrsToList + (d: k: { + mode = [ + "n" + "x" + ]; + key = k; + action = "v:count == 0 ? 'g${k}' : '${k}'"; + options = { + desc = d; + expr = true; + silent = true; + }; + }) + { + Down = "j"; + Up = "k"; + } + ) + + # Resize Windows - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + ++ (mapAttrsToList + ( + k: d: + let + s = if (d == "Increase") then "+" else "-"; + in + { + mode = [ "n" ]; + key = ""; + action = "resize ${s}4"; + options = { + desc = "${d} Window Height"; + }; + } + ) + { + Up = "Increase"; + Down = "Decrease"; + Left = "Decrease"; + Right = "Increase"; + } + ) + + # Buffers - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + ++ (mapAttrsToList + ( + k: d: + let + # previous -> Prev + prettyDir = (toUpper (substring 0 1 d)) + (substring 1 3 d); + in + { + mode = [ "n" ]; + key = k; + action = "b${d}"; + options = { + desc = "${prettyDir} Buffer"; + }; + } + ) + { + "" = "previous"; + "" = "next"; + "[b" = "previous"; + "]b" = "next"; + } + ) + + ++ [ + { + mode = [ "n" ]; + key = "bD"; + action = "bd"; + options = { + desc = "Delete Buffer and Window"; + }; + } + ] + + # Clear Search/Diff Update/Redraw - - - - - - - - - - - - - - - - - - - - - + ++ [ + { + mode = [ + "i" + "n" + ]; + key = ""; + action = "noh"; + options = { + desc = "Escape and Clear hlsearch"; + }; + } + + { + mode = [ "n" ]; + key = "ur"; + action = "nohlsearchdiffupdatenormal! "; + options = { + desc = "Redraw / Clear hlsearch / Diff Update"; + }; + } + ] + + # Better n & N - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + ++ (mapCartesianProduct + ( + { m, d }: + let + nn = if (d == "Next") then "Nn" else "nN"; + zv = if (m == "n") then ".'zv'" else ""; + in + { + mode = [ m ]; + key = if (d == "Next") then "n" else "N"; + action = "'${nn}'[v:searchforward]${zv}"; + options = { + desc = "${d} Search Result"; + expr = true; + }; + } + ) + { + m = [ + "n" + "x" + "o" + ]; + d = [ + "Next" + "Prev" + ]; + } + ) + + # Undo Break-Points - - - - - - - - - - - - - - - - - - - - - - - - - - - - + ++ (map + (c: { + mode = [ "i" ]; + key = c; + action = "${c}u"; + }) + [ + "," + "." + ";" + ] + ) + + # Search Docs (keywordprog) - - - - - - - - - - - - - - - - - - - - - - - - + # https://til.codeinthehole.com/posts/about-how-to-use-keywordprg-effectively/ + ++ [ + { + mode = [ "n" ]; + key = "K"; + action = "norm! K"; + options = { + desc = "Search for word"; + }; + } + ] + + # Better Indenting - - - - - - - - - - - - - - - - - - - - - - - - - - - - + ++ (map + (c: { + mode = [ "v" ]; + key = c; + action = "${c}gv"; + }) + [ + "<" + ">" + ] + ) + + # Commenting - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + ++ (mapAttrsToList + (k: d: { + mode = [ "n" ]; + key = "gc${k}"; + action = "${k}Vcxnormal gccfxa"; + options = { + desc = "Add Comment ${d}"; + }; + }) + { + o = "Below"; + O = "Above"; + } + ) + + # New File - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + ++ [ + { + mode = [ "n" ]; + key = "fn"; + action = "enew"; + options = { + desc = "New File"; + }; + } + ] + + # Locations/Quickfixes - - - - - - - - - - - - - - - - - - - - - - - - - - - + ++ [ + { + mode = [ "n" ]; + key = "xl"; + action = "lopen"; + options = { + desc = "Location List"; + }; + } + + { + mode = [ "n" ]; + key = "xq"; + action = "copen"; + options = { + desc = "Quickfix list"; + }; + } + ] + + ++ (mapAttrsToList + ( + k: d: + let + cmd = substring 0 4 (toLower d); + in + { + mode = [ "n" ]; + key = "${k}q"; + action = "c${cmd}"; + options = { + desc = "${d} Quickfix"; + }; + } + ) + { + "[" = "Previous"; + "]" = "Next"; + } + ) + + # Diagnostics - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + ++ [ + { + mode = [ "n" ]; + key = "cd"; + action.__raw = # lua + "function() vim.diagnostic.open_float() end"; + options = { + desc = "Line Diagnostics"; + }; + } + ] + + ++ (mapCartesianProduct + ( + { dir, key }: + let + sevs = { + d = { + key = "nil"; + desc = "Diagnostic"; + }; + e = { + key = "vim.diagnostic.severity.ERROR"; + desc = "Error"; + }; + w = { + key = "vim.diagnostic.severity.WARN"; + desc = "Warning"; + }; + }; + + cmd = toLower dir; + sev = sevs."${key}".key; + in + { + mode = [ "n" ]; + key = "${if (dir == "Next") then "]" else "["}${key}"; + action.__raw = # lua + "function() vim.diagnostic.goto_${cmd}({ severity = ${sev} }) end"; + options = { + desc = "${dir} ${sevs."${key}".desc}"; + }; + } + ) + { + dir = [ + "Next" + "Prev" + ]; + key = [ + "d" + "e" + "w" + ]; + } + ) + + # Quit - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + ++ [ + { + mode = [ "n" ]; + key = "qq"; + action = "qa"; + options = { + desc = "Quit All"; + }; + } + ] + + # Inspect - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + ++ [ + { + mode = [ "n" ]; + key = "ui"; + action.__raw = # lua + "vim.show_pos"; + options = { + desc = "Inspect Position"; + }; + } + ] + + # Window Management - - - - - - - - - - - - - - - - - - - - - - - - - - - - + ++ [ + { + mode = [ "n" ]; + key = "w"; + action = ""; + options = { + desc = "Windows"; + remap = true; + }; + } + + { + mode = [ "n" ]; + key = "-"; + action = "s"; + options = { + desc = "Split Window Below"; + remap = true; + }; + } + + { + mode = [ "n" ]; + key = "|"; + action = "v"; + options = { + desc = "Split Window Right"; + remap = true; + }; + } + + { + mode = [ "n" ]; + key = "wd"; + action = "c"; + options = { + desc = "Delete Window"; + remap = true; + }; + } + + ] + + # Tab Management - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + ++ (mapAttrsToList + (k: a: { + mode = [ "n" ]; + key = "${k}"; + action = "tab${toLower a}"; + options = { + desc = if (a == "Only") then "Close Other Tabs" else "${a} Tab"; + }; + }) + { + l = "Last"; + o = "Only"; + f = "First"; + "" = "New"; + "]" = "Next"; + d = "Close"; + "[" = "Previous"; + } + ); +} diff --git a/modules/nixvim/plugins/base/snacks/default.nix b/modules/nixvim/plugins/base/snacks/default.nix new file mode 100644 index 0000000..6dd1f37 --- /dev/null +++ b/modules/nixvim/plugins/base/snacks/default.nix @@ -0,0 +1,207 @@ +{ pkgs, ... }: +{ + extraPackages = [ pkgs.lazygit ]; + + plugins.snacks = { + enable = true; + + bigfile.enabled = true; + notifier.enabled = true; + quickfile.enabled = true; + statuscolumn.enabled = true; + words.enabled = true; + }; + + keymaps = [ + { + mode = [ "n" ]; + key = "un"; + action.__raw = # lua + "function() Snacks.notifier.hide() end"; + options = { + desc = "Dismiss All Notifications"; + }; + } + + { + mode = [ "n" ]; + key = "bd"; + action.__raw = # lua + "function() Snacks.bufdelete() end"; + options = { + desc = "Delete Buffer"; + }; + } + + { + mode = [ "n" ]; + key = "bo"; + action.__raw = # lua + "function() Snacks.bufdelete.other() end"; + options = { + desc = "Delete Other Buffers"; + }; + } + + # LazyGit - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + { + mode = [ "n" ]; + key = "gg"; + action.__raw = # lua + "function() Snacks.lazygit() end"; + options = { + desc = "Lazygit"; + }; + } + + { + mode = [ "n" ]; + key = "gf"; + action.__raw = # lua + "function() Snacks.lazygit.log_file() end"; + options = { + desc = "Lazygit Current File History"; + }; + } + + { + mode = [ "n" ]; + key = "gl"; + action.__raw = # lua + "function() Snacks.lazygit.log() end"; + options = { + desc = "Lazygit Log"; + }; + } + + # Git - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + { + mode = [ "n" ]; + key = "gb"; + action.__raw = # lua + "function() Snacks.git.blame_line() end"; + options = { + desc = "Gib Blame Line"; + }; + } + + { + mode = [ + "n" + "x" + ]; + key = "gB"; + action.__raw = # lua + "function() Snacks.gitbrowse() end"; + options = { + desc = "Git Browse"; + }; + } + ]; + + # Toggles - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + extraConfigLua = # lua + '' + ---@param buf? number + function format_enabled(buf) + buf = (buf == nil or buf == 0) and vim.api.nvim_get_current_buf() or buf + local gaf = vim.g.autoformat + local baf = vim.b[buf].autoformat + + -- If the buffer has a local value, use that + if baf ~= nil then + return baf + end + + -- Otherwise use the global value if set, or true by default + return gaf == nil or gaf + end + + ---@param enable? boolean + ---@param buf? boolean + function format_enable(state, buf) + if enable == nil then + enable = true + end + + if buf then + vim.b.autoformat = enable + else + vim.g.autoformat = enable + vim.b.autoformat = nil + end + end + + ---@param buf? boolean + function toggle_format(buf) + return Snacks.toggle({ + name = "Auto Format (" .. (buf and "Buffer" or "Global") .. ")", + get = function() + if not buf then + return vim.g.autoformat == nil or vim.g.autoformat + end + return format_enabled() + end, + set = function(state) + format_enable(state, buf) + end, + }) + end + + toggle_format():map("uf") + toggle_format(true):map("uF") + + Snacks.toggle.option("spell", { name = "Spelling"}):map("us") + Snacks.toggle.option("wrap", {name = "Wrap"}):map("uw") + Snacks.toggle.option("relativenumber", { name = "Relative Number"}):map("uL") + Snacks.toggle.diagnostics():map("ud") + Snacks.toggle.line_number():map("ul") + Snacks.toggle.option("conceallevel", {off = 0, on = vim.o.conceallevel > 0 and vim.o.conceallevel or 2}):map("uc") + Snacks.toggle.treesitter():map("uT") + Snacks.toggle.option("background", { off = "light", on = "dark" , name = "Dark Background"}):map("ub") + if vim.lsp.inlay_hint then + Snacks.toggle.inlay_hints():map("uh") + end + + function maximize_window() + ---@type {k:string, v:any}[]? + local maximized = nil + return Snacks.toggle({ + name = "Maximize", + get = function() + return maximized ~= nil + end, + set = function(state) + if state then + maximized = {} + local function set(k, v) + table.insert(maximized, 1, { k = v, v = vim.o[k] }) + vim.o[k] = v + end + set("winwidth", 999) + set("winheight", 999) + set("winminwidth", 10) + set("winminheight", 4) + vim.cmd("wincmd =") + vim.api.nvim_create_autocmd("ExitPre", { + once = true, + group = vim.api.nvim_create_augroup("marleyvim_restore_max_exit_pre", { clear = true }), + desc = "Restore width/height when closing Neovim while maximized", + callback = function() + maximize_window.set(false) + end, + }) + else + for _, opt in ipairs(maximized) do + vim.o[opt.k] = opt.v + end + maximized = nil + vim.cmd("wincmd =") + end + end, + }) + end + + maximize_window():map("wm") + ''; +}