Commit 9dcb6bdc6e1d

Vincent Demeester <vincent@sbr.pm>
2026-01-26 10:04:05
feat(home): add lazyworktree home-manager module
Add a home-manager module for lazyworktree (git worktree TUI) with: - YAML configuration file generation - Zsh/bash shell integration with worktree jumping - Repository aliases for quick access - Configuration with delta, nerd-fonts, emacsclient Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent ec3f702
Changed files (4)
home/common/dev/default.nix
@@ -3,6 +3,7 @@
   imports = [
     ./ai.nix
     ./go.nix
+    ./lazyworktree.nix
     ./lua.nix
     ./nix.nix
     ./python.nix
home/common/dev/lazyworktree.nix
@@ -0,0 +1,44 @@
+{ config, ... }:
+{
+  imports = [ ../../modules/lazyworktree.nix ];
+
+  programs.lazyworktree = {
+    enable = true;
+    enableZshIntegration = true;
+
+    settings = {
+      # UI settings
+      icon_set = "nerd-font-v3";
+      max_name_length = 95;
+      sort_mode = "switched";
+
+      # Use delta for diffs (matches git.nix configuration)
+      git_pager = "delta";
+      git_pager_args = "--paging=never --dark --line-numbers";
+
+      # Editor (matches your typical setup)
+      editor = "emacsclient -t";
+
+      # Workflow settings
+      auto_fetch_prs = false; # Enable manually when needed
+      auto_refresh = true;
+      refresh_interval = 10;
+
+      # Git settings (matches git.nix)
+      merge_method = "rebase";
+
+      # Session management
+      session_prefix = "wt-";
+
+      # Branch naming
+      issue_branch_name_template = "{{.IssueNumber}}-{{.Slug}}";
+      pr_branch_name_template = "pr-{{.PRNumber}}-{{.Slug}}";
+    };
+
+    # Quick aliases for commonly used repositories
+    aliases = {
+      # Home configuration
+      wh = "${config.home.homeDirectory}/src/home";
+    };
+  };
+}
home/modules/lazyworktree.nix
@@ -0,0 +1,242 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+let
+  inherit (lib) mkIf types;
+
+  cfg = config.programs.lazyworktree;
+
+  yamlFormat = pkgs.formats.yaml { };
+
+  configDir =
+    if pkgs.stdenv.hostPlatform.isDarwin && !config.xdg.enable then
+      "Library/Application Support"
+    else
+      config.xdg.configHome;
+
+  worktreeDir =
+    if cfg.worktreeDirectory != null then
+      cfg.worktreeDirectory
+    else
+      "${config.home.homeDirectory}/.local/share/worktrees";
+in
+{
+  options.programs.lazyworktree = {
+    enable = lib.mkEnableOption "lazyworktree, a TUI for managing Git worktrees";
+
+    package = lib.mkPackageOption pkgs "lazyworktree" { nullable = true; };
+
+    worktreeDirectory = lib.mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      example = "~/worktrees";
+      description = ''
+        Override the default worktree root directory.
+        Defaults to `~/.local/share/worktrees`.
+      '';
+    };
+
+    settings = lib.mkOption {
+      type = yamlFormat.type;
+      default = { };
+      defaultText = lib.literalExpression "{ }";
+      example = lib.literalExpression ''
+        {
+          theme = "nord";
+          icon_set = "nerd-font-v3";
+          editor = "nvim";
+          auto_fetch_prs = true;
+          git_pager = "delta";
+        }
+      '';
+      description = ''
+        Configuration written to
+        {file}`$XDG_CONFIG_HOME/lazyworktree/config.yaml`
+        on Linux or on Darwin if [](#opt-xdg.enable) is set, otherwise
+        {file}`~/Library/Application Support/lazyworktree/config.yaml`.
+        See
+        <https://github.com/chmouel/lazyworktree>
+        for supported values.
+      '';
+    };
+
+    enableZshIntegration = lib.mkEnableOption "zsh integration" // {
+      default = true;
+    };
+
+    enableBashIntegration = lib.mkEnableOption "bash integration";
+
+    shellWrapperName = lib.mkOption {
+      type = types.str;
+      default = "lwt";
+      example = "wt";
+      description = ''
+        Name of the shell wrapper function for jumping to worktrees.
+      '';
+    };
+
+    aliases = lib.mkOption {
+      type = types.attrsOf types.str;
+      default = { };
+      example = lib.literalExpression ''
+        {
+          pm = "~/git/myrepo";
+          home = "~/src/home";
+        }
+      '';
+      description = ''
+        Repository aliases for quick worktree jumping.
+        Each key becomes a shell function that jumps to that repository's worktrees.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    home.packages = mkIf (cfg.package != null) [ cfg.package ];
+
+    home.file."${configDir}/lazyworktree/config.yaml" = {
+      enable = cfg.settings != { };
+      source = yamlFormat.generate "lazyworktree-config" cfg.settings;
+    };
+
+    programs =
+      let
+        # Helper to extract repo slug from git remote
+        gitRepoSlugFunction = ''
+          _lwt_git_repo_slug() {
+              local dir="$1"
+              local url slug
+
+              url=$(cd "$dir" 2>/dev/null && git remote get-url origin 2>/dev/null) || return 1
+              slug=$(echo "$url" | sed -E 's#^.*[:/]([^/]+/[^/]+)(\.git)?$#\1#')
+              [[ -n "$slug" ]] || return 1
+              echo "$slug"
+          }
+        '';
+
+        # Shell function to jump to worktrees with directory change on exit
+        shellWrapperFunction = ''
+          ${cfg.shellWrapperName}() {
+              local dir="$1"; shift
+              local repo slug
+
+              if [[ -z "$dir" || ! -d "$dir" ]]; then
+                  echo >&2 "${cfg.shellWrapperName}: invalid directory: $dir"
+                  return 1
+              fi
+
+              slug=$(_lwt_git_repo_slug "$dir" 2>/dev/null)
+              repo="''${slug:-$(basename "$dir")}"
+
+              local wt_root="${worktreeDir}/$repo"
+
+              # Direct jump if worktree name provided
+              if [[ -n "$1" && -d "$wt_root/$1" ]]; then
+                  cd "$wt_root/$1" || return 1
+                  return
+              fi
+
+              cd "$dir" || return 1
+
+              local tmp selected
+              tmp=$(mktemp "''${TMPDIR:-/tmp}/lazyworktree.selection.XXXXXX") || return 1
+              lazyworktree --output-selection="$tmp"
+              local rc=$?
+              if [[ $rc -ne 0 ]]; then
+                  rm -f "$tmp"
+                  return $rc
+              fi
+
+              if [[ -s "$tmp" ]]; then
+                  selected=$(<"$tmp")
+                  [[ -n "$selected" && -d "$selected" ]] && cd "$selected" || true
+              fi
+              rm -f "$tmp"
+          }
+        '';
+
+        # Function to jump to last selected worktree
+        goLastFunction = ''
+          ${cfg.shellWrapperName}_go_last() {
+              local dir="$1"
+              local repo slug last_selected selected
+
+              if [[ -z "$dir" || ! -d "$dir" ]]; then
+                  echo >&2 "${cfg.shellWrapperName}_go_last: invalid directory: $dir"
+                  return 1
+              fi
+
+              slug=$(_lwt_git_repo_slug "$dir" 2>/dev/null)
+              repo="''${slug:-$(basename "$dir")}"
+
+              last_selected="${worktreeDir}/$repo/.last-selected"
+              if [[ -f "$last_selected" ]]; then
+                  selected=$(<"$last_selected")
+                  if [[ -n "$selected" && -d "$selected" ]]; then
+                      cd "$selected" || return 1
+                      return
+                  fi
+              fi
+
+              echo >&2 "No last selected worktree found"
+              return 1
+          }
+        '';
+
+        shellFunctions = ''
+          # lazyworktree shell integration
+          ${gitRepoSlugFunction}
+          ${shellWrapperFunction}
+          ${goLastFunction}
+        '';
+
+        # Zsh completion for alias functions (completes worktree names)
+        zshAliasCompletion = ''
+          _lwt_complete_worktrees() {
+              local dir="$1"
+              local repo slug wt_root
+
+              slug=$(_lwt_git_repo_slug "$dir" 2>/dev/null)
+              repo="''${slug:-$(basename "$dir")}"
+
+              wt_root="${worktreeDir}/$repo"
+              [[ -d "$wt_root" ]] || return
+
+              local -a dirs
+              dirs=(''${wt_root}/*(/:t))
+              _describe 'worktree' dirs
+          }
+        '';
+
+        aliasDefinitions = lib.concatStringsSep "\n" (
+          lib.mapAttrsToList (name: dir: ''
+            ${name}() { ${cfg.shellWrapperName} ${dir} "$@" }
+          '') cfg.aliases
+        );
+
+        zshAliasCompletions = lib.concatStringsSep "\n" (
+          lib.mapAttrsToList (name: dir: ''
+            _${name}() { _lwt_complete_worktrees ${dir} }
+            compdef _${name} ${name}
+          '') cfg.aliases
+        );
+
+      in
+      {
+        bash.initExtra = mkIf cfg.enableBashIntegration ''
+          ${shellFunctions}
+          ${aliasDefinitions}
+        '';
+
+        zsh.initContent = mkIf cfg.enableZshIntegration ''
+          ${shellFunctions}
+          ${zshAliasCompletion}
+          ${aliasDefinitions}
+          ${zshAliasCompletions}
+        '';
+      };
+  };
+}
systems/kyushu/home.nix
@@ -9,6 +9,7 @@ in
 {
   imports = [
     ../../home/common/dev/containers.nix
+    ../../home/common/dev/lazyworktree.nix
     ../../home/common/dev/tektoncd.nix
     ../../home/common/services/color-scheme-timer.nix
     ../../home/common/services/gcal-to-org.nix
@@ -51,7 +52,6 @@ in
     qmk_hid
 
     wip.voxtype
-    lazyworktree
 
     startpaac