diff options
| author | cashmere <cashmere@autistici.org> | 2026-06-03 16:03:18 +0200 |
|---|---|---|
| committer | cashmere <cashmere@autistici.org> | 2026-06-03 16:03:18 +0200 |
| commit | 43e3efdd42e3cb23f3183c27974a7b9fd0f5a278 (patch) | |
| tree | 24c84f2e540936f2de106451f6d4fb00ad8abb0d /config.org | |
| parent | d6709fc3e5fc1559059895442e37fe0523666772 (diff) | |
| download | emacs-43e3efdd42e3cb23f3183c27974a7b9fd0f5a278.tar.gz | |
2026-06-03T16:03:23+02:00
Diffstat (limited to 'config.org')
| -rw-r--r-- | config.org | 1461 |
1 files changed, 917 insertions, 544 deletions
@@ -8,9 +8,9 @@ #+begin_src emacs-lisp ;;; config.el --- Emacs-Kick --- A feature rich Emacs config for (neo)vi(m)mers -*- lexical-binding: t; -*- -;; (setenv "LSP_USE_PLISTS" "true") +(setenv "LSP_USE_PLISTS" "true") ;; (setq debug-on-error t) -;; (setq gc-cons-threshold #x40000000) +(setq gc-cons-threshold #x40000000) ;; (setq gc-cons-threshold 50000000) ;; (setenv "LSP_USE_PLISTS" "true") ;; (setq lsp-use-plists t) @@ -18,10 +18,7 @@ (setq package-enable-at-startup nil) ;; (setq-default mode-line-format t) ;; disabled: boolean t is not valid for mode-line-format (setq default-frame-alist '((undecorated . t))) -(setq gc-cons-threshold 100000000) - -(add-hook 'emacs-startup-hook - (lambda () (setq gc-cons-threshold (* 50 1024 1024)))) +;; GC + file-name-handler-alist tuning lives in early-init.el now. (setq create-lockfiles nil) (setq make-backup-files nil) @@ -107,6 +104,7 @@ (inhibit-startup-message t) ;; Disable the startup message when Emacs launches. (initial-scratch-message "") ;; Clear the initial message in the *scratch* buffer. (ispell-dictionary "en_US") ;; Set the default dictionary for spell checking. + (text-mode-ispell-word-completion nil) ;; Stop ispell capf in text/org buffers (no wordlist on NixOS → corfu errors). (make-backup-files nil) ;; Disable creation of backup files. ;; (pixel-scroll-precision-mode t) ;; Enable precise pixel scrolling. (pixel-scroll-precision-use-momentum nil) ;; Disable momentum scrolling for pixel precision. @@ -308,6 +306,31 @@ * erc #+begin_src emacs-lisp +;; Always-available IRC entry points so `M-x run-irc` works from any daemon +;; (hub, work, standalone). Calling `erc-tls` autoloads ERC on demand. +(defun my/soju-password () + "Read the Soju bouncer password from the sops-nix secret." + (string-trim + (with-temp-buffer + (insert-file-contents + "/home/cashmere/.config/sops-nix/secrets/senpai_password") + (buffer-string)))) + +(defun run-irc () + "Connect to all IRC networks via Soju bouncer." + (interactive) + (let ((pw (my/soju-password))) + (erc-tls :server "bouncer.cashmere.rs" + :port 6699 + :nick "cashmere1337" + :user "cashmere/irc.libera.chat@emacs" + :password pw) + (erc-tls :server "bouncer.cashmere.rs" + :port 6699 + :nick "cashmere" + :user "cashmere/ergo@emacs" + :password pw))) + (when (my/load-for-hub-p) (use-package erc :ensure nil @@ -380,29 +403,6 @@ (erc-update-modules)) -(defun my/soju-password () - "Read the Soju bouncer password from the sops-nix secret." - (string-trim - (with-temp-buffer - (insert-file-contents - "/home/cashmere/.config/sops-nix/secrets/senpai_password") - (buffer-string)))) - -(defun run-irc () - "Connect to all IRC networks via Soju bouncer." - (interactive) - (let ((pw (my/soju-password))) - (erc-tls :server "bouncer.cashmere.rs" - :port 6699 - :nick "cashmere1337" - :user "cashmere/irc.libera.chat@emacs" - :password pw) - (erc-tls :server "bouncer.cashmere.rs" - :port 6699 - :nick "cashmere" - :user "cashmere/ergo@emacs" - :password pw))) - (defvar my/consult-source-erc (list :name "IRC" :narrow ?i @@ -459,28 +459,32 @@ Temporarily disables notifications during the fetch." (erc-cmd-LIST)) (defun persp-irc () - "Switch to the irc perspective and connect or show ERC buffers." + "Switch to (or create) the IRC frame and show ERC buffers." (interactive) - (let ((erc-bufs (cl-remove-if-not - (lambda (buf) - (with-current-buffer buf - (derived-mode-p 'erc-mode))) - (buffer-list)))) - (persp-switch "irc") + (let* ((erc-bufs (cl-remove-if-not + (lambda (buf) + (with-current-buffer buf + (derived-mode-p 'erc-mode))) + (buffer-list))) + (irc-frame (cl-find-if + (lambda (f) + (string= (frame-parameter f 'name) "irc")) + (frame-list)))) + (if irc-frame + (select-frame-set-input-focus irc-frame) + (select-frame-set-input-focus (make-frame '((name . "irc"))))) (if erc-bufs - (progn - (dolist (buf erc-bufs) - (persp-add-buffer buf)) - (switch-to-buffer (car erc-bufs))) + (switch-to-buffer (car erc-bufs)) (letrec ((hook-fn (lambda () - (when (bound-and-true-p persp-mode) - (let ((buf (current-buffer))) - (persp-add-buffer buf) - (switch-to-buffer buf))) + (switch-to-buffer (current-buffer)) (remove-hook 'erc-join-hook hook-fn)))) (add-hook 'erc-join-hook hook-fn)) (run-irc)))) +) ;; end (when (my/load-for-hub-p) ...) + +;; ERC keybindings and hooks — register from any daemon, since `run-irc` +;; autoloads ERC even outside the hub daemon. (with-eval-after-load 'erc (add-hook 'erc-mode-hook (lambda () @@ -488,11 +492,6 @@ Temporarily disables notifications during the fetch." (setq-local maximum-scroll-margin 0.0) (setq-local corfu-auto nil))) - (add-hook 'erc-join-hook - (lambda () - (when (bound-and-true-p persp-mode) - (persp-add-buffer (current-buffer))))) - ;; Prevent accidental ERC buffer kills from parting channels (add-hook 'kill-buffer-query-functions (lambda () @@ -504,23 +503,23 @@ Temporarily disables notifications during the fetch." (buffer-name))) t))) - (general-def 'normal erc-mode-map - "q" 'quit-window - "Q" 'my/erc-quit-all - "gb" 'my/erc-switch-channel - "gn" 'erc-track-switch-buffer - "gH" 'my/erc-fetch-history - "go" 'erc-channel-names - "gj" 'erc-join-channel - "gl" 'my/erc-list-channels - "gr" 'my/erc-reconnect - "RET" 'erc-send-current-line) + (with-eval-after-load 'general + (general-def 'normal erc-mode-map + "q" 'quit-window + "Q" 'my/erc-quit-all + "gb" 'my/erc-switch-channel + "gn" 'erc-track-switch-buffer + "gH" 'my/erc-fetch-history + "go" 'erc-channel-names + "gj" 'erc-join-channel + "gl" 'my/erc-list-channels + "gr" 'my/erc-reconnect + "RET" 'erc-send-current-line) - (general-def '(normal insert) erc-mode-map - "M-n" 'erc-track-switch-buffer - "C-k" 'erc-previous-command - "C-j" 'erc-next-command)) -) ;; end (when (my/load-for-hub-p) ...) + (general-def '(normal insert) erc-mode-map + "M-n" 'erc-track-switch-buffer + "C-k" 'erc-previous-command + "C-j" 'erc-next-command))) ;; Auto-start ERC in hub daemon on first frame (when (my/hub-p) @@ -531,7 +530,6 @@ Temporarily disables notifications during the fetch." (add-hook 'server-after-make-frame-hook #'my/hub-auto-start-erc)) #+end_src - * lambda-themes #+begin_src emacs-lisp @@ -815,6 +813,8 @@ I adjusted the config accordingly to my modes which I use on a frequent. (or (mode . eca-chat-mode))) + ("ERC" (or (mode . erc-mode))) + ("Emacs" (or (mode . emacs-lisp-mode) (name . "^\\*Help\\*$") @@ -859,9 +859,31 @@ Not necessary but an eyecandy to look at! ("C-c ^ p" . smerge-previous))) ;; Move to the previous conflict. #+end_src +* general (leader keys) + +Defined early so later =with-eval-after-load 'general= callbacks find =my-leader= already bound. + +#+begin_src emacs-lisp +(use-package general + :ensure (:wait t) + :demand t + :config + (general-evil-setup) + + (general-create-definer my-leader + :states '(normal visual) + :keymaps 'override + :prefix "SPC") + + (general-create-definer my-local-leader + :states '(normal visual) + :keymaps 'override + :prefix ",")) +#+end_src + * ai -** gptel +** COMMENT gptel #+begin_src emacs-lisp (use-package gptel @@ -946,7 +968,8 @@ Not necessary but an eyecandy to look at! ** eca /(editor code assistant)/ -#+begin_src emacs-lisp +# Temporarily disabled while testing claude-code-ide — remove :tangle no to re-enable. +#+begin_src emacs-lisp :tangle no (use-package eca :ensure (:host github :repo "editor-code-assistant/eca-emacs" :files ("*.el")) @@ -1035,6 +1058,118 @@ Not necessary but an eyecandy to look at! "C-c ." '(eca-transient-menu :wk "transient menu")))) #+end_src +** claude-code-ide + +Native [[https://github.com/manzaltu/claude-code-ide.el][Claude Code IDE]] integration via MCP. Requires the =claude= CLI in +PATH (already provided through the Nix profile) and uses =vterm= as the +terminal backend (=eat= is also installed as a fallback). + +#+begin_src emacs-lisp +(use-package claude-code-ide + :ensure (:host github :repo "manzaltu/claude-code-ide.el" + :files ("*.el")) + :defer t + :commands (claude-code-ide + claude-code-ide-menu + claude-code-ide-toggle + claude-code-ide-toggle-recent + claude-code-ide-continue + claude-code-ide-resume + claude-code-ide-stop + claude-code-ide-send-prompt + claude-code-ide-insert-at-mentioned + claude-code-ide-switch-to-buffer + claude-code-ide-list-sessions + claude-code-ide-check-status + claude-code-ide-show-debug + claude-code-ide-clear-debug) + :custom + ;; CLI + (claude-code-ide-cli-path "claude") + (claude-code-ide-cli-debug nil) + ;; Terminal backend — vterm is preferred, eat as fallback + (claude-code-ide-terminal-backend 'vterm) + (claude-code-ide-vterm-anti-flicker nil) + + ;; Window placement — mirror eca on the right side so the two AI assistants + ;; don't fight over the same slot + (claude-code-ide-use-side-window t) + (claude-code-ide-window-side 'right) + (claude-code-ide-window-width 100) + (claude-code-ide-focus-on-open t) + + ;; Ediff integration for code suggestions + (claude-code-ide-use-ide-diff t) + (claude-code-ide-focus-claude-after-ediff t) + (claude-code-ide-show-claude-window-in-ediff t) + (claude-code-ide-switch-tab-on-ediff t) + + ;; Diagnostics — auto-pick flycheck/flymake + (claude-code-ide-diagnostics-backend 'auto) + + ;; Allow Claude to evaluate Elisp in this Emacs (executeCode MCP tool) + (claude-code-ide-enable-execute-code t) + + :init + ;; Evil: open the Claude terminal in insert state so you can type immediately. + ;; Defined in :init so it applies even before claude-code-ide loads. + (with-eval-after-load 'evil + (evil-set-initial-state 'vterm-mode 'insert) + (evil-set-initial-state 'eat-mode 'insert)) + + (defun my/claude-code-ide--enter-insert (&rest _) + "Force evil insert state in the Claude terminal buffer after invocation." + (when (bound-and-true-p evil-mode) + (run-at-time + 0.05 nil + (lambda () + (when-let* ((buf (seq-find + (lambda (b) + (string-match-p "\\*claude-code" + (buffer-name b))) + (buffer-list)))) + (with-current-buffer buf + (when (get-buffer-window buf) + (select-window (get-buffer-window buf))) + (evil-insert-state))))))) + + (dolist (cmd '(claude-code-ide + claude-code-ide-toggle + claude-code-ide-toggle-recent + claude-code-ide-continue + claude-code-ide-resume + claude-code-ide-switch-to-buffer)) + (advice-add cmd :after #'my/claude-code-ide--enter-insert)) + + ;; Leader-key bindings (SPC a …) — defined pre-load so :commands autoloads fire. + (with-eval-after-load 'general + (my-leader + "a" '(:ignore t :wk "claude-code") + "aa" '(claude-code-ide :wk "start / toggle") + "am" '(claude-code-ide-menu :wk "transient menu") + "at" '(claude-code-ide-toggle :wk "toggle window") + "aT" '(claude-code-ide-toggle-recent :wk "toggle recent") + "ap" '(claude-code-ide-send-prompt :wk "send prompt") + "a@" '(claude-code-ide-insert-at-mentioned :wk "@mention region") + "ac" '(claude-code-ide-continue :wk "continue last") + "ar" '(claude-code-ide-resume :wk "resume session") + "as" '(claude-code-ide-switch-to-buffer :wk "switch buffer") + "al" '(claude-code-ide-list-sessions :wk "list sessions") + "aq" '(claude-code-ide-stop :wk "stop") + "a?" '(claude-code-ide-check-status :wk "check status") + "ad" '(claude-code-ide-show-debug :wk "show debug") + "aD" '(claude-code-ide-clear-debug :wk "clear debug")) + + (my-leader 'visual + "a@" '(claude-code-ide-insert-at-mentioned :wk "@mention region"))) + + (global-set-key (kbd "C-c C-'") #'claude-code-ide-menu) + + :config + ;; Runs only on first invocation — not at Emacs startup. + (claude-code-ide-emacs-tools-setup)) +#+end_src + * eldoc #+begin_src emacs-lisp @@ -1266,18 +1401,18 @@ automatically encrypted before save and can be decrypted in-place with #+begin_src emacs-lisp #+end_src -** org-modern (disabled in favor of render-org) +** COMMENT org-modern (disabled in favor of render-org) #+begin_src emacs-lisp -;; (use-package org-modern -;; :ensure t -;; :hook (org-mode . org-modern-mode) -;; :config -;; (setq org-modern-hide-stars t) -;; (setq org-modern-star 'replace) -;; (setq org-modern-fold-stars '(("◉" . "○"))) -;; (setq org-modern-replace-stars "◉○◉○◉") -;; (global-org-modern-mode)) +(use-package org-modern + :ensure t + :hook (org-mode . org-modern-mode) + :config + (setq org-modern-hide-stars t) + (setq org-modern-star 'replace) + (setq org-modern-fold-stars '(("◉" . "○"))) + (setq org-modern-replace-stars "◉○◉○◉") + (global-org-modern-mode)) #+end_src @@ -1306,21 +1441,28 @@ Display inline images in terminal Emacs via the Kitty graphics protocol. Only activates when running in a Kitty terminal (not in GUI Emacs). #+begin_src emacs-lisp -(use-package kitty-graphics - :ensure (:host github :repo "cashmeredev/kitty-graphics.el") - :if (and (not (display-graphic-p)) (getenv "KITTY_PID")) - :config +;; TEMP: load kitty-graphics from local dev clone (/home/cashmere/projects/ +;; kitty-graphics/) instead of the elpaca-managed build, so edits to the +;; source apply on Emacs restart without an `elpaca-rebuild' cycle. +;; Swap back to the `use-package' / `:ensure' form below once changes are +;; committed + pushed and elpaca has pulled the new commit. +(when (not (display-graphic-p)) + (add-to-list 'load-path "/home/cashmere/projects/kitty-graphics") + (require 'kitty-graphics) + (setq kitty-gfx-enable-video t) + (add-hook 'dired-mode-hook #'kitty-gfx-dired-auto-preview-mode) + (setq kitty-gfx-debug nil) (kitty-graphics-mode 1)) - ;; (with-eval-after-load 'dired - ;; (define-key dired-mode-map (kbd "P") #'kitty-gfx-dired-preview)) -;; (when (and (not (display-graphic-p)) -;; (or (getenv "KITTY_PID") ;; Kitty terminal -;; (string-prefix-p "foot" (or (getenv "TERM") "")))) ;; foot terminal (Sixel) -;; (load "~/projects/kitty-graphics/kitty-graphics.el") -;; (kitty-graphics-mode 1) -;; (with-eval-after-load 'dired -;; (define-key dired-mode-map (kbd "P") #'kitty-gfx-dired-preview))) +;; (use-package kitty-graphics +;; :ensure (:host github :repo "cashmeredev/kitty-graphics.el") +;; :if (not (display-graphic-p)) +;; :custom +;; (kitty-gfx-enable-video t) +;; :hook +;; (dired-mode . kitty-gfx-dired-auto-preview-mode) +;; :config +;; (kitty-graphics-mode 1)) #+end_src ** org-fancy-properties @@ -1412,6 +1554,44 @@ Old config :after org) #+end_src +** emacs-jupyter + +Jupyter kernels in org-babel. =#+begin_src jupyter-python :session foo= +gibt persistente Python REPL, inline plots, async, completion. + +Braucht system-side: =python3= mit =ipykernel jupyter-client pyzmq= + +=jupyter= CLI (siehe nix flake systems/md). + +=emacs-zmq= wird beim ersten Laden native gebaut. + +#+begin_src emacs-lisp +(use-package jupyter + :ensure t + :defer t + :commands (jupyter-run-repl jupyter-connect-repl) + :init + ;; lazy: kein :after org, kein emacs-zmq native build beim startup. + ;; jupyter laedt erst wenn ein jupyter-* src block ausgefuehrt wird. + (with-eval-after-load 'org + (setq org-babel-default-header-args:jupyter-python + '((:async . "yes") + (:session . "py") + (:kernel . "python3") + (:results . "raw drawer"))) + (advice-add 'org-babel-get-src-block-info :around + (lambda (orig &rest a) + (let ((info (apply orig a))) + (when (and info + (stringp (nth 0 info)) + (string-prefix-p "jupyter-" (nth 0 info)) + (not (featurep 'jupyter))) + (require 'jupyter) + (org-babel-do-load-languages + 'org-babel-load-languages + (append org-babel-load-languages '((jupyter . t))))) + info))))) +#+end_src + ** ob-just Org-babel support for justfile source blocks. Writes the block body to a @@ -1461,7 +1641,7 @@ Copies the resulting URL to the kill ring and clipboard." ((and (not file) (buffer-file-name) (file-name-extension (buffer-file-name))) (file-name-extension (buffer-file-name))))) (ext-flag (if ext (format " -- -ext %s" ext) "")) - (cmd (format "ssh pb@pb.cashmere.rs%s 2>/dev/null | sed 's/\\x1b\\[[0-9;]*m//g' | grep -oP 'https://\\S+/f/\\S+'" ext-flag)) + (cmd (format "ssh pb@pb.moneyspread.st%s 2>/dev/null | sed 's/\\x1b\\[[0-9;]*m//g' | grep -oP 'https://\\S+/f/\\S+'" ext-flag)) (url (string-trim (if (and file (file-exists-p file)) (shell-command-to-string (format "cat %s | %s" (shell-quote-argument file) cmd)) @@ -2384,28 +2564,28 @@ Denote fitself focused on the last versions to have a core for note taking and t :config (setopt denote-journal-title-format 'day-date-month-year)) -(defun my/denote-journal-template () - "Insert journal structure into a new journal entry. -Only inserts when the buffer has just the front-matter (fresh file)." - (when (and (buffer-file-name) - (string-match-p "journal" (buffer-file-name)) - (<= (count-lines (point-min) (point-max)) 6)) - (goto-char (point-max)) - (insert - "\n* Clockreport \n" - "\n#+BEGIN: clocktable :scope file :maxlevel 3 :emphasize nil :link t\n" - "#+END:\n" - "\n* Agenda \n" - "\n** NEXT Plan the day\n" - ":LOGBOOK:\n" - ":END:\n" - "\n* Braindump \n" - ":PROPERTIES:\n" - ":VISIBILITY: folded\n" - ":END:\n"))) +;; (defun my/denote-journal-template () +;; "Insert journal structure into a new journal entry. +;; Only inserts when the buffer has just the front-matter (fresh file)." +;; (when (and (buffer-file-name) +;; (string-match-p "journal" (buffer-file-name)) +;; (<= (count-lines (point-min) (point-max)) 6)) +;; (goto-char (point-max)) +;; (insert +;; "\n* Clockreport \n" +;; "\n#+BEGIN: clocktable :scope file :maxlevel 3 :emphasize nil :link t\n" +;; "#+END:\n" +;; "\n* Agenda \n" +;; "\n** NEXT Plan the day\n" +;; ":LOGBOOK:\n" +;; ":END:\n" +;; "\n* Braindump \n" +;; ":PROPERTIES:\n" +;; ":VISIBILITY: folded\n" +;; ":END:\n"))) -(advice-add 'denote-journal-new-or-existing-entry :after - (lambda (&rest _) (my/denote-journal-template))) +;; (advice-add 'denote-journal-new-or-existing-entry :after +;; (lambda (&rest _) (my/denote-journal-template))) #+end_src ** config @@ -2569,8 +2749,7 @@ Some goodies for better integration with org files. #+begin_src emacs-lisp (use-package denote-explore - :ensure t - ) + :ensure t) #+end_src ** denote-publish @@ -2723,24 +2902,24 @@ Timers that expired while Emacs was closed fire immediately." cand)))) #+end_src -** vertico-posframe +** COMMENT vertico-posframe #+begin_src emacs-lisp -;; (use-package vertico-posframe -;; :ensure t -;; :init -;; (setq vertico-posframe-parameters '((left-fringe . 12) ;; Fringes -;; (right-fringe . 12) -;; (accept-focus . t))) -;; ;; (undecorated . nil) ;; Rounded frame - -;; :config -;; (vertico-posframe-mode 1) -;; (setq vertico-posframe-width 96 ;; Narrow frame -;; vertico-posframe-height vertico-count ;; Default height -;; ;; Don't create posframe for these commands -;; vertico-multiform-commands '((consult-line (:not posframe)) -;; (consult-ripgrep (:not posframe))))) +(use-package vertico-posframe + :ensure t + :init + (setq vertico-posframe-parameters '((left-fringe . 12) ;; Fringes + (right-fringe . 12) + (accept-focus . t))) + ;; (undecorated . nil) ;; Rounded frame + + :config + (vertico-posframe-mode 1) + (setq vertico-posframe-width 96 ;; Narrow frame + vertico-posframe-height vertico-count ;; Default height + ;; Don't create posframe for these commands + vertico-multiform-commands '((consult-line (:not posframe)) + (consult-ripgrep (:not posframe))))) #+end_src ** corfu @@ -2758,18 +2937,18 @@ Timers that expired while Emacs was closed fire immediately." (corfu-history-mode) (corfu-popupinfo-mode)) -(defun my/suppress-corfu-terminal-warning (orig-fun type message &rest args) - (unless (and (eq type 'corfu) - (string-match-p "corfu-terminal.*not needed" message)) - (apply orig-fun type message args))) +;; (defun my/suppress-corfu-terminal-warning (orig-fun type message &rest args) +;; (unless (and (eq type 'corfu) +;; (string-match-p "corfu-terminal.*not needed" message)) +;; (apply orig-fun type message args))) -(advice-add 'display-warning :around #'my/suppress-corfu-terminal-warning) +;; (advice-add 'display-warning :around #'my/suppress-corfu-terminal-warning) -(use-package corfu-terminal - :ensure t - :after corfu - :init - (corfu-terminal-mode +1)) +;; (use-package corfu-terminal +;; :ensure t +;; :after corfu +;; :init +;; (corfu-terminal-mode +1)) #+end_src *** cape @@ -2851,7 +3030,16 @@ ensures that every available tree sitter is downloaded and set up for you. #+begin_src emacs-lisp (use-package embark :ensure t - :defer t) + :defer t + :bind + (("C-." . embark-act) + ("M-." . embark-dwim) + ("C-h B" . embark-bindings) + :map minibuffer-local-map + ("C-c C-e" . embark-export) + ("C-c C-o" . embark-collect)) + :init + (setq prefix-help-command #'embark-prefix-help-command)) #+end_src ** embark-consult @@ -2895,7 +3083,10 @@ completion-category-overrides '((file (styles partial-completion))))) ;; Customi (use-package eglot :ensure nil :config - (setq eglot-autoshutdown t) + ;; nil: LSP server stays alive when last managed buffer closes. + ;; Verhindert churn bei org src edit buffers (C-c ' macht buffer auf/zu + ;; → mit t neuer pyrefly process jedes Mal). + (setq eglot-autoshutdown nil) (setq eglot-send-changes-idle-time 0.1) (setq eglot-sync-connect nil) (setq eglot-connect-timeout 30) @@ -2964,6 +3155,12 @@ completion-category-overrides '((file (styles partial-completion))))) ;; Customi '(nix-ts-mode . ("nixd"))))) #+end_src +*** mark eglot-workspace-configuration safe + +#+begin_src emacs-lisp +(put 'eglot-workspace-configuration 'safe-local-variable #'listp) +#+end_src + ** python-ts-mode Because I have to and not want to. @@ -2973,10 +3170,15 @@ Because I have to and not want to. :ensure nil :mode ("\\.py\\'" . python-ts-mode) :hook (python-ts-mode . eglot-ensure) + :custom + (python-indent-offset 4) + (python-indent-guess-indent-offset-verbose nil) + (python-shell-interpreter "python3") + (python-shell-completion-native-enable nil) :config (with-eval-after-load 'eglot (add-to-list 'eglot-server-programs - '(python-ts-mode . ("rass" "--" "pyrefly" "lsp" "--" "ruff" "server"))))) + '((python-ts-mode python-mode) . ("rass" "--" "pyrefly" "lsp" "--" "ruff" "server"))))) #+end_src *** pyvenv @@ -3122,6 +3324,119 @@ Uses built-in tree-sitter modes for =.ts= and =.tsx= files. ;; '((nushell . t))))) #+end_src +** Xonsh + +Python-powered shell. Major mode for =.xsh= files. + +#+begin_src emacs-lisp +(use-package xonsh-mode + :ensure (:host github :repo "seanfarley/xonsh-mode") + :mode ("\\.xsh\\'" "\\.xonshrc\\'")) +#+end_src + +*** xonsh tree-sitter mode + +No dedicated xonsh grammar exists. Xonsh = python superset, so derive +=xonsh-ts-mode= from =python-ts-mode= — get python font-lock, indent, +imenu, navigation. Xonsh-specific syntax (=$()=, =@()=, =![]=, +=$VAR=) shows as python parse errors but stays readable. Fallback to +=xonsh-mode= when python grammar missing. + +#+begin_src emacs-lisp +(define-derived-mode xonsh-ts-mode python-ts-mode "Xonsh[ts]" + "Major mode for xonsh, derived from `python-ts-mode'. +Xonsh is a python superset; reuse python tree-sitter parser." + (setq-local comment-start "# " + comment-end "")) + +(with-eval-after-load 'treesit + (when (treesit-language-available-p 'python) + (add-to-list 'auto-mode-alist '("\\.xsh\\'" . xonsh-ts-mode)) + (add-to-list 'auto-mode-alist '("\\.xonshrc\\'" . xonsh-ts-mode)))) +#+end_src + +*** LSP in org src edit buffers + +=C-c '= opens indirect buffer without =buffer-file-name= → eglot +refuses to start. Hack: assign temp =.py= path so pyrefly/ruff +attach. Works for python AND xonsh src blocks (both route to +python-ts-mode LSP). + +#+begin_src emacs-lisp +(defun my/org-src-eglot-python () + "Give org src edit buffers a stable temp file name so eglot can attach. +Name derived from owning org file path → identical across re-opens of +the same src block file, so eglot reuses the existing connection +instead of churning." + (when (and (derived-mode-p 'python-ts-mode 'python-mode) + (not buffer-file-name) + (bound-and-true-p org-src-mode)) + (let* ((parent (or (buffer-file-name (org-src-source-buffer)) + (buffer-name (org-src-source-buffer)) + "scratch")) + (root (or (and (project-current) + (project-root (project-current))) + temporary-file-directory)) + (name (format ".ob-src-%s.py" + (substring (md5 parent) 0 8)))) + (setq-local buffer-file-name (expand-file-name name root)) + (set-buffer-modified-p nil) + (eglot-ensure)))) + +(add-hook 'org-src-mode-hook #'my/org-src-eglot-python) + +;; Kein cleanup-hook: temp file bleibt liegen. Sonst killed eglot +;; jedesmal die LSP connection wenn C-c ' das buffer schliesst → +;; neuer pyrefly process beim naechsten Edit. File ist harmlos (gitignored +;; via .gitignore pattern .ob-src-*.py). +#+end_src + +*** xonsh LSP via eglot + +No xonsh language server. Body mostly valid python → reuse python +LSP stack (=pyrefly= + =ruff server= via =rass=, same as +=python-ts-mode=). Subshell syntax flags as errors — known frickelig +trade-off. Hook both =xonsh-mode= and =xonsh-ts-mode=. + +#+begin_src emacs-lisp +(with-eval-after-load 'eglot + (add-to-list 'eglot-server-programs + '((xonsh-mode xonsh-ts-mode) + . ("rass" "--" "pyrefly" "lsp" "--" "ruff" "server")))) + +;; kein auto-eglot fuer xonsh — pyrefly.toml waere noetig. Manuell via +;; M-x my/pyrefly-here-xonsh (legt temp pyrefly.toml an + startet eglot). +;; (add-hook 'xonsh-mode-hook #'eglot-ensure) +;; (add-hook 'xonsh-ts-mode-hook #'eglot-ensure) +#+end_src + +*** xonsh org babel + +Custom =org-babel-execute:xonsh= — writes block body to temp =.xsh= file +and runs via the =xonsh= interpreter. Params: +- =:dir= working directory (default: =default-directory=) +- =:cmdline= extra args passed to =xonsh= + +#+begin_src emacs-lisp +(with-eval-after-load 'org + (defun org-babel-execute:xonsh (body params) + "Execute a xonsh source block. +BODY is the xonsh script. PARAMS may include :dir and :cmdline." + (let* ((dir (or (cdr (assq :dir params)) default-directory)) + (cmdline (or (cdr (assq :cmdline params)) "")) + (tmp (make-temp-file "ob-xonsh-" nil ".xsh"))) + (unwind-protect + (progn + (with-temp-file tmp (insert body)) + (let ((default-directory dir)) + (shell-command-to-string + (format "xonsh %s %s" + cmdline + (shell-quote-argument tmp))))) + (when (file-exists-p tmp) (delete-file tmp))))) + (add-to-list 'org-src-lang-modes '("xonsh" . xonsh))) +#+end_src + ** Go mode #+begin_src emacs-lisp @@ -3246,7 +3561,7 @@ Uses built-in tree-sitter modes for =.ts= and =.tsx= files. :ensure t :config (setq vterm-timer-delay 0.0001) - (setq vterm-shell "fish")) + (setq vterm-shell "xonsh")) #+end_src *** multi-vterm @@ -3391,7 +3706,18 @@ With prefix arg, always create a new terminal." (olivetti-margin-width 10) ; No side margins ;; (olivetti-shrink t) (olivetti-safe t) -) + :config + ;; Guard: vertico-posframe calls `text-scale-set' on *Minibuf-1*, which + ;; triggers olivetti's buffer-local text-scale-mode-hook. olivetti then + ;; calls window-width on the minibuffer window (nil) -> wrong-type-argument. + (add-hook 'minibuffer-setup-hook + (lambda () + (when (bound-and-true-p olivetti-mode) + (olivetti-mode -1)))) + (define-advice olivetti-set-window + (:around (orig win) my/skip-minibuffer) + (unless (window-minibuffer-p win) + (funcall orig win)))) #+end_src * shackle @@ -3435,24 +3761,24 @@ With prefix arg, always create a new terminal." ;; (setq-default visual-fill-column-center-text t))) #+end_src -* diff-hl +* COMMENT diff-hl #+begin_src emacs-lisp -;; (use-package diff-hl -;; :defer t -;; :ensure t -;; :hook -;; (find-file . (lambda () -;; (global-diff-hl-mode) ;; Enable Diff-HL mode for all files. -;; (diff-hl-flydiff-mode) ;; Automatically refresh diffs. -;; (diff-hl-margin-mode))) ;; Show diff indicators in the margin. -;; :custom -;; (diff-hl-side 'left) ;; Set the side for diff indicators. -;; (diff-hl-margin-symbols-alist '((insert . "┃") ;; Customize symbols for each change type. -;; (delete . "-") -;; (change . "┃") -;; (unknown . "┆") -;; (ignored . "i")))) +(use-package diff-hl + ;; :defer t + :ensure t + :hook + (find-file . (lambda () + (global-diff-hl-mode) ;; Enable Diff-HL mode for all files. + (diff-hl-flydiff-mode) ;; Automatically refresh diffs. + (diff-hl-margin-mode))) ;; Show diff indicators in the margin. + :custom + (diff-hl-side 'left) ;; Set the side for diff indicators. + (diff-hl-margin-symbols-alist '((insert . "┃") ;; Customize symbols for each change type. + (delete . "-") + (change . "┃") + (unknown . "┆") + (ignored . "i")))) #+end_src * hl-todo @@ -3494,6 +3820,36 @@ With prefix arg, always create a new terminal." (setq switch-to-prev-buffer-skip (lambda (window buffer bury-or-kill) (string-match-p "\\*magit" (buffer-name buffer)))) + +(defcustom my/auto-git-add-exclude-regexps + '("/\\.git/" "/node_modules/" "/\\.direnv/" "/straight/" "/elpaca/" + "\\.gpg\\'" "/\\.cache/" "/__pycache__/") + "Paths matching these regexps are skipped by `my/auto-git-add-new-file'." + :type '(repeat regexp) + :group 'my) + +(defun my/auto-git-add-new-file () + "Stage newly created file with intent-to-add so magit shows diffs. +Runs on `after-save-hook'. Only acts when the saved file is inside a +git project and currently untracked. Uses `git add -N' (intent-to-add) +so the working-tree diff stays visible until the user explicitly stages." + (let ((file buffer-file-name)) + (when (and file + (file-exists-p file) + (not (seq-some (lambda (re) (string-match-p re file)) + my/auto-git-add-exclude-regexps)) + (locate-dominating-file file ".git")) + (let ((default-directory (file-name-directory file))) + (when (and (zerop (call-process "git" nil nil nil "rev-parse" "--is-inside-work-tree")) + ;; ls-files --error-unmatch exits non-zero if untracked + (not (zerop (call-process "git" nil nil nil + "ls-files" "--error-unmatch" file)))) + (call-process "git" nil nil nil "add" "-N" file) + (when (fboundp 'magit-refresh) + (magit-refresh)) + (message "auto-staged (intent-to-add): %s" (file-name-nondirectory file))))))) + +(add-hook 'after-save-hook #'my/auto-git-add-new-file) #+end_src * indent-guide @@ -3510,26 +3866,6 @@ With prefix arg, always create a new terminal." * keybindings -** general - -#+begin_src emacs-lisp -(use-package general - :ensure (:wait t) - :demand t - :config - (general-evil-setup) - - (general-create-definer my-leader - :states '(normal visual) - :keymaps 'override - :prefix "SPC") - - (general-create-definer my-local-leader - :states '(normal visual) - :keymaps 'override - :prefix ",")) -#+end_src - ** evil-mode #+begin_src emacs-lisp @@ -3543,6 +3879,7 @@ With prefix arg, always create a new terminal." (setq evil-undo-system 'undo-tree) (setq evil-split-window-below t) (setq evil-vsplit-window-right t) + (setq evil-paste-from-register nil) :config (evil-mode 1) (define-key evil-normal-state-map "u" 'undo-tree-undo) @@ -3677,20 +4014,16 @@ With prefix arg, always create a new terminal." ** evil multiple cursor #+begin_src emacs-lisp -;; Paket für echte Multiple Cursors (use-package evil-mc :ensure t :after evil :config (global-evil-mc-mode 1) - ;; Hilfsfunktion: Prüfen, ob wir im Visual-Block-Mode sind - ;; Diese Funktion fängt den Fehler ab, falls Variablen nicht existieren. (defun my/evil-visual-block-p () (and (bound-and-true-p evil-visual-selection) (eq evil-visual-selection 'block))) - ;; Funktion für Insert am Anfang (I) (defun my/evil-mc-visual-block-insert () "Erstellt Cursor am Anfang des Blocks und wechselt in Insert-Mode." (interactive) @@ -3698,10 +4031,8 @@ With prefix arg, always create a new terminal." (progn (evil-mc-make-cursor-in-visual-selection-beg) (evil-insert 1)) - ;; Fallback: normales Verhalten, falls kein Block-Mode (call-interactively 'evil-insert))) - ;; Funktion für Append am Ende (A) (defun my/evil-mc-visual-block-append () "Erstellt Cursor am Ende des Blocks und wechselt in Insert-Mode." (interactive) @@ -3712,10 +4043,8 @@ With prefix arg, always create a new terminal." ;; Fallback: normales Verhalten (call-interactively 'evil-append))) - ;; Tastenbelegung im Visual Mode überschreiben (define-key evil-visual-state-map (kbd "I") 'my/evil-mc-visual-block-insert) (define-key evil-visual-state-map (kbd "A") 'my/evil-mc-visual-block-append)) - #+end_src * flash @@ -3827,49 +4156,6 @@ With prefix arg, always create a new terminal." (evil-org-agenda-set-keys)) #+end_src -* perspective - -Workspace isolation via =perspective.el=. Each perspective has its own -buffer list so switching buffers only shows relevant ones. Per-frame -- -every =emacsclient -c= frame gets its own set of perspectives. - -#+begin_src emacs-lisp -(use-package perspective - :ensure (:wait t) - :demand t - :custom - (persp-sort 'created) - (persp-suppress-no-prefix-key-warning t) - :init - (setq persp-mode-prefix-key (kbd "C-c M-p")) - :config - (persp-mode) - - ;; ---- consult integration -------------------------------------- - ;; Make SPC , show only buffers from the current perspective. - ;; SPC TAB B gives the unfiltered global list. - - (with-eval-after-load 'consult - (consult-customize consult--source-buffer :hidden t :default nil) - (add-to-list 'consult-buffer-sources persp-consult-source)) - - ;; ---- keybindings via general ---------------------------------- - - (my-leader - "TAB" '(:ignore t :wk "perspective") - "TAB TAB" '(persp-switch :wk "switch") - "TAB n" '(persp-next :wk "next") - "TAB p" '(persp-prev :wk "prev") - "TAB d" '(persp-kill :wk "delete") - "TAB r" '(persp-rename :wk "rename") - "TAB b" '(persp-switch-to-buffer :wk "buffer") - "TAB B" '(consult-buffer :wk "global buffer") - "TAB a" '(persp-add-buffer :wk "add buffer") - "TAB k" '(persp-remove-buffer :wk "remove buffer"))) -#+end_src - - - * nerd icons ** nerd icons dired @@ -3883,7 +4169,7 @@ every =emacsclient -c= frame gets its own set of perspectives. (dired-mode . nerd-icons-dired-mode)) #+end_src -** nerd icons bla +** nerd-icons-completion #+begin_src emacs-lisp (use-package nerd-icons-completion @@ -4317,27 +4603,14 @@ every =emacsclient -c= frame gets its own set of perspectives. (copy-sequence mode-line-misc-info))) (use-package maple-modeline + :disabled t :ensure (:host github :repo "honmaple/emacs-maple-modeline") - :hook (after-init . maple-modeline-mode) + ;; :hook (elpaca-after-init . maple-modeline-mode) :config (setq maple-modeline-separator 'nil) (setq maple-modeline-height 25) (setq maple-modeline-icon t) - ;; (defun maple-modeline-to-header-line () - ;; "Move maple-modeline from mode-line to header-line in all buffers." - ;; (unless (featurep 'ewm) - ;; (let ((fmt '(:eval (maple-modeline--init)))) - ;; (setq-default header-line-format fmt) - ;; (setq-default mode-line-format nil) - ;; (dolist (buf (buffer-list)) - ;; (with-current-buffer buf - ;; (setq header-line-format fmt) - ;; (setq mode-line-format nil))) - ;; (force-mode-line-update t)))) - - ;; (add-hook 'maple-modeline-mode-hook #'maple-modeline-to-header-line) - (maple-modeline-define-segment my-modeline-time :format (propertize (downcase (format-time-string " %I:%M %p ")) 'face 'bold)) @@ -4406,18 +4679,7 @@ every =emacsclient -c= frame gets its own set of perspectives. count position)) - (maple-modeline-define ewm-style - :left ((evil :left (bar :left "")) - buffer-info - flycheck - version-control) - :right (org-clock-task - tmr-timer - mu4e-mail - position)) - - (setq maple-modeline-style - (if (featurep 'ewm) 'ewm-style 'my-custom-style)) + (setq maple-modeline-style 'my-custom-style) :custom-face (header-line ((t (:inherit mode-line :box nil)))) @@ -4427,27 +4689,24 @@ every =emacsclient -c= frame gets its own set of perspectives. ** punch-line - #+begin_src emacs-lisp -;; (use-package punch-line -;; :ensure (:host github :repo "konrad1977/punch-line") -;; :config -;; (setq punch-line-left-separator " " -;; punch-line-right-separator " " -;; punch-show-git-info nil -;; punch-show-buffer-position t -;; punch-line-music-max-length 80 -;; punch-line-modal-divider-style 'block -;; punch-line-section-backgrounds 'auto -;; punch-show-weather-info t -;; punch-weather-longitude "10.41" -;; punch-weather-latitude "53.25" -;; punch-cpu-usage t -;; punch-show-processes-info t) -;; (punch-line-mode 1) -;; (setq-default header-line-format mode-line-format) -;; (setq-default mode-line-format nil)) - +(use-package punch-line + :ensure (:host github :repo "cashmeredev/punch-line") + :config + (setq punch-line-left-separator " " + punch-line-right-separator " " + punch-show-git-info nil + punch-show-buffer-position t + punch-show-column-info t + punch-line-music-max-length 80 + punch-line-modal-divider-style 'block + punch-line-section-backgrounds 'auto + punch-show-weather-info t + punch-weather-longitude "10.41" + punch-weather-latitude "53.25" + punch-cpu-usage t + punch-show-processes-info t) + (punch-line-mode 1)) #+end_src * soph @@ -4891,6 +5150,37 @@ every =emacsclient -c= frame gets its own set of perspectives. (load-theme 'noctalia t) #+end_src +** nano-theme + +Nicolas Rougier's N Λ N O theme — companion to =nano-modeline=. Built around six base faces (default, critical, salient, faded, popout, subtle) and a deliberately restrained palette. Light variant uses Material colors, dark variant uses Nord. + +We keep our own font (=MapleMono NF=) and skip nano's font setup (=nano-fonts-use=). =nano-window-divider-show= paints subtle dividers between windows so the minimal aesthetic still feels structured. + +#+begin_src emacs-lisp +;; (use-package nano-theme +;; :ensure (:host github :repo "rougier/nano-theme") +;; :init +;; (setq nano-fonts-use nil) ;; keep MapleMono NF, don't force Roboto +;; (setq nano-window-divider-show t) ;; subtle dividers between windows +;; :config +;; (load-theme 'nano-dark t) + +;; ;; Convenience: cycle between nano-light and nano-dark. +;; (defun cashmere/nano-toggle-theme () +;; "Toggle between `nano-light' and `nano-dark'." +;; (interactive) +;; (let ((dark-active (member 'nano-dark custom-enabled-themes))) +;; (mapc #'disable-theme custom-enabled-themes) +;; (load-theme (if dark-active 'nano-light 'nano-dark) t))) + +;; ;; Soften a few faces that nano leaves a bit too plain. +;; (with-eval-after-load 'org +;; (set-face-attribute 'org-document-title nil :height 1.4 :weight 'light) +;; (set-face-attribute 'org-level-1 nil :height 1.2 :weight 'medium) +;; (set-face-attribute 'org-level-2 nil :height 1.1 :weight 'medium) +;; (set-face-attribute 'org-level-3 nil :height 1.05 :weight 'medium))) +#+end_src + * dirvish #+begin_src emacs-lisp @@ -4903,7 +5193,8 @@ every =emacsclient -c= frame gets its own set of perspectives. (dirvish-quick-access-entries '(("h" "~/" "Home") ("d" "~/Downloads/" "Downloads") - ("p" "~/projects/" "Projects"))) + ;; ("p" "~/projects/" "Projects") + )) (dirvish-reuse-session 'open) (dirvish-attributes '(file-size)) @@ -4919,7 +5210,27 @@ every =emacsclient -c= frame gets its own set of perspectives. :config (setq dired-dwim-target t) (setq delete-by-moving-to-trash t) - (setq dired-mouse-drag-files t)) + (setq dired-mouse-drag-files t) + + ;; On TTY: drop dirvish's GUI-only media dispatchers (image/video/gif). + ;; They call `create-image' + `image-size' on cached thumbnails → + ;; "Window system frame should be used" on terminal frames. Bare + ;; symbols here — `dirvish-*-dp' names only appear after + ;; `dirvish--preview-dps-validate' interns them (dirvish.el:577). + ;; + ;; kitty-graphics ships its own dirvish dispatcher (`kitty-image', + ;; registered by `kitty-gfx--install-dirvish' on `kitty-graphics-mode' + ;; enable, see kitty-graphics.el:4040) that handles images + video + ;; thumbnails via the Kitty protocol. It runs before this filter + ;; thanks to being prepended; removing the GUI dispatchers is purely + ;; a safety net for cases where `kitty-image' returns nil (backend + ;; lost, mode disabled mid-flight). + (unless (display-graphic-p) + (with-eval-after-load 'dirvish + (setq dirvish-preview-dispatchers + (cl-remove-if (lambda (d) + (memq d '(image gif video video-mtn))) + dirvish-preview-dispatchers))))) #+end_src * tramp-hlo @@ -4990,12 +5301,12 @@ still require a restart since elpaca queues run at init time." ;; (my/global-olivetti-mode) #+end_src -* org-modern-indent (disabled in favor of render-org) +* COMMENT org-modern-indent (disabled in favor of render-org) #+begin_src emacs-lisp -;; (use-package org-modern-indent -;; :ensure (:host github :repo "jdtsmith/org-modern-indent") -;; :hook (org-mode . org-modern-indent-mode)) +(use-package org-modern-indent + :ensure (:host github :repo "jdtsmith/org-modern-indent") + :hook (org-mode . org-modern-indent-mode)) #+end_src * ibuffer hide @@ -5032,7 +5343,8 @@ still require a restart since elpaca queues run at init time." projectile-indexing-method 'alien projectile-sort-order 'recentf projectile-require-project-root nil - projectile-globally-ignored-buffers '("\\*magit.*"))) + projectile-globally-ignored-buffers '("\\*magit.*")) + (add-hook 'after-save-hook #'projectile-cache-current-file)) #+end_src ** consult projectile @@ -5046,15 +5358,36 @@ still require a restart since elpaca queues run at init time." (setq consult-project-function #'projectile-project-root)) #+end_src -* pass +** wgrep #+begin_src emacs-lisp -(use-package hidepw +(use-package wgrep :ensure t + :defer t :custom - (hidepw-hide-first-line t) - :hook (pass-view-mode . hidepw-mode)) + (wgrep-auto-save-buffer t) + (wgrep-change-readonly-file t)) + +(defun my/project-replace () + "Project-wide search via `consult-ripgrep'. +In the minibuffer press `C-c C-e' (embark-export) — wgrep mode +activates automatically. Edit, then `C-c C-c' commits, `C-c C-k' aborts." + (interactive) + (call-interactively #'consult-ripgrep)) + +(defun my/embark-export-auto-wgrep (&rest _) + "Enter wgrep mode automatically after `embark-export'." + (when (and (derived-mode-p 'grep-mode) + (fboundp 'wgrep-change-to-wgrep-mode)) + (wgrep-change-to-wgrep-mode))) + +(with-eval-after-load 'embark + (advice-add 'embark-export :after #'my/embark-export-auto-wgrep)) +#+end_src +* pass + +#+begin_src emacs-lisp (use-package pass :ensure t :defer t @@ -5064,25 +5397,6 @@ still require a restart since elpaca queues run at init time." '("\\*Pass.*\\*" (display-buffer-full-frame))) - (defvar-local pass--hidden-overlays nil - "Overlays used to hide the password line.") - - (defun pass-toggle-password () - "Toggle visibility of the first line (password) in a pass-view buffer." - (interactive) - (if pass--hidden-overlays - (progn - (mapc #'delete-overlay pass--hidden-overlays) - (setq pass--hidden-overlays nil)) - (save-excursion - (goto-char (point-min)) - (let ((ov (make-overlay (line-beginning-position) (line-end-position)))) - (overlay-put ov 'display (propertize "••••••••" 'face 'shadow)) - (overlay-put ov 'pass-hidden t) - (push ov pass--hidden-overlays))))) - - (add-hook 'pass-view-mode-hook #'pass-toggle-password) - (with-eval-after-load 'evil (general-def 'normal pass-mode-map "q" 'quit-window @@ -5102,7 +5416,7 @@ still require a restart since elpaca queues run at init time." (general-def 'normal pass-view-mode-map "q" 'quit-window - "t" 'pass-toggle-password + "t" 'pass-view-toggle-password "y" 'pass-copy "Y" 'pass-copy-field))) #+end_src @@ -5148,10 +5462,19 @@ still require a restart since elpaca queues run at init time." #+begin_src emacs-lisp (use-package pdf-tools :ensure nil + :if (display-graphic-p) :magic ("%PDF" . pdf-view-mode) :config (pdf-loader-install) :hook (pdf-view-mode . (lambda () (display-line-numbers-mode -1)))) + +;; Site-lisp may pre-register pdf-view-mode in magic-mode-alist on TUI emacs. +;; Strip those entries so opening a PDF in -nw falls back to doc-view/fundamental. +(unless (display-graphic-p) + (setq magic-mode-alist + (seq-remove (lambda (e) (eq (cdr e) 'pdf-view-mode)) magic-mode-alist)) + (setq auto-mode-alist + (seq-remove (lambda (e) (eq (cdr e) 'pdf-view-mode)) auto-mode-alist))) #+end_src * keymaps @@ -5203,7 +5526,7 @@ still require a restart since elpaca queues run at init time." "b" '(:ignore t :wk "buffer/bookmarks") "bb" '(consult-bookmark :wk "display current bookmarks") - "bI" '(ibuffer :wk "ibuffer") + "ba" '(ibuffer :wk "ibuffer") "bi" '(projectile-ibuffer :wk "ibuffer project") "bd" '(bookmark-delete :wk "delete bookmark") "bk" '(kill-current-buffer :wk "kill buffer") @@ -5216,7 +5539,7 @@ still require a restart since elpaca queues run at init time." "pb" '(consult-projectile-buffer :wk "buffers") "pk" '(projectile-kill-buffers :wk "kill buffers") "pd" '(projectile-remove-known-project :wk "delete project") - "pr" '(projectile-recentf :wk "recent files") + "pr" '(my/project-replace :wk "project replace (wgrep)") "pa" '(projectile-add-known-project :wk "add project") "pi" '(projectile-invalidate-cache :wk "invalidate cache") @@ -5243,6 +5566,7 @@ still require a restart since elpaca queues run at init time." "o" '(:ignore t :wk "open") "os" '(my/snip-upload :wk "snip buffer/region") "oS" '(my/snip-upload-file :wk "snip file") + "op" '(pass :wk "pass") "h" '(:ignore t :wk "help") "hm" '(describe-mode :wk "mode") @@ -5288,23 +5612,17 @@ still require a restart since elpaca queues run at init time." "t" '(:ignore t :wk "treesitter") "ts" '(flash-treesitter :wk "flash treesitter") - ;; ECA (AI assistant) - "a" '(:ignore t :wk "eca/ai") - "aa" '(eca :wk "open eca") - "ac" '(eca-chat-toggle-window :wk "toggle chat") - "an" '(eca-chat-new :wk "new chat") - "as" '(eca-chat-select :wk "select chat") - "ar" '(eca-rewrite :wk "rewrite region") - "am" '(eca-chat-select-model :wk "select model") - "av" '(eca-chat-select-variant :wk "select variant") - "ag" '(eca-chat-select-agent :wk "select agent") - "at" '(eca-transient-menu :wk "transient menu") - "ad" '(eca-mcp-details :wk "mcp details") - "aR" '(eca-restart :wk "restart eca") - "aS" '(eca-stop :wk "stop eca") - "aw" '(eca-workspaces :wk "workspaces") - "ae" '(eca-show-errors :wk "show errors") - "aC" '(eca-open-global-config :wk "global config")) + + "e" '(:ignore t :wk "eww/web") + "e e" '(eww :wk "eww browse / search") + "e n" '(my/eww-new-buffer :wk "new eww buffer") + "e b" '(eww-list-bookmarks :wk "bookmarks") + "e h" '(eww-list-histories :wk "history") + "e f" '(elfeed :wk "elfeed (rss)") + "e s" '(engine/search-duckduckgo :wk "search duckduckgo") + "e L" '(link-hint-open-link :wk "hint open link") + "e C" '(link-hint-copy-link :wk "hint copy link") + ) (defun my/flash-enabled-p () (and (not (derived-mode-p 'magit-mode 'dired-mode 'ibuffer-mode)) @@ -5486,6 +5804,30 @@ date) and the dired header are never skipped." "h" 'dirvish-hardlink) #+end_src +** eshell keymaps + +#+begin_src emacs-lisp +(defun my/eshell-clear () + (interactive) + (eshell/clear-scrollback)) + +(defun my/atuin-history () + (interactive) + (eshell-atuin-history)) + +(with-eval-after-load 'eshell + (add-hook 'eshell-mode-hook + (lambda () + (local-set-key (kbd "C-l") 'my/eshell-clear) + (local-set-key (kbd "C-r") 'my/atuin-history)))) + +(with-eval-after-load 'evil + (with-eval-after-load 'eshell + (evil-define-key '(normal insert) eshell-mode-map + (kbd "C-r") 'my/atuin-history + (kbd "C-l") 'my/eshell-clear))) +#+end_src + ** local leader #+begin_src emacs-lisp @@ -5498,41 +5840,9 @@ date) and the dired header are never skipped." "e" '(eshell :wk "eshell") "z" '(golden-ratio-mode :wk "zoom/golden ratio") "o" '(my/global-olivetti-mode :wk "center buffer") - "s" '(my-org-sidecar-left :wk "org sidecar") - - "g" '(:wk "gptel" :ignore) - - "gg" '(gptel :wk "open gptel") - "ga" '(gptel-add :wk "add buffer to gptel") - "gf" '(gptel-add-file :wk "add file to gptel") - "gA" '(gptel-abort :wk "abort response") - "gr" '(gptel-rewrite :wk "rewrite section") - "gm" '(gptel-menu :wk "open gptel menu")) + "s" '(my-org-sidecar-left :wk "org sidecar")) #+end_src -** emms-keybinds - -#+begin_src emacs-lisp -;; (my-leader -;; :keymaps 'override -;; "m" '(:ignore t :wk "music/emms") -;; "mm" '(emms-browser :wk "browser") -;; "mp" '(emms-playlist-mode-go :wk "playlist") -;; "ma" '(emms-add-directory-tree :wk "add directory") -;; "mf" '(emms-play-file :wk "play file") -;; "md" '(emms-play-directory :wk "play directory") -;; "ms" '(emms-start :wk "start/play") -;; "mS" '(emms-stop :wk "stop") -;; "mn" '(emms-next :wk "next track") -;; "mN" '(emms-previous :wk "previous track") -;; "mP" '(emms-pause :wk "pause") -;; "mr" '(emms-random :wk "random track") -;; "mc" '(emms-playlist-clear :wk "clear playlist") -;; "mC" '(emms-cache-set-from-mpd-all :wk "sync mpd cache") -;; "mu" '(emms-player-mpd-update-all :wk "update mpd db") -;; "mw" '(emms-playlist-save :wk "save playlist") -;; "ml" '(emms-playlist-load :wk "load playlist")) -#+end_src ** convenient keys @@ -5608,6 +5918,7 @@ date) and the dired header are never skipped." ((string< a-feed b-feed) t) ((string> a-feed b-feed) nil) (t (string< a-title b-title))))))) + (use-package elfeed-org :ensure t :after elfeed @@ -5616,6 +5927,34 @@ date) and the dired header are never skipped." :config (elfeed-org)) +(use-package elfeed-goodies + :ensure t + :after elfeed + :config + (elfeed-goodies/setup)) + +(use-package shrface + :ensure t + :after elfeed + :config + (shrface-basic) + (shrface-trial) + (shrface-default-keybindings) + (setq shrface-href-versatile t + shrface-bullets-bullet-list '("◉" "○" "✸" "✿"))) + +(with-eval-after-load 'elfeed + (require 'shrface) + (add-hook 'elfeed-show-mode-hook + (lambda () + (visual-line-mode 1) + (setq-local shr-width nil + shr-use-fonts t + shr-max-image-proportion 0.6 + shr-indentation 2 + line-spacing 0.2) + (shrface-mode 1)))) + (defun elfeed-export-from-index-to-csv (output-file) (interactive "FExport CSV nach: ") (require 'elfeed) @@ -5644,6 +5983,140 @@ date) and the dired header are never skipped." browse-url-generic-program "qutebrowser") #+end_src +* eww + +** core eww / shr / url settings + +#+begin_src emacs-lisp +(use-package eww + :ensure nil + :commands (eww eww-search-words) + :custom + (eww-search-prefix "https://duckduckgo.com/html/?q=") + (eww-auto-rename-buffer 'title) + (eww-browse-url-new-window-is-tab nil) + (eww-download-directory (expand-file-name "~/Downloads")) + (eww-bookmarks-directory (locate-user-emacs-file "eww-bookmarks/")) + (eww-restore-desktop t) + (eww-desktop-remove-duplicates t) + (eww-history-limit 150) + (eww-suggest-uris '(eww-links-at-point + thing-at-point-url-at-point)) + (eww-header-line-format "%t — %u") + (url-privacy-level '(email os emacs lastloc)) + (url-cookie-trusted-urls '()) + :config + (setq-default shr-inhibit-images nil + shr-use-fonts t + shr-use-colors t + shr-folding-mode t + shr-bullet "• " + shr-image-animate nil + shr-max-image-proportion 0.6 + shr-width nil + shr-cookie-policy nil) + (make-directory eww-bookmarks-directory t) + + (with-eval-after-load 'general + (my-local-leader + :keymaps 'eww-mode-map + "B" '(eww-add-bookmark :wk "add bookmark") + "r" '(eww-readable :wk "reader mode") + "i" '(my/eww-toggle-images :wk "toggle images") + "y" '(my/eww-copy-as-org :wk "copy page as org") + "d" '(eww-download :wk "download")))) +#+end_src + +** helper functions + +#+begin_src emacs-lisp +(defun my/eww-new-buffer (url) + "Open URL in a fresh eww buffer (keep current)." + (interactive (list (read-from-minibuffer "URL: "))) + (eww url 4)) + +(defun my/eww-toggle-images () + "Toggle image rendering and reload page." + (interactive) + (setq-local shr-inhibit-images (not shr-inhibit-images)) + (eww-reload) + (message "Images %s" (if shr-inhibit-images "off" "on"))) + +(defun my/eww-copy-as-org () + "Yank visible eww region/page as org-formatted text." + (interactive) + (require 'ol-eww) + (org-eww-copy-for-org-mode)) +#+end_src + +** shrface — org-like html rendering + +#+begin_src emacs-lisp +(use-package shrface + :ensure t + :hook (eww-after-render . shrface-mode) + :custom + (shrface-bullets-bullet-list '("◉" "○" "✸" "✿")) + (shrface-href-versatile t) + :config + (shrface-basic) + (shrface-trial)) +#+end_src + +** shr-tag-pre-highlight — syntax-highlighted code blocks + +#+begin_src emacs-lisp +(use-package shr-tag-pre-highlight + :ensure (:host github :repo "xuchunyang/shr-tag-pre-highlight.el") + :after shr + :config + (add-to-list 'shr-external-rendering-functions + '(pre . shr-tag-pre-highlight))) +#+end_src + +** ace-link — avy-style link jumping + +#+begin_src emacs-lisp +(use-package ace-link + :ensure t + :commands (ace-link-eww ace-link-info ace-link-help ace-link-org) + :config (ace-link-setup-default)) +#+end_src + +** link-hint — vimium-style hints + +#+begin_src emacs-lisp +(use-package link-hint + :ensure t + :commands (link-hint-open-link link-hint-copy-link)) +#+end_src + +** engine-mode — search engine shortcuts (C-x / …) + +#+begin_src emacs-lisp +(use-package engine-mode + :ensure t + :commands (engine/search-duckduckgo + engine/search-google + engine/search-github + engine/search-mdn + engine/search-wikipedia + engine/search-arch-wiki + engine/search-nixpkgs + engine/search-rustdocs) + :config + (engine-mode 1) + (setq engine/browser-function 'eww-browse-url) + (defengine duckduckgo "https://duckduckgo.com/html/?q=%s" :keybinding "d") + (defengine google "https://google.com/search?q=%s" :keybinding "g") + (defengine github "https://github.com/search?q=%s&type=code" :keybinding "h") + (defengine mdn "https://developer.mozilla.org/en-US/search?q=%s" :keybinding "m") + (defengine wikipedia "https://en.wikipedia.org/wiki/Special:Search?search=%s" :keybinding "w") + (defengine arch-wiki "https://wiki.archlinux.org/index.php?search=%s" :keybinding "a") + (defengine nixpkgs "https://search.nixos.org/packages?query=%s" :keybinding "n") + (defengine rustdocs "https://docs.rs/releases/search?query=%s" :keybinding "r")) +#+end_src + * mu4e #+begin_src emacs-lisp @@ -5765,23 +6238,6 @@ this package provides the ability to display rgb and hex colors. (setq denote-project-notes-identifier '(project stayem))) #+end_src -#+RESULTS: -: t - -* snippy - -#+begin_src emacs-lisp -;; (use-package snippy -;; :ensure (:host github -;; :repo "MiniApollo/snippy.git" -;; :branch "main") -;; :hook (after-init . global-snippy-minor-mode) -;; :custom -;; (snippy-global-languages '("global")) ;; Recomended -;; :config -;; (snippy-install-or-update-snippets)) ;; Autoupdate git repo -#+end_src - * emacs-everywhere #+begin_src emacs-lisp @@ -5800,153 +6256,6 @@ this package provides the ability to display rgb and hex colors. ;; (profiler-stop) #+end_src -* ewm - -#+begin_src emacs-lisp -(with-eval-after-load 'ewm - (require 'ewm-transient) - - ;; ── Bugfix: guard against nil output in pointer functions ── - ;; ewm-input--pointer-in-window-p crashes with "stringp, nil" when - ;; called on a frame without ewm-output (e.g. the initial daemon frame - ;; during startup when dashboard does switch-to-buffer). - (defun ewm-input--pointer-in-window-p (window) - "Return non-nil if pointer is inside WINDOW. Safe for non-EWM frames." - (let* ((frame (window-frame window)) - (output (frame-parameter frame 'ewm-output))) - (when output - (let* ((output-offset (ewm--get-output-offset output)) - (edges (window-inside-pixel-edges window)) - (left (+ (car output-offset) (nth 0 edges))) - (top (+ (cdr output-offset) (nth 1 edges))) - (right (+ (car output-offset) (nth 2 edges))) - (bottom (+ (cdr output-offset) (nth 3 edges))) - (pointer (ewm-get-pointer-location)) - (px (car pointer)) - (py (cdr pointer))) - (and (<= left px right) - (<= top py bottom)))))) - - (defun ewm-input--warp-pointer-to-window (window) - "Warp pointer to center of WINDOW. Safe for non-EWM frames." - (unless (minibufferp (window-buffer window)) - (let* ((frame (window-frame window)) - (output (frame-parameter frame 'ewm-output))) - (when (and output (not (ewm-input--pointer-in-window-p window))) - (let* ((output-offset (ewm--get-output-offset output)) - (edges (window-inside-pixel-edges window)) - (x (+ (car output-offset) (/ (+ (nth 0 edges) (nth 2 edges)) 2))) - (y (+ (cdr output-offset) (/ (+ (nth 1 edges) (nth 3 edges)) 2)))) - (ewm-warp-pointer (float x) (float y))))))) - - ;; ── Modeline: use minimal style under ewm ──── - (when (bound-and-true-p maple-modeline-mode) - (setq maple-modeline-style 'ewm-style) - (force-mode-line-update t)) - - ;; ── Settings ───────────────────────────────────────────── - (setq ewm-mouse-follows-focus t) - (setq ewm-input-config '((keyboard :repeat-delay 200 :repeat-rate 25))) - (setq ewm-idle '(300 . "swaylock -f -c 333333")) - - ;; Per-host output config - (pcase (system-name) - ("md" - (setq ewm-output-config - '(("DP-1" :x 0 :y 0 :refresh 144)))) - ("sportmacher" - (setq ewm-output-config - '(("DSI-1" :width 800 :height 1280 :transform 3 :scale 1.0))))) - - ;; ── Programm-Launcher ────────────────────────────────────── - (define-key ewm-mode-map (kbd "s-<return>") - (lambda () (interactive) (start-process "kitty" nil "kitty"))) - (define-key ewm-mode-map (kbd "M-s-7") - (lambda (command) - (interactive (list (read-shell-command "$ "))) - (start-process-shell-command command nil command))) - (define-key ewm-mode-map (kbd "s-SPC") - (lambda () (interactive) (start-process "vicinae" nil "vicinae" "toggle"))) - (define-key ewm-mode-map (kbd "s-b") - (lambda () (interactive) (start-process "helium" nil "helium-browser"))) - (define-key ewm-mode-map (kbd "s-p") - (lambda () (interactive) (start-process "vicinae" nil "vicinae" "vicinae://extensions/tinkerbells/pass/pass"))) - (define-key ewm-mode-map (kbd "s-v") - (lambda () (interactive) (start-process "vicinae" nil "vicinae" "vicinae://extensions/vicinae/clipboard/history"))) - (define-key ewm-mode-map (kbd "s-s") - (lambda () (interactive) (start-process "vicinae" nil "vicinae" "vicinae://extensions/vicinae/power"))) - (define-key ewm-mode-map (kbd "s-d") - (lambda () (interactive) (start-process "vicinae" nil "vicinae" "vicinae://extensions/cashmere/deepl-improve/deepl-improve"))) - (define-key ewm-mode-map (kbd "M-s") - (lambda () (interactive) (start-process "snipe" nil "snipe"))) - (define-key ewm-mode-map (kbd "s-<print>") - (lambda () (interactive) - (start-process-shell-command "screenshot" nil "grim -g \"$(slurp)\" - | wl-copy"))) - (define-key ewm-mode-map (kbd "<print>") - (lambda () (interactive) - (start-process-shell-command "screenshot-pb" nil - "grim -g \"$(slurp)\" - | ssh pb@pb.cashmere.rs -- -ext png 2>/dev/null | sed 's/\\x1b\\[[0-9;]*m//g' | grep -oP 'https://\\S+/f/\\S+' | sed 's/$/?r=1/' | wl-copy"))) - - ;; ── Window Management ──────────────────────────────────── - (define-key ewm-mode-map (kbd "s-h") #'windmove-left) - (define-key ewm-mode-map (kbd "s-j") #'windmove-down) - (define-key ewm-mode-map (kbd "s-k") #'windmove-up) - (define-key ewm-mode-map (kbd "s-l") #'windmove-right) - (define-key ewm-mode-map (kbd "s-q") #'kill-current-buffer) - (define-key ewm-mode-map (kbd "s-f") #'ewm-toggle-fullscreen) - (define-key ewm-mode-map (kbd "s-S-q") #'ewm-stop-module) - - ;; tab switching s-0..s-9 (s-0 = tab 10) - (dotimes (i 10) - (define-key ewm-mode-map - (kbd (format "s-%d" i)) - (let ((n i)) - (lambda () (interactive) (tab-bar-select-tab (if (= n 0) 10 n)))))) - - ;; ── Media Keys ─────────────────────────────────────────── - (define-key ewm-mode-map (kbd "<XF86AudioLowerVolume>") - (lambda () (interactive) (start-process "vol-" nil "pamixer" "-d" "5"))) - (define-key ewm-mode-map (kbd "<XF86AudioRaiseVolume>") - (lambda () (interactive) (start-process "vol+" nil "pamixer" "-i" "5"))) - (define-key ewm-mode-map (kbd "<XF86AudioMute>") - (lambda () (interactive) (start-process "mute" nil "pamixer" "--toggle-mute"))) - (define-key ewm-mode-map (kbd "<XF86AudioMicMute>") - (lambda () (interactive) - (start-process-shell-command "micmute" nil - "pactl list sources short | grep input | awk '{print $1}' | xargs -I{} pactl set-source-mute {} toggle"))) - (define-key ewm-mode-map (kbd "<XF86AudioPlay>") - (lambda () (interactive) (start-process "play" nil "playerctl" "play-pause"))) - (define-key ewm-mode-map (kbd "<XF86AudioMedia>") - (lambda () (interactive) (start-process "play" nil "playerctl" "play-pause"))) - (define-key ewm-mode-map (kbd "<XF86AudioNext>") - (lambda () (interactive) (start-process "next" nil "playerctl" "next"))) - (define-key ewm-mode-map (kbd "<XF86AudioPrev>") - (lambda () (interactive) (start-process "prev" nil "playerctl" "previous"))) - (define-key ewm-mode-map (kbd "<XF86MonBrightnessUp>") - (lambda () (interactive) (start-process "bri+" nil "brightnessctl" "set" "+5%"))) - (define-key ewm-mode-map (kbd "<XF86MonBrightnessDown>") - (lambda () (interactive) (start-process "bri-" nil "brightnessctl" "set" "5%-"))) - - ;; ── Leader bindings under SPC e ────────────────────────── - (my-leader - "e" '(:ignore t :wk "ewm") - "e a" '(ewm-launch-app :wk "launch app") - "e n" '(ewm-next-surface-buffer :wk "next surface") - "e p" '(ewm-prev-surface-buffer :wk "prev surface") - "e f" '(ewm-toggle-fullscreen :wk "fullscreen") - "e t" '(ewm-transient :wk "control panel") - "e s" '(ewm-screenshot :wk "screenshot") - "e i" '(ewm-show-state :wk "inspect state") - "e d" '(ewm-debug-mode :wk "debug") - "e l" '(ewm-lock-session :wk "lock") - "e T" '(:ignore t :wk "tab") - "e T n" '(tab-new :wk "new tab") - "e T c" '(tab-close :wk "close tab") - "e T l" '(tab-next :wk "next tab") - "e T h" '(tab-previous :wk "prev tab") - "e T r" '(tab-recent :wk "recent tab"))) -#+end_src - * arrow.el #+begin_src emacs-lisp @@ -5972,15 +6281,6 @@ this package provides the ability to display rgb and hex colors. (add-to-list 'global-colorful-modes 'helpful-mode)) #+end_src - -* plz - -#+begin_src emacs-lisp -(use-package plz - :ensure t) -#+end_src - - * gleam #+begin_src emacs-lisp @@ -5993,16 +6293,68 @@ this package provides the ability to display rgb and hex colors. '(gleam-ts-mode . ("gleam" "lsp")))) #+end_src -*** envrc +* envrc Automatic flake devshell activation. +Async activation: defer per-buffer =envrc-mode= via idle timer so direnv +=process-file= does not block buffer display. + #+begin_src emacs-lisp (use-package envrc :ensure t - :init (envrc-global-mode)) + :defer 1 + :init + (require 'notifications) + (defvar my/envrc-notified (make-hash-table :test 'equal) + "Directories already notified about, to avoid spam.") + (defun my/envrc-notify (dir status) + "Desktop notification for DIR with STATUS symbol. +Skip when no .envrc (STATUS `none'). Dedupe by .envrc root so +opening another file in same project does not re-notify." + (when (and dir (memq status '(on error))) + (let ((root (or (locate-dominating-file dir ".envrc") dir))) + (unless (gethash root my/envrc-notified) + (puthash root t my/envrc-notified) + (ignore-errors + (notifications-notify + :title "direnv" + :body (format "%s — %s" + (abbreviate-file-name root) + (pcase status + ('on "environment loaded") + ('error "load failed") + (_ (format "%s" status)))) + :app-name "emacs" + :urgency (if (eq status 'error) 'critical 'low) + :timeout 3000)) + (message "envrc: %s [%s]" root status))))) + (defun my/envrc-maybe-async () + "Enable `envrc-mode' asynchronously for current buffer." + (when (and (not (bound-and-true-p envrc-mode)) + buffer-file-name + (not (file-remote-p buffer-file-name))) + (let ((buf (current-buffer))) + (run-with-idle-timer + 0.2 nil + (lambda () + (when (buffer-live-p buf) + (with-current-buffer buf + (envrc-mode 1) + (let ((dir (and (boundp 'envrc--status) + (car-safe (bound-and-true-p envrc--process-env)) + default-directory)) + (status (bound-and-true-p envrc--status))) + (when status + (my/envrc-notify default-directory status)))))))))) + :hook ((find-file . my/envrc-maybe-async) + (dired-mode . my/envrc-maybe-async)) + :config + (when (bound-and-true-p envrc-global-mode) + (envrc-global-mode -1))) #+end_src -* bbj + +* COMMENT bbj #+begin_src emacs-lisp (use-package bbj @@ -6023,11 +6375,33 @@ Automatic flake devshell activation. (use-package tldr :ensure t) #+end_src -* holo-layer -Visual enhancement overlay for Emacs (cursor animation, window borders, etc). -Installed via Nix emacs package set — no elpaca needed. +* eshell + +#+begin_src emacs-lisp +(use-package eshell + :ensure nil + :config + (setq eshell-command-aliases-list + '(("nh-switch" "nh os switch --quiet $*")))) +#+end_src + +* atuin-eshell + +#+begin_src emacs-lisp +(use-package eshell-atuin + :ensure t + :after eshell + :config + (eshell-atuin-mode)) +#+end_src + +* elfetch +#+begin_src emacs-lisp +(use-package el-fetch + :ensure t) +#+end_src * provide init @@ -6035,4 +6409,3 @@ Installed via Nix emacs package set — no elpaca needed. (provide 'init) #+end_src - |
