diff --git a/plugins/default.nix b/plugins/default.nix index ca984a64..01c68e6d 100644 --- a/plugins/default.nix +++ b/plugins/default.nix @@ -18,6 +18,7 @@ ./git/fugitive.nix ./git/gitgutter.nix + ./git/gitsigns.nix ./git/neogit.nix ./languages/ledger.nix diff --git a/plugins/git/gitsigns.nix b/plugins/git/gitsigns.nix new file mode 100644 index 00000000..368c5f9f --- /dev/null +++ b/plugins/git/gitsigns.nix @@ -0,0 +1,433 @@ +{ + config, + lib, + pkgs, + helpers, + ... +}: +with lib; let + signOptions = defaults: + with types; { + hl = mkOption { + type = str; + description = "Specifies the highlight group to use for the sign"; + default = defaults.hl; + }; + text = mkOption { + type = str; + description = "Specifies the character to use for the sign"; + default = defaults.text; + }; + numhl = mkOption { + type = str; + description = "Specifies the highlight group to use for the number column"; + default = defaults.numhl; + }; + linehl = mkOption { + type = str; + description = "Specifies the highlight group to use for the line"; + default = defaults.linehl; + }; + showCount = mkEnableOption "Enable showing count of hunk, e.g. number of deleted lines"; + }; + signSetupOptions = values: { + inherit (values) hl text numhl linehl; + show_count = values.showCount; + }; + + luaFunction = types.submodule { + options.function = mkOption { + type = types.str; + description = "Lua function definition"; + }; + }; +in { + options.plugins.gitsigns = { + enable = mkEnableOption "Enable gitsigns plugin"; + signs = { + add = signOptions { + hl = "GitSignsAdd"; + text = "┃"; + numhl = "GitSignsAddNr"; + linehl = "GitSignsAddLn"; + }; + change = signOptions { + hl = "GitSignsChange"; + text = "┃"; + numhl = "GitSignsChangeNr"; + linehl = "GitSignsChangeLn"; + }; + delete = signOptions { + hl = "GitSignsDelete"; + text = "▁"; + numhl = "GitSignsDeleteNr"; + linehl = "GitSignsDeleteLn"; + }; + topdelete = signOptions { + hl = "GitSignsDelete"; + text = "▔"; + numhl = "GitSignsDeleteNr"; + linehl = "GitSignsDeleteLn"; + }; + changedelete = signOptions { + hl = "GitSignsChange"; + text = "~"; + numhl = "GitSignsChangeNr"; + linehl = "GitSignsChangeLn"; + }; + untracked = signOptions { + hl = "GitSignsAdd"; + text = "┆"; + numhl = "GitSignsAddNr"; + linehl = "GitSignsAddLn"; + }; + }; + worktrees = let + worktreeModule = { + options = { + toplevel = mkOption { + type = types.str; + }; + gitdir = mkOption { + type = types.str; + }; + }; + }; + in + mkOption { + type = types.nullOr (types.listOf (types.submodule worktreeModule)); + default = null; + description = '' + Detached working trees. + If normal attaching fails, then each entry in the table is attempted with the work tree + details set. + ''; + }; + onAttach = mkOption { + type = types.nullOr luaFunction; + default = null; + description = '' + Callback called when attaching to a buffer. Mainly used to setup keymaps + when `config.keymaps` is empty. The buffer number is passed as the first + argument. + + This callback can return `false` to prevent attaching to the buffer. + ''; + example = '' + \'\' + function(bufnr) + if vim.api.nvim_buf_get_name(bufnr):match() then + -- Don't attach to specific buffers whose name matches a pattern + return false + end + -- Setup keymaps + vim.api.nvim_buf_set_keymap(bufnr, 'n', 'hs', 'lua require"gitsigns".stage_hunk()', {}) + ... -- More keymaps + end + \'\' + ''; + }; + + watchGitDir = { + enable = mkOption { + type = types.bool; + default = true; + description = "Whether the watcher is enabled"; + }; + interval = mkOption { + type = types.int; + default = 1000; + description = "Interval the watcher waits between polls of the gitdir in milliseconds"; + }; + followFiles = mkOption { + type = types.bool; + default = true; + description = "If a file is moved with `git mv`, switch the buffer to the new location"; + }; + }; + signPriority = mkOption { + type = types.int; + default = 6; + description = "Priority to use for signs"; + }; + signcolumn = mkOption { + type = types.bool; + default = true; + description = '' + Enable/disable symbols in the sign column. + + When enabled the highlights defined in `signs.*.hl` and symbols defined + in `signs.*.text` are used. + ''; + }; + numhl = mkEnableOption '' + Enable/disable line number highlights. + + When enabled the highlights defined in `signs.*.numhl` are used. If + the highlight group does not exist, then it is automatically defined + and linked to the corresponding highlight group in `signs.*.hl`. + ''; + linehl = mkEnableOption '' + Enable/disable line highlights. + + When enabled the highlights defined in `signs.*.linehl` are used. If + the highlight group does not exist, then it is automatically defined + and linked to the corresponding highlight group in `signs.*.hl`. + ''; + showDeleted = mkEnableOption '' + Show the old version of hunks inline in the buffer (via virtual lines). + + Note: Virtual lines currently use the highlight `GitSignsDeleteVirtLn`. + ''; + diffOpts = let + diffOptModule = { + options = { + algorithm = mkOption { + type = types.enum ["myers" "minimal" "patience" "histogram"]; + default = "myers"; + description = "Diff algorithm to use"; + }; + internal = mkOption { + type = types.bool; + default = false; + description = "Use Neovim's built in xdiff library for running diffs"; + }; + indentHeuristic = mkOption { + type = types.bool; + default = false; + description = "Use the indent heuristic for the internal diff library."; + }; + vertical = mkOption { + type = types.bool; + default = true; + description = "Start diff mode with vertical splits"; + }; + linematch = mkOption { + type = types.nullOr types.int; + default = null; + description = '' + Enable second-stage diff on hunks to align lines. + Requires `internal=true`. + ''; + }; + }; + }; + in + mkOption { + type = types.nullOr (types.submodule diffOptModule); + default = null; + description = "Diff options. If set to null they are derived from the vim diffopt"; + }; + base = mkOption { + type = types.nullOr types.str; + default = null; + description = "The object/revision to diff against. Default to 'index'"; + }; + countChars = mkOption { + type = types.attrsOf types.str; + default = { + "1" = "1"; + "2" = "2"; + "3" = "3"; + "4" = "4"; + "5" = "5"; + "6" = "6"; + "7" = "7"; + "8" = "8"; + "9" = "9"; + "+" = ">"; + }; + description = '' + The count characters used when `signs.*.show_count` is enabled. The + `+` entry is used as a fallback. With the default, any count outside + of 1-9 uses the `>` character in the sign. + + Possible use cases for this field: + • to specify unicode characters for the counts instead of 1-9. + • to define characters to be used for counts greater than 9. + ''; + }; + statusFormatter = mkOption { + type = luaFunction; + default = { + function = '' + function(status) + local added, changed, removed = status.added, status.changed, status.removed + local status_txt = {} + if added and added > 0 then table.insert(status_txt, '+'..added ) end + if changed and changed > 0 then table.insert(status_txt, '~'..changed) end + if removed and removed > 0 then table.insert(status_txt, '-'..removed) end + return table.concat(status_txt, ' ') + end + ''; + }; + description = "Function used to format `b:gitsigns_status`"; + }; + maxFileLength = mkOption { + type = types.int; + default = 40000; + description = "Max file length (in lines) to attach to"; + }; + previewConfig = mkOption { + type = types.attrsOf types.anything; + default = { + border = "single"; + style = "minimal"; + relative = "cursor"; + row = 0; + col = 1; + }; + description = '' + Option overrides for the Gitsigns preview window. + Table is passed directly to `nvim_open_win`. + ''; + }; + attachToUntracked = mkOption { + type = types.bool; + default = true; + description = "Attach to untracked files."; + }; + updateDebounce = mkOption { + type = types.number; + default = 100; + description = "Debounce time for updates (in milliseconds)."; + }; + currentLineBlame = mkEnableOption '' + Adds an unobtrusive and customisable blame annotation at the end of the current line. + ''; + currentLineBlameOpts = { + virtText = mkOption { + type = types.bool; + default = true; + description = "Whether to show a virtual text blame annotation"; + }; + virtTextPos = mkOption { + type = types.enum ["eol" "overlay" "right_align"]; + default = "eol"; + description = "Blame annotation position"; + }; + delay = mkOption { + type = types.int; + default = 1000; + description = "Sets the delay (in milliseconds) before blame virtual text is displayed"; + }; + ignoreWhitespace = mkEnableOption "Ignore whitespace when running blame"; + virtTextPriority = mkOption { + type = types.int; + default = 100; + description = "Priority of virtual text"; + }; + }; + currentLineBlameFormatter = { + normal = mkOption { + type = types.either types.str luaFunction; + default = " , - "; + description = '' + String or function used to format the virtual text of + |gitsigns-config-current_line_blame|. + + See |gitsigns-config-current_line_blame_formatter| for more details. + ''; + }; + + nonCommitted = mkOption { + type = types.either types.str luaFunction; + default = " "; + description = '' + String or function used to format the virtual text of + |gitsigns-config-current_line_blame| for lines that aren't committed. + ''; + }; + }; + trouble = mkOption { + type = types.nullOr types.bool; + default = null; + description = '' + When using setqflist() or setloclist(), open Trouble instead of the quickfix/location list + window. + ''; + }; + yadm.enable = mkEnableOption "Enable YADM support"; + wordDiff = mkEnableOption '' + Highlight intra-line word differences in the buffer. + Requires `config.diff_opts.internal = true`. + ''; + debugMode = mkEnableOption '' + Enables debug logging and makes the following functions available: `dump_cache`, + `debug_messages`, `clear_debug`. + ''; + }; + + config = let + cfg = config.plugins.gitsigns; + in + mkIf cfg.enable { + extraPlugins = with pkgs.vimPlugins; [ + gitsigns-nvim + ]; + extraConfigLua = let + luaFnOrStrToObj = val: + if builtins.isString val + then val + else {__raw = val.function;}; + setupOptions = { + inherit (cfg) worktrees signcolumn numhl linehl trouble yadm; + signs = mapAttrs (_: signSetupOptions) cfg.signs; + on_attach = + if cfg.onAttach != null + then {__raw = cfg.onAttach.function;} + else null; + watch_gitdir = { + inherit (cfg.watchGitDir) enable interval; + follow_files = cfg.watchGitDir.followFiles; + }; + sign_priority = cfg.signPriority; + show_deleted = cfg.showDeleted; + diff_opts = + if cfg.diffOpts == null + then null + else { + inherit (cfg.diffOpts) algorithm internal vertical linematch; + indent_heuristic = cfg.diffOpts.indentHeuristic; + }; + count_chars = let + isStrInt = s: (builtins.match "[0-9]+" s) != null; + in { + __raw = + "{" + + (concatStringsSep "," ( + lib.mapAttrsToList ( + name: value: + if isStrInt name + then "[${name}] = ${helpers.toLuaObject value}" + else "[${helpers.toLuaObject name}] = ${helpers.toLuaObject value}" + ) + cfg.countChars + )) + + "}"; + }; + status_formatter = {__raw = cfg.statusFormatter.function;}; + max_file_length = cfg.maxFileLength; + preview_config = cfg.previewConfig; + attach_to_untracked = cfg.attachToUntracked; + update_debounce = cfg.updateDebounce; + current_line_blame = cfg.currentLineBlame; + current_line_blame_opts = let + cfgCl = cfg.currentLineBlameOpts; + in { + inherit (cfgCl) delay; + virt_text = cfgCl.virtText; + virt_text_pos = cfgCl.virtTextPos; + ignore_whitespace = cfgCl.ignoreWhitespace; + virt_text_priority = cfgCl.virtTextPriority; + }; + current_line_blame_formatter = luaFnOrStrToObj cfg.currentLineBlameFormatter.normal; + current_line_blame_formatter_nc = luaFnOrStrToObj cfg.currentLineBlameFormatter.nonCommitted; + word_diff = cfg.wordDiff; + debug_mode = cfg.debugMode; + }; + in '' + require('gitsigns').setup(${helpers.toLuaObject setupOptions}) + ''; + }; +}