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