This repository has no description
11 kB
335 lines
1{ self, ... }:
2{
3 perSystem =
4 { pkgs, ... }:
5 let
6 zshrc = ''
7 # Source latest environment to pick up changes after nixos-rebuild
8 # (bypasses the __NIXOS_SET_ENVIRONMENT_DONE guard in /etc/zshenv)
9 . /etc/set-environment
10
11 autoload -U compinit && compinit
12
13 # Setup custom interactive shell init stuff.
14 # Bind gpg-agent to this TTY if gpg commands are used.
15 export GPG_TTY=$(tty)
16
17 # show how long a command takes
18 ZSH_COMMAND_TIME_MIN_SECONDS=3
19 ZSH_COMMAND_TIME_EXCLUDE=(vim)
20 ZSH_COMMAND_TIME_MSG="took: %s sec"
21 source ${
22 pkgs.fetchFromGitHub {
23 owner = "popstas";
24 repo = "zsh-command-time";
25 rev = "803d26eef526bff1494d1a584e46a6e08d25d918";
26 hash = "sha256-ndHVFcz+XmUW0zwFq7pBXygdRKyPLjDZNmTelhd5bv8=";
27 }
28 }/command-time.plugin.zsh
29
30 unsetopt nomatch # no matches found urls
31 setopt autocd extendedglob
32 bindkey -e
33
34 #C-x C-e open line in editor
35 autoload -z edit-command-line
36 zle -N edit-command-line
37 bindkey "^X^E" edit-command-line
38
39 #fzf inclusion
40 source ${pkgs.fzf}/share/fzf/completion.zsh
41 source ${pkgs.fzf}/share/fzf/key-bindings.zsh
42
43 # atuin distributed shell history
44 if type -p atuin >/dev/null; then
45 export ATUIN_CONFIG_DIR=${pkgs.writeTextDir "/config.toml" ''
46 auto_sync = true
47 update_check = false
48 sync_address = "http://green.r:8888"
49 sync_frequency = 0
50 style = "compact"
51 ''};
52 export ATUIN_NOBIND="true" # disable all keybdinings of atuin
53 eval "$(${pkgs.atuin}/bin/atuin init zsh)" # TODO make this optional?
54 bindkey '^r' _atuin_search_widget # bind ctrl+r to atuin
55 # use zsh only session history
56 fc -p
57 fi
58
59 #completion magic
60 autoload -Uz compinit
61 compinit
62 zstyle ':completion:*' menu select
63
64 #enable automatic rehashing of $PATH
65 zstyle ':completion:*' rehash true
66
67 # Lazy load completions for commands based on their installation path
68 _lazy_completion_loader() {
69 local cmd="$words[1]"
70 local basename_cmd="$cmd"
71 local cmd_path=""
72
73 [[ -n "$ZSH_COMP_DEBUG" ]] && echo "DEBUG: _lazy_completion_loader called for: $cmd" >&2
74
75 # Get command path and basename
76 if [[ "$cmd" == /* ]] && [[ -x "$cmd" ]]; then
77 cmd_path="$cmd"
78 basename_cmd="$(basename "$cmd")"
79 elif command -v "$cmd" >/dev/null 2>&1; then
80 cmd_path="$(which "$cmd" 2>/dev/null)"
81 basename_cmd="$(basename "$cmd_path")"
82 fi
83
84 [[ -n "$ZSH_COMP_DEBUG" ]] && echo "DEBUG: cmd_path=$cmd_path, basename_cmd=$basename_cmd" >&2
85
86 # Find and load completion file
87 local current="$cmd_path"
88 while [[ -n "$current" ]]; do
89 local share_dir="$(dirname "$current")/../share"
90 for subdir in zsh/site-functions zsh/vendor-completions; do
91 local dir="$share_dir/$subdir"
92 if [[ -f "$dir/_$basename_cmd" ]]; then
93 [[ -n "$ZSH_COMP_DEBUG" ]] && echo "DEBUG: found completion: $dir/_$basename_cmd" >&2
94 [[ ! " ''${fpath[@]} " =~ " $dir " ]] && fpath=("$dir" $fpath)
95 source "$dir/_$basename_cmd"
96 [[ -n "$ZSH_COMP_DEBUG" ]] && echo "DEBUG: after sourcing, _comps[$basename_cmd]=''${_comps[$basename_cmd]:-not set}" >&2
97 break 2
98 fi
99 done
100 # Follow absolute symlinks only
101 [[ -L "$current" ]] || break
102 current="$(readlink "$current" 2>/dev/null)"
103 [[ "$current" == /* ]] || break
104 done
105
106 # Register completion for the original command if needed
107 if [[ -n "''${_comps[$basename_cmd]:-}" ]]; then
108 if [[ "$cmd" != "$basename_cmd" ]]; then
109 _comps[$cmd]="''${_comps[$basename_cmd]}"
110 [[ -n "$ZSH_COMP_DEBUG" ]] && echo "DEBUG: registered ''${_comps[$basename_cmd]} for $cmd" >&2
111 fi
112 fi
113
114 # Remove lazy loader only if it's still set
115 if [[ "''${_comps[$cmd]:-}" == "_lazy_completion_loader" ]]; then
116 compdef -d "$cmd" 2>/dev/null
117 fi
118
119 # Run the appropriate completion
120 local comp="''${_comps[$cmd]:-}"
121 [[ -z "$comp" ]] && comp="''${_comps[$basename_cmd]:-_normal}"
122 [[ -n "$ZSH_COMP_DEBUG" ]] && echo "DEBUG: using completion: $comp" >&2
123 $comp "$@"
124 }
125
126 # Set as default completer for commands without completions
127 zstyle -e ':completion:*' completer '
128 if [[ -z $_comps[$words[1]] ]]; then
129 reply=(_lazy_completion_loader _complete)
130 else
131 reply=(_complete)
132 fi
133 '
134
135 # fancy mv which interactively gets the second argument if not given
136 function mv() {
137 if [[ "$#" -ne 1 ]] || [[ ! -e "$1" ]]; then
138 command mv -v "$@"
139 return
140 fi
141
142 newfilename="$1"
143 vared newfilename
144 command mv -v -- "$1" "$newfilename"
145 }
146
147 #emacs bindings
148 bindkey "[7~" beginning-of-line
149 bindkey "[8~" end-of-line
150 bindkey "Oc" emacs-forward-word
151 bindkey "Od" emacs-backward-word
152
153 # direnv integration
154 eval "$(${pkgs.direnv}/bin/direnv hook zsh)"
155
156 # Use nvim as MANPAGER if available and output is not piped
157 if command -v nvim >/dev/null 2>&1; then
158 export MANPAGER='sh -c "if [ -t 1 ]; then nvim +Man!; else cat; fi"'
159 fi
160
161 # This function is called whenever a command is not found.
162 command_not_found_handler() {
163 local p='${pkgs.nix-index}/etc/profile.d/command-not-found.sh'
164 if [ -x "$p" ] && [ -f '/nix/var/nix/profiles/per-user/root/channels/nixos/programs.sqlite' ]; then
165 # Run the helper program.
166 "$p" "$@"
167
168 # Retry the command if we just installed it.
169 if [ $? = 126 ]; then
170 "$@"
171 else
172 return 127
173 fi
174 else
175 # Indicate than there was an error so ZSH falls back to its default handler
176 echo "$1: command not found" >&2
177 return 127
178 fi
179 }
180
181 download_nixpkgs_cache_index () {
182 filename="index-$(uname -m | sed 's/^arm64$/aarch64/')-$(uname | tr A-Z a-z)"
183 mkdir -p ~/.cache/nix-index && cd ~/.cache/nix-index
184 # -N will only download a new version if there is an update.
185 wget -q -N https://github.com/nix-community/nix-index-database/releases/latest/download/$filename
186 ln -f $filename files
187 }
188
189 # Setup aliases.
190 alias -- grep='grep --color=auto'
191 alias -- ip='ip -color=auto'
192 alias -- l='ls -alh'
193 alias -- la='ls -la'
194 alias -- ll='ls -l'
195 alias -- ls='ls --color'
196 alias -- ns='nix-shell --command zsh'
197 alias -- nb='nix build --no-link --print-out-paths -L'
198 alias -- ne='nix eval --json'
199 alias -- nloc='nix-locate --top-level --whole-name'
200
201 # Setup prompt.
202 autoload -U promptinit
203 promptinit
204
205 p_error='%(?..%F{red}%?%f )'
206 t_error='%(?..%? )'
207
208 case $UID in
209 0)
210 p_username='%F{red}root%f'
211 t_username='root'
212 ;;
213 1337)
214 p_username=""
215 t_username=""
216 ;;
217 *)
218 p_username='%F{blue}%n%f'
219 t_username='%n'
220 ;;
221 esac
222
223 if test -n "$SSH_CLIENT"; then
224 p_hostname='@%F{magenta}%M%f '
225 t_hostname='@%M '
226 else
227 p_hostname=""
228 t_hostname=""
229 fi
230
231 #check if in nix shell
232 if test -n "$IN_NIX_SHELL"; then
233 p_nixshell='%F{green}[s]%f '
234 t_nixshell='[s] '
235 else
236 p_nixshell=""
237 t_nixshell=""
238 fi
239
240 PROMPT="$p_error$p_username$p_hostname$p_nixshell%~ "
241 TITLE="$t_error$t_username$t_hostname$t_nixshell%~"
242 case $TERM in
243 (*xterm* | *rxvt*)
244 function precmd {
245 PROMPT_EVALED=$(print -P "$TITLE")
246 echo -ne "\033]0;$$ $PROMPT_EVALED\007"
247 }
248 # This seems broken for some reason
249 # # This is seen while the shell waits for a command to complete.
250 # function preexec {
251 # PROMPT_EVALED=$(print -P "$TITLE")
252 # echo -ne "\033]0;$$ $PROMPT_EVALED $1\007"
253 # }
254 ;;
255 esac
256
257
258 # Auto-start tmux for new sessions
259 # Check if we're already in tmux (even through sudo)
260 if [[ "$TERM" != "linux" && -z "$TMUX" && "$TERM" != "dumb" ]]; then
261 # Check if we're inside tmux by looking for tmux in the process tree
262 in_tmux=false
263 pid=$$
264 while [[ $pid -ne 1 ]]; do
265 if ps -p $pid -o comm= 2>/dev/null | grep -q '^tmux'; then
266 in_tmux=true
267 break
268 fi
269 # Get parent PID
270 pid=$(ps -p $pid -o ppid= 2>/dev/null | tr -d ' ') || break
271 done
272
273 if [[ "$in_tmux" == "false" ]]; then
274 # Preserve SSH_AUTH_SOCK for tmux
275 if [[ -n "$SSH_AUTH_SOCK" ]]; then
276 tmux set-environment -g SSH_AUTH_SOCK "$SSH_AUTH_SOCK" 2>/dev/null
277 fi
278
279 # Generate a readable session name using name-generator
280 session_name=$(${
281 self.packages.${pkgs.system}.name-generator
282 }/bin/name-generator 2>/dev/null || echo "session-$$")
283 # Start tmux with the generated session name
284 exec tmux -u new-session -s "$session_name"
285 fi
286 fi
287
288 # Set tmux status bar color based on hostname for SSH sessions
289 if [[ -n "$TMUX" && "$__host__" != "$HOST" ]]; then
290 # Generate a color based on hostname hash
291 color=$(( $(echo -n "$HOST" | od -An -N4 -tu4) % 255 ))
292 tmux set -g status-bg "colour$color" 2>/dev/null || true
293 export __host__="$HOST"
294 fi
295
296 # Configure tmux for better mouse support
297 if [[ -n "$TMUX" ]]; then
298 # Enable mouse mode for scrolling
299 tmux set -g mouse on 2>/dev/null || true
300 fi
301
302 # Disable some features to support TRAMP.
303 if [ "$TERM" = dumb ]; then
304 unsetopt zle prompt_cr prompt_subst
305 unset RPS1 RPROMPT
306 PS1='$ '
307 PROMPT='$ '
308 fi
309 '';
310 in
311 {
312 packages.zsh =
313 (self.wrapLib.makeWrapper {
314 pkgs = pkgs;
315 package = pkgs.zsh;
316 runtimeInputs = with pkgs; [
317 gnugrep
318 tmux
319 fzf
320 direnv
321 atuin
322 nix-index
323 self.packages.${pkgs.system}.name-generator
324 ];
325 env = {
326 ZDOTDIR = pkgs.writeTextDir "/.zshrc" zshrc;
327 };
328 }).overrideAttrs
329 (old: {
330 passthru = (old.passthru or { }) // {
331 inherit zshrc;
332 };
333 });
334 };
335}