nftable-migration
1;;; config-shells.el --- -*- lexical-binding: t; -*-
2;;; Commentary:
3;;; Shell scripting
4;;; Code:
5
6(defvar ISATUIN (executable-find "atuin")
7 "Whether atuin is available for shell/eshell history.")
8
9(use-package shell
10 :commands (shell)
11 :bind (("<f1>" . shell)
12 (:map shell-mode-map
13 ("<tab>" . completion-at-point)))
14 :config
15 (setq-default explicit-shell-file-name "zsh"
16 shell-file-name "zsh")
17 (unbind-key "C-c C-l" shell-mode-map)
18 (bind-key "C-c C-l" #'counsel-shell-history shell-mode-map))
19
20(defun run-in-compile (base-cmd &rest ARGS)
21 "Use `compile' to run the BASE-CMD and ARGS, from eshell."
22 (compile (concat base-cmd " " (apply #'concat ARGS))))
23
24;; TODO: understand and rework eshell completion
25(use-package eshell
26 :commands (eshell eshell-here)
27 :bind* ("C-x m t" . eshell-here)
28 :config
29 (defun eshell/make (&rest ARGS)
30 "Shortcut to more easily run builds in a compile buffer"
31 (cond ((or (file-exists-p "Makefile")
32 (file-exists-p "makefile"))
33 (run-in-compile "make" ARGS))
34 ((file-exists-p "build.zig")
35 (run-in-compile "zig build"))
36 (t "No supported build system found.")))
37 (defun eshell-here ()
38 "Open EShell in the directory associated with the current buffer's file.
39The EShell is renamed to match that directory to make multiple windows easier."
40 (interactive)
41 (let* ((parent (if (buffer-file-name)
42 (file-name-directory (buffer-file-name))
43 default-directory))
44 (name (car (last (split-string parent "/" t)))))
45 (eshell "new")
46 (rename-buffer (concat "*eshell: " name "*"))))
47
48 ;; Handy aliases
49 (defalias 'ff 'find-file)
50 (defalias 'emacs 'find-file)
51 (defalias 'e 'find-file)
52 (defalias 'ec 'find-file)
53 (defalias 'd 'dired)
54
55 (defun eshell/gs (&rest args)
56 (magit-status (pop args) nil)
57 (eshell/echo)) ; The echo command suppresses output
58
59 (defun eshell/cdg ()
60 "Change directory to the project's root."
61 (eshell/cd (locate-dominating-file default-directory ".git")))
62
63 (defun eshell/extract (file)
64 "One universal command to extract FILE (for bz2, gz, rar, etc.)"
65 (eshell-command-result (format "%s %s" (cond ((string-match-p ".*\.tar.bz2" file)
66 "tar xzf")
67 ((string-match-p ".*\.tar.gz" file)
68 "tar xzf")
69 ((string-match-p ".*\.bz2" file)
70 "bunzip2")
71 ((string-match-p ".*\.rar" file)
72 "unrar x")
73 ((string-match-p ".*\.gz" file)
74 "gunzip")
75 ((string-match-p ".*\.tar" file)
76 "tar xf")
77 ((string-match-p ".*\.tbz2" file)
78 "tar xjf")
79 ((string-match-p ".*\.tgz" file)
80 "tar xzf")
81 ((string-match-p ".*\.zip" file)
82 "unzip")
83 ((string-match-p ".*\.jar" file)
84 "unzip")
85 ((string-match-p ".*\.Z" file)
86 "uncompress")
87 (t
88 (error "Don't know how to extract %s" file)))
89 file)))
90
91 ;; From https://karthinks.com/software/jumping-directories-in-eshell/
92 (defun eshell/j (&optional regexp)
93 "Navigate to a previously visited directory in eshell, or to
94any directory proferred by `consult-dir'."
95 (let ((eshell-dirs (delete-dups
96 (mapcar 'abbreviate-file-name
97 (ring-elements eshell-last-dir-ring)))))
98 (cond
99 ((and (not regexp) (featurep 'consult-dir))
100 (let* ((consult-dir--source-eshell `(:name "Eshell"
101 :narrow ?e
102 :category file
103 :face consult-file
104 :items ,eshell-dirs))
105 (consult-dir-sources (cons consult-dir--source-eshell
106 consult-dir-sources)))
107 (eshell/cd (substring-no-properties
108 (consult-dir--pick "Switch directory: ")))))
109 (t (eshell/cd (if regexp (eshell-find-previous-directory regexp)
110 (completing-read "cd: " eshell-dirs)))))))
111
112 (add-hook
113 'eshell-mode-hook
114 (lambda ()
115 (let ((ls (if (executable-find "exa") "exa" "ls")))
116 (eshell/alias "ls" (concat ls " $*"))
117 (eshell/alias "ll" (concat ls " -l $*"))
118 (eshell/alias "l" (concat ls " -lah $*")))
119 (eshell-smart-initialize)
120 (eshell-dirs-initialize)
121 (bind-keys :map eshell-mode-map
122 ("C-c C-l" . counsel-esh-history)
123 ([remap eshell-pcomplete] . completion-at-point)
124 )))
125
126 ;; Use system su/sudo
127 (with-eval-after-load "em-unix"
128 '(progn
129 (unintern 'eshell/su nil)
130 (unintern 'eshell/sudo nil)))
131
132 (add-hook 'eshell-mode-hook #'with-editor-export-editor))
133
134(use-package eshell-atuin
135 :when ISATUIN
136 :after eshell
137 ;; :bind* ( :map eshell-mode-map
138 ;; ([remap eshell-previous-matching-input] . eshell-atuin-history))
139 :bind (("C-r" . eshell-atuin-history)
140 ([remap eshell-list-history] . eshell-atuin-history))
141 :config
142 (eshell-atuin-mode)
143 (setopt eshell-atuin-filter-mode 'global
144 eshell-atuin-search-fields '(time duration command directory host)
145 eshell-atuin-search-options '() ;; default --exit 0 ignores all the one imported… which is a shame
146 eshell-atuin-history-format "%-80c %-40i %>10t %h"))
147
148(use-package em-prompt
149 :after eshell
150 :config
151 (defun vde/eshell-quit-or-delete-char (arg)
152 "Use C-d to either delete forward char or exit EShell."
153 (interactive "p")
154 (if (and (eolp) (looking-back eshell-prompt-regexp nil nil))
155 (progn
156 (eshell-life-is-too-much))
157 (delete-char arg)))
158
159 (add-hook 'eshell-mode-hook
160 (lambda ()
161 (bind-key "C-d"
162 #'vde/eshell-quit-or-delete-char eshell-mode-map))))
163
164(use-package esh-mode
165 :disabled
166 :after eshell
167 :bind (:map eshell-mode-map
168 ("<tab>" . vde/esh-mode-completion-at-point))
169 :config
170 (setq-default eshell-scroll-to-bottom-on-input 'all)
171 (defun vde/esh-mode-completion-at-point ()
172 "Same as `completion-at-point' except for some commands."
173 (interactive)
174 ;; unbinding pcomplete/make gives a chance to `bash-completion'
175 ;; to complete make rules. Bash-completion is indeed more
176 ;; powerfull than `pcomplete-make'.
177 (cl-letf (((symbol-function 'pcomplete/make) nil))
178 (completion-at-point))))
179
180(use-package em-smart
181 :after eshell)
182(use-package em-dirs
183 :after eshell)
184
185(use-package em-cmpl
186 :after eshell
187 :hook (eshell-mode . eshell-cmpl-initialize)
188 :config
189 (defun my/eshell-bash-completion ()
190 (let ((bash-completion-nospace t))
191 (while (pcomplete-here
192 (nth 2 (bash-completion-dynamic-complete-nocomint
193 (save-excursion (eshell-bol) (point))
194 (point)))))))
195 (when (require 'bash-completion nil t)
196 (setq eshell-default-completion-function #'my/eshell-bash-completion))
197
198 (add-to-list 'eshell-command-completions-alist
199 '("gunzip" "gz\\'"))
200 (add-to-list 'eshell-command-completions-alist
201 '("tar" "\\(\\.tar|\\.tgz\\|\\.tar\\.gz\\)\\'")))
202
203(use-package em-hist
204 :after eshell
205 :config (setq eshell-hist-ignoredups t))
206
207(use-package em-tramp
208 :after eshell)
209
210(use-package em-term
211 :after eshell
212 :config
213 (add-to-list 'eshell-visual-commands "ssh")
214 (add-to-list 'eshell-visual-commands "htop")
215 (add-to-list 'eshell-visual-commands "top")
216 (add-to-list 'eshell-visual-commands "tail")
217 (add-to-list 'eshell-visual-commands "npm")
218 (add-to-list 'eshell-visual-commands "ncdu"))
219
220(use-package em-banner
221 :after eshell
222 :config
223 (setq eshell-banner-message "
224 Welcome to the Emacs
225
226 _/ _/ _/
227 _/_/ _/_/_/ _/_/_/ _/_/ _/ _/
228 _/_/_/_/ _/_/ _/ _/ _/_/_/_/ _/ _/
229 _/ _/_/ _/ _/ _/ _/ _/
230 _/_/_/ _/_/_/ _/ _/ _/_/_/ _/ _/
231
232"))
233
234(use-package eshell-prompt-extras
235 :after eshell
236 :custom
237 (eshell-highlight-prompt nil)
238 (eshell-prompt-function 'vde-theme-lambda)
239 :config
240 (setq epe-path-style 'fish
241 epe-fish-path-max-len 20)
242 (defun vde-kubernetes-current-context ()
243 "Return the current context"
244 (if (not (string-empty-p (getenv "KUBECONFIG")))
245 (epe-trim-newline (shell-command-to-string (concat
246 "env KUBECONFIG="
247 (getenv "KUBECONFIG")
248 " kubectl config current-context")))
249 (epe-trim-newline (shell-command-to-string "kubectl config current-context"))))
250 (defun vde-kubernetes-p ()
251 "If you have kubectl install and a config set,
252using either KUBECONFIG or ~/.kube/config"
253 (and (eshell-search-path "kubectl")
254 (not (string-empty-p (vde-kubernetes-current-context)))
255 (not (string-match-p "error: current-context is not set" (vde-kubernetes-current-context)))))
256 ;; From epe-theme-lambda
257 (defun vde-theme-lambda ()
258 "A eshell-prompt lambda theme."
259 (setq eshell-prompt-regexp "^[^#\nλ]*[#λ] ")
260 (concat
261 (when (epe-remote-p)
262 (epe-colorize-with-face
263 (concat (epe-remote-user) "@" (epe-remote-host) " ")
264 'epe-remote-face))
265 (when (and epe-show-python-info (bound-and-true-p venv-current-name))
266 (epe-colorize-with-face (concat "(" venv-current-name ") ") 'epe-venv-face))
267 (let ((f (cond ((eq epe-path-style 'fish) 'epe-fish-path)
268 ((eq epe-path-style 'single) 'epe-abbrev-dir-name)
269 ((eq epe-path-style 'full) 'abbreviate-file-name))))
270 (epe-colorize-with-face (funcall f (eshell/pwd)) 'epe-dir-face))
271 (when (epe-git-p)
272 (concat
273 (epe-colorize-with-face ":" 'epe-dir-face)
274 (epe-colorize-with-face
275 (concat (epe-git-branch)
276 (epe-git-dirty)
277 (epe-git-untracked)
278 (let ((unpushed (epe-git-unpushed-number)))
279 (unless (= unpushed 0)
280 (concat ":" (number-to-string unpushed)))))
281 'epe-git-face)))
282 (when (vde-kubernetes-p)
283 (concat (epe-colorize-with-face " (" 'epe-dir-face)
284 (epe-colorize-with-face (vde-kubernetes-current-context) 'epe-dir-face)
285 (epe-colorize-with-face ")" 'epe-dir-face)))
286 (epe-colorize-with-face " λ" 'epe-symbol-face)
287 (epe-colorize-with-face (if (= (user-uid) 0) "#" "") 'epe-sudo-symbol-face)
288 " ")))
289
290(use-package eat
291 :commands (eat)
292 :init (setq eat-kill-buffer-on-exit t
293 eat-enable-yank-to-terminal t)
294 :hook ((eshell-mode . eat-eshell-mode)
295 (eshell-mode . eat-eshell-visual-command-mode)))
296
297(use-package xterm-color
298 :after eshell
299 :init
300 ;; (setq comint-output-filter-functions
301 ;; (remove 'ansi-color-process-output comint-output-filter-functions))
302 (add-hook 'shell-mode-hook
303 (lambda ()
304 ;; Disable font-locking in this buffer to improve performance
305 (font-lock-mode -1)
306 ;; Prevent font-locking from being re-enabled in this buffer
307 (make-local-variable 'font-lock-function)
308 (setq font-lock-function (lambda (_) nil))
309 (add-hook 'comint-preoutput-filter-functions 'xterm-color-filter nil t)))
310 (add-hook 'eshell-before-prompt-hook
311 (lambda ()
312 (setenv "TERM" "xterm-256color")
313 (setq xterm-color-preserve-properties t)))
314 (add-to-list 'eshell-preoutput-filter-functions 'xterm-color-filter)
315 (setq eshell-output-filter-functions (remove 'eshell-handle-ansi-color eshell-output-filter-functions))
316 (setq compilation-environment '("TERM=xterm-256color")))
317
318(use-package vterm
319 :commands (vterm vde/vterm-toggle)
320 :bind (("C-c t v" . vde/vterm-toggle)
321 ("C-c t r" . vde/run-in-vterm))
322 :custom
323 (vterm-kill-buffer-on-exit t)
324 (vterm-max-scrollback 100000)
325 (vterm-tramp-shells '(("scp" "/usr/bin/env zsh")
326 ("ssh" "/usr/bin/env zsh")
327 ("sshx" "/usr/bin/env zsh")
328 ("sshfs" "/usr/bin/env zsh")
329 ("docker" "/bin/sh")))
330 :config
331 (defun vde/vterm-tramp-get-method-parameter (method param)
332 "Return the method parameter PARAM.
333If the `tramp-methods' entry does not exist, return NIL."
334 (let ((entry (assoc param (assoc method tramp-methods))))
335 (when entry (cadr entry))))
336 (add-hook 'vterm-set-title-functions 'vterm--rename-buffer-as-title)
337 ;; TODO: hook into projectile-run-vterm instead
338 ;; Also, look into vterm-toggle way of doing things.. I thing it is trying to be too smart about it..
339 ;; I prefer an easy projectile integration (or projects integration)
340 (defun vde/vterm ()
341 ""
342 (interactive)
343 (let* ((dir (expand-file-name default-directory))
344 cd-cmd cur-host vterm-dir vterm-host cur-user cur-port remote-p cur-method login-cmd)
345 (if (ignore-errors (file-remote-p dir))
346 (with-parsed-tramp-file-name dir nil
347 (setq remote-p t)
348 (setq cur-host host)
349 (setq cur-method (tramp-find-method method user cur-host))
350 (setq cur-user (or (tramp-find-user cur-method user cur-host) ""))
351 (setq cur-port (or port ""))
352 (setq dir localname))
353 (setq cur-host (system-name)))
354 (setq login-cmd (vde/vterm-tramp-get-method-parameter cur-method 'tramp-login-program))
355 (setq cd-cmd (concat " cd " (shell-quote-argument dir)))
356 (setq shell-buffer (format "vterm %s %s" cur-host dir))
357 (if (buffer-live-p shell-buffer)
358 (switch-to-buffer shell-buffer)
359 (progn
360 (message (format "buffer '%s' doesn't exists" shell-buffer))
361 (vterm shell-buffer)
362 (with-current-buffer shell-buffer
363 (message (format "%s" remote-p))
364 (when remote-p
365 (let* ((method (if (string-equal login-cmd "ssh") "ssh" cur-method))
366 (login-opts (vde/vterm-tramp-get-method-parameter method 'tramp-login-args))
367 (login-shell (vde/vterm-tramp-get-method-parameter method 'tramp-remote-shell))
368 (login-shell-args (tramp-get-sh-extra-args login-shell))
369 ;; (vterm-toggle-tramp-get-method-parameter cur-method 'tramp-remote-shell)
370 (spec (format-spec-make
371 ?h cur-host ?u cur-user ?p cur-port ?c ""
372 ?l (concat login-shell " " login-shell-args)))
373 (cmd
374 (concat login-cmd " "
375 (mapconcat
376 (lambda (x)
377 (setq x (mapcar (lambda (y) (format-spec y spec)) x))
378 (unless (member "" x) (string-join x " ")))
379 login-opts " "))))
380 (vterm-send-string cmd)
381 (vterm-send-return)))
382 (vterm-send-string cd-cmd)
383 (vterm-send-return))))))
384 (defun vde/vterm-toggle ()
385 "Toggle between the main vterm buffer and the current buffer.
386If you are in a vterm buffer, switch the window configuration
387back to your code buffers. Otherwise, create at least one vterm
388buffer if it doesn't exist already, and switch to it. On every
389toggle, the current window configuration is saved in a register."
390 (interactive)
391 (if (eq major-mode 'vterm-mode)
392 (jump-to-register ?W)
393 ;; Save current window config and jump to shell
394 (window-configuration-to-register ?W)
395 (condition-case nil
396 (jump-to-register ?Z)
397 (error
398 (vterm)
399 (when (= (length (window-list)) 2)
400 (other-window 1)
401 (vterm 1)
402 (other-window 1))))
403 (window-configuration-to-register ?Z)))
404 (buffer-name)
405 (defun vde/run-in-vterm ()
406 (interactive)
407 (with-current-buffer "vterm"
408 (vterm-send-string (read-string "Command: "))
409 (vterm-send-C-j))))
410
411(use-package multi-vterm
412 :commands (multi-vterm multi-vterm-projectile multi-vterm-dedicated-toggle)
413 :bind (("C-c t t" . multi-vterm-dedicated-toggle)
414 ("C-c t p" . multi-vterm-prev)
415 ("C-c t n" . multi-vterm-next)
416 ("C-c t s" . multi-vterm)))
417;; for fish in ansi-term
418(add-hook 'term-mode-hook 'toggle-truncate-lines)
419
420(use-package tramp
421 :defer t
422 :config
423 (setq-default tramp-use-ssh-controlmaster-options nil ; Don't override SSH config.
424 tramp-default-method "ssh") ; ssh is faster than scp and supports ports.
425 (add-to-list 'tramp-remote-path "/run/current-system/sw/bin")
426 (add-to-list 'tramp-remote-path "/etc/profiles/per-user/root/bin/")
427 (add-to-list 'tramp-remote-path "/etc/profiles/per-user/vincent/bin/")
428 (add-to-list 'tramp-remote-path "~/.nix-profile/bin")
429 (add-to-list 'tramp-remote-path "~/bin")
430 (add-to-list 'tramp-remote-path 'tramp-own-remote-path))
431
432(defun generic-term-init ()
433 (visual-line-mode -1)
434 (setq-local global-hl-line-mode nil)
435 (setq-local scroll-margin 0))
436
437(add-hook 'term-mode-hook #'generic-term-init)
438(add-hook 'shell-mode-hook #'generic-term-init)
439(add-hook 'eshell-mode-hook #'generic-term-init)
440
441(provide 'config-shells)
442;;; config-shells.el ends here