#+title: config.org #+date: [2025-10-20 Mon 08:33] #+identifier: 20251020T083323 #+PROPERTY: header-args :tangle config.el #+STARTUP: content * early-init #+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") ;; (setq debug-on-error t) ;; (setenv "LSP_USE_PLISTS" "true") ;; (setq lsp-use-plists t) (setq pgtk-wait-for-event-timeout 0.001) (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))) ;; GC + file-name-handler-alist tuning lives in early-init.el now. (setq create-lockfiles nil) (setq make-backup-files nil) (setq backup-directory-alist `(("." . ,(expand-file-name "backup" user-emacs-directory)))) (setq tramp-backup-directory-alist backup-directory-alist) (setq backup-by-copying-when-linked t) (setq backup-by-copying t) ; Backup by copying rather renaming (setq delete-old-versions t) ; Delete excess backup versions silently (setq version-control t) ; Use version numbers for backup files (setq kept-new-versions 5) (setq kept-old-versions 5) #+end_src * daemon #+begin_src emacs-lisp ;;; Daemon detection — determines which daemon we're running as. ;;; Start Emacs with: emacs --fg-daemon=hub (persistent: IRC, mail, agenda) ;;; emacs --fg-daemon=work (restartable: code, projects) ;;; emacs (standalone, loads everything) (defvar my/daemon-name (let ((d (daemonp))) (cond ((stringp d) d) ; named daemon: "hub" or "work" (d "work") ; unnamed daemon (--daemon without name) → treat as work (t nil))) ; standalone emacs "Name of the current Emacs daemon, or nil for standalone.") (defun my/hub-p () "Return non-nil when running as the hub daemon." (equal my/daemon-name "hub")) (defun my/work-p () "Return non-nil when running as the work daemon." (equal my/daemon-name "work")) (defun my/standalone-p () "Return non-nil when running standalone (not as daemon)." (null my/daemon-name)) (defun my/load-for-hub-p () "Return non-nil if hub packages should be loaded (hub or standalone)." (or (my/hub-p) (my/standalone-p))) (defun my/load-for-work-p () "Return non-nil if work packages should be loaded (work or standalone)." (or (my/work-p) (my/standalone-p))) ;; Per-daemon state files to avoid conflicts between hub and work daemons (when my/daemon-name (let ((suffix (concat "-" my/daemon-name))) (setq recentf-save-file (expand-file-name (concat "recentf" suffix ".eld") user-emacs-directory)) (setq savehist-file (expand-file-name (concat "savehist" suffix ".el") user-emacs-directory)) (setq save-place-file (expand-file-name (concat "places" suffix ".eld") user-emacs-directory)) (setq bookmark-default-file (expand-file-name (concat "bookmarks" suffix ".eld") user-emacs-directory)) (setq projectile-known-projects-file (expand-file-name (concat "projectile-bookmarks" suffix ".eld") user-emacs-directory)) (setq org-clock-persist-file (expand-file-name (concat "org-clock-save" suffix ".el") user-emacs-directory)))) #+end_src * emacs #+begin_src emacs-lisp (use-package emacs :ensure nil :custom (column-number-mode t) (auto-save-default nil) ;; Disable automatic saving of buffers. (create-lockfiles nil) ;; Prevent the creation of lock files when editing. (delete-by-moving-to-trash t) ;; Move deleted files to the trash instead of permanently deleting them. (delete-selection-mode 1) ;; Enable replacing selected text with typed text. (display-line-numbers-type 'relative) ;; Use relative line numbering in programming modes. (global-auto-revert-non-file-buffers t) ;; Automatically refresh non-file buffers. (history-length 25) ;; Set the length of the command history. (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. (ring-bell-function 'ignore) ;; Disable the audible bell. (split-width-threshold 300) ;; Prevent automatic window splitting if the window width exceeds 300 pixels. (switch-to-buffer-obey-display-actions t) ;; Make buffer switching respect display actions. (tab-always-indent 'complete) ;; Make the TAB key complete text instead of just indenting. (tab-width 4) ;; Set the tab width to 4 spaces. (treesit-font-lock-level 4) ;; Use advanced font locking for Treesit mode. (truncate-lines t) ;; Enable line truncation to avoid wrapping long lines. (use-dialog-box nil) ;; Disable dialog boxes in favor of minibuffer prompts. (use-short-answers t) ;; Use short answers in prompts for quicker responses (y instead of yes) (warning-minimum-level :emergency) ;; Set the minimum level of warnings to display. :hook ;; Add hooks to enable specific features in certain modes. (prog-mode . display-line-numbers-mode) (org-mode . display-line-numbers-mode) (markdown-mode . display-line-numbers-mode) (text-mode . visual-line-mode) ;; Soft-wrap prose/text files at window edge. (conf-mode . visual-line-mode) ;; Soft-wrap .conf and similar config files. :config (add-to-list 'custom-theme-load-path user-emacs-directory) (setq custom-safe-themes t) ;; By default emacs gives you access to a lot of *special* buffers, while navigating with [b and ]b, ;; this might be confusing for newcomers. This settings make sure ]b and [b will always load a ;; file buffer. To see all buffers use SPC, b l, or b i. (defun skip-these-buffers (_window buffer _bury-or-kill) "Function for `switch-to-prev-buffer-skip'." (string-match "\\*[^*]+\\*" (buffer-name buffer))) (setq switch-to-prev-buffer-skip 'skip-these-buffers) (setq switch-to-prev-buffer-skip-regexp '("\\*[^*]+\\*" "^magit" "^\\*magit")) ;; Configure font settings based on the operating system. ;; Ok, this kickstart is meant to be used on the terminal, not on GUI. ;; But without this, I fear you could start Graphical Emacs and be sad :( ;; (set-face-attribute 'default nil :family "Maple Mono NF" :height 150) ;; (when (eq system-type 'darwin) ;; Check if the system is macOS. ;; (setq mac-command-modifier 'meta) ;; Set the Command key to act as the Meta key. ;; (set-face-attribute 'default nil :family "Fragment Mono" :height 130)) ;; Save manual customizations to a separate file instead of cluttering `init.el'. ;; You can M-x customize, M-x customize-group, or M-x customize-themes, etc. ;; The saves you do manually using the Emacs interface would overwrite this file. ;; The following makes sure those customizations are in a separate file. (setq custom-file (locate-user-emacs-file "custom-vars.el")) ;; Specify the custom file path. (load custom-file 'noerror 'nomessage) ;; Load the custom file quietly, ignoring errors. ;; Makes Emacs vertical divisor the symbol │ instead of |. (set-display-table-slot standard-display-table 'vertical-border (make-glyph-code ?│)) :init ;; Initialization settings that apply before the package is loaded. (tool-bar-mode -1) (menu-bar-mode -1) (scroll-bar-mode -1) (add-to-list 'default-frame-alist '(vertical-scroll-bars . nil)) (add-to-list 'default-frame-alist '(horizontal-scroll-bars . nil)) ;; (set-frame-parameter (selected-frame) 'alpha-background 80) ;; (add-to-list 'default-frame-alist '(alpha-background . 80)) (global-hl-line-mode 1) ;; Highlight the current line (global-auto-revert-mode 1) ;; Enable global auto-revert mode to keep buffers up to date with their corresponding files. (setq-default indent-tabs-mode nil) (recentf-mode 1) ;; Enable tracking of recently opened files. (savehist-mode 1) ;; Enable saving of command history. (save-place-mode 1) ;; Enable saving the place in files for easier return. (winner-mode 1) ;; Enable winner mode to easily undo window configuration changes. (xterm-mouse-mode 1) ;; Enable mouse support in terminal mode. (file-name-shadow-mode 1) ;; Enable shadowing of filenames for clarity. (repeat-mode 1) ;; Enable repeat key after key chord ;;oding system for files to UTF-8. (modify-coding-system-alist 'file "" 'utf-8) ;; Add a hook to run code after Emacs has fully initialized. (add-hook 'after-init-hook (lambda () (message "Emacs has fully loaded. This code runs after startup.") ;; Insert a welcome message in the *scratch* buffer displaying loading time and activated packages. (with-current-buffer (get-buffer-create "*scratch*") (insert (format ";; Welcome to Emacs! ;; ;; Loading time : %s ;; Packages : %s " (emacs-init-time) (length (elpaca--queued)))))))) (defcustom ek-use-nerd-fonts t "Configuration for using Nerd Fonts Symbols." :type 'boolean :group 'appearance) #+end_src * wayland clipboard fix #+begin_src emacs-lisp ;; (setq wl-copy-process nil) ;; (defun wl-copy (text) ;; (setq wl-copy-process (make-process :name "wl-copy" ;; :buffer nil ;; :command '("wl-copy" "-f" "-n") ;; :connection-type 'pipe ;; :noquery t)) ;; (process-send-string wl-copy-process text) ;; (process-send-eof wl-copy-process)) ;; (defun wl-paste () ;; (if (and wl-copy-process (process-live-p wl-copy-process)) ;; nil ; should return nil if we're the current paste owner ;; (shell-command-to-string "wl-paste -n | tr -d \r"))) ;; (setq interprogram-cut-function 'wl-copy) ;; (setq interprogram-paste-function 'wl-paste) #+end_src * window #+begin_src emacs-lisp (use-package window :ensure nil :custom (display-buffer-alist '( ("\\*\\(Backtrace\\|Warnings\\|Compile-Log\\|[Hh]elp\\|Messages\\|Bookmark List\\|Occur\\)\\*" (display-buffer-in-side-window) (window-height . 0.25) (side . bottom) (slot . 0)) ("\\*\\(eldoc\\)\\*" (display-buffer-in-side-window) (window-height . 0.25) (side . bottom) (slot . 1) (window-parameters . ((no-delete-other-windows . t))) (body-function . (lambda (window) (select-window window)))) ("\\*\\(Flymake diagnostics\\|xref\\|ivy\\|Swiper\\|Completions\\)" (display-buffer-in-side-window) (window-height . 0.25) (side . bottom) (slot . 1))))) #+end_src * dired #+begin_src emacs-lisp (use-package dired :ensure nil :custom (dired-listing-switches "-lah --group-directories-first") (dired-dwim-target t) (dired-guess-shell-alist-user '(("\\.\\(mp[34]\\|m4a\\|ogg\\|flac\\|webm\\|mkv\\)" "mpv" "xdg-open" "open") (".*" "open" "xdg-open"))) (dired-kill-when-opening-new-dired-buffer t) (dired-create-destination-dirs 'always) :config (with-eval-after-load 'async (dired-async-mode 1)) (require 'mailcap) (setq mailcap-prefer-mailcap-viewers nil) (mailcap-add "image/png" "kitty --hold kitty +kitten icat %s") (mailcap-add "image/jpeg" "kitty --hold kitty +kitten icat %s") (mailcap-add "image/jpg" "kitty --hold kitty +kitten icat %s") (mailcap-add "image/gif" "kitty --hold kitty +kitten icat %s") (define-key dired-mode-map (kbd "E") (lambda () (interactive) (mailcap-view-file (dired-get-filename)))) (advice-add 'dired-create-directory :around (lambda (orig-fun dirname) (let ((current-buffer (current-buffer))) (funcall orig-fun dirname) (with-current-buffer current-buffer (revert-buffer))))) (when (eq system-type 'darwin) (let ((gls (executable-find "gls"))) (when gls (setq insert-directory-program gls))))) #+end_src #+begin_src emacs-lisp (use-package async :ensure t) #+end_src * 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))) (use-package erc :ensure nil :defer t :custom ;; connection (erc-nick "cashmere1337") (erc-user-full-name "cashmere") ;; behaviour (erc-join-buffer 'buffer) (erc-kill-buffer-on-part nil) (erc-kill-queries-on-quit t) (erc-kill-server-buffer-on-quit t) ;; visuals (erc-fill-function 'erc-fill-wrap) (erc-timestamp-format "[%H:%M]") (erc-timestamp-format-left "[%H:%M]") (erc-timestamp-format-right "[%H:%M]") (erc-hide-list '("JOIN" "PART" "QUIT" "NICK" "MODE")) (erc-track-exclude-types '("JOIN" "PART" "QUIT" "NICK" "MODE" "324" "329" "332" "333" "353" "477")) (erc-track-shorten-start 4) (erc-track-visibility 'visible) ;; prompt (erc-prompt (lambda () (concat (buffer-name) ">"))) ;; modules (erc-modules '(autojoin button completion fill irccontrols list match move-to-prompt netsplit networks nicks noncommands notifications readonly ring scrolltobottom stamp track)) :config ;; highlight own nick (setq erc-current-nick-highlight-type 'all) (setq erc-keywords '("cashmere")) (setq erc-pals '("cashmere")) ;; nicks module (built-in nick coloring in Emacs 30) (setq erc-nicks-contrast-range '(40 . 90)) ;; scrolltobottom -- keep input always at bottom (setq erc-scrolltobottom-all t) (erc-scrolltobottom-mode 1) ;; fill-wrap -- modern chat-like wrapping (setq erc-fill-wrap-align-prompt nil) (setq erc-fill-static-center 14) ;; track -- activity in modeline (setq erc-track-position-in-mode-line t) ;; keep large buffer for soju history replay (setq erc-max-buffer-size 100000) (erc-update-modules)) (defvar my/consult-source-erc (list :name "IRC" :narrow ?i :category 'buffer :face 'erc-default-face :state #'consult--buffer-state :items (lambda () (mapcar #'buffer-name (cl-remove-if-not (lambda (buf) (with-current-buffer buf (derived-mode-p 'erc-mode))) (buffer-list)))))) (defun my/erc-switch-channel () "Switch to an ERC channel buffer via consult." (interactive) (consult-buffer (list my/consult-source-erc))) (defun my/erc-fetch-history (&optional count) "Fetch COUNT previous messages from Soju via CHATHISTORY. Defaults to 1000 (soju max). With prefix arg, prompts for count. Temporarily disables notifications during the fetch." (interactive "P") (let ((target (erc-default-target))) (if (not target) (message "Not in a channel buffer.") (let ((n (min (if count (read-number "Messages to fetch: " 1000) 1000) 1000))) (erc-notifications-disable) (erc-server-send (format "CHATHISTORY LATEST %s * %d" target n)) (run-at-time 5 nil #'erc-notifications-enable))))) (defun my/erc-quit-all () "Disconnect from IRC and kill all ERC buffers." (interactive) (erc-cmd-QUIT "bye") (run-at-time 1 nil (lambda () (dolist (buf (buffer-list)) (when (with-current-buffer buf (derived-mode-p 'erc-mode)) (kill-buffer buf)))))) (defun my/erc-reconnect () "Reconnect to the current ERC server." (interactive) (erc-cmd-RECONNECT)) (defun my/erc-list-channels () "Request channel list from the current ERC server." (interactive) (erc-cmd-LIST)) (defun persp-irc () "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))) (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 (switch-to-buffer (car erc-bufs)) (letrec ((hook-fn (lambda () (switch-to-buffer (current-buffer)) (remove-hook 'erc-join-hook hook-fn)))) (add-hook 'erc-join-hook hook-fn)) (run-irc)))) ;; 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 () (setq-local scroll-margin 0) (setq-local maximum-scroll-margin 0.0) (setq-local corfu-auto nil))) ;; Prevent accidental ERC buffer kills from parting channels (add-hook 'kill-buffer-query-functions (lambda () (if (and (derived-mode-p 'erc-mode) (erc-server-process-alive) (not (eq this-command 'my/erc-quit-all))) (yes-or-no-p (format "ERC buffer %s is connected. Really kill (will part channel)? " (buffer-name))) t))) (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))) ;; Auto-start ERC in hub daemon on first frame (when (my/hub-p) (defun my/hub-auto-start-erc (&optional _frame) "Auto-connect ERC when first hub frame is created." (run-irc) (remove-hook 'server-after-make-frame-hook #'my/hub-auto-start-erc)) (add-hook 'server-after-make-frame-hook #'my/hub-auto-start-erc)) #+end_src * lambda-themes #+begin_src emacs-lisp ;; (use-package lambda-themes ;; :ensure (:host github :repo "lambda-emacs/lambda-themes") ;; :custom ;; (lambda-themes-set-italic-comments t) ;; (lambda-themes-set-italic-keywords t) ;; (lambda-themes-set-variable-pitch t) ;; (lambda-themes-set-theme 'light) ;; :config ;; (load-theme 'lambda-light)) #+end_src * TODO centered cursor #+begin_src emacs-lisp (defvar my/centered-cursor-enabled nil) (defun my/centered-cursor () (interactive) (if my/centered-cursor-enabled (progn (setq scroll-preserve-screen-position nil scroll-conservatively 0 maximum-scroll-margin 0.0 scroll-margin 0) (setq my/centered-cursor-enabled nil) (message "centered-cursor off")) (progn (setq scroll-preserve-screen-position t scroll-conservatively 101 maximum-scroll-margin 0.5 scroll-margin 99999) (setq my/centered-cursor-enabled t) (message "centered-cursor on")))) #+end_src * cursory #+begin_src emacs-lisp (use-package cursory :ensure t :demand t :if (display-graphic-p) :config (setq cursory-presets '((box :cursor-color success ; will typically be green :blink-cursor-interval 1.2) (box-no-blink :inherit box :blink-cursor-mode -1) (bar :cursor-type (bar . 2) :cursor-color error ; will typically be red :blink-cursor-interval 0.8) (bar-no-other-window :inherit bar :cursor-in-non-selected-windows nil) (bar-no-blink :inherit bar :blink-cursor-mode -1) (underscore :cursor-color warning ; will typically be yellow :cursor-type (hbar . 3) :blink-cursor-interval 0.3 :blink-cursor-blinks 50) (underscore-no-other-window :inherit underscore :cursor-in-non-selected-windows nil) (underscore-thick :inherit underscore :cursor-type (hbar . 8) :cursor-in-non-selected-windows (hbar . 3)) (t ; the default values :cursor-color unspecified ; use the theme's original :cursor-type box :cursor-in-non-selected-windows hollow :blink-cursor-mode 1 :blink-cursor-blinks 10 :blink-cursor-interval 0.2 :blink-cursor-delay 0.2))) ;; I am using the default value of `cursory-latest-state-file'. ;; Set last preset or fall back to desired style from ;; `cursory-presets'. Alternatively, use the function ;; `cursory-set-last-or-fallback' (can be added to the ;; `after-init-hook'. (cursory-set-preset (or (cursory-restore-latest-preset) 'box)) ;; Persist configurations between Emacs sessions. Also apply the ;; :cursor-color again when swithcing to another theme. (cursory-mode 1) :bind ;; We have to use the "point" mnemonic, because C-c c is often the ;; suggested binding for `org-capture' and is the one I use as well. ("C-c p" . cursory-set-preset)) #+end_src * clipetty #+begin_src emacs-lisp (use-package clipetty :ensure t :hook (after-init . global-clipetty-mode)) #+end_src ** kkp.el #+begin_src emacs-lisp ;; (use-package kkp ;; :ensure t ;; :hook (tty-setup . global-kkp-mode)) #+end_src * isearch #+begin_src emacs-lisp (use-package isearch :ensure nil ;; This is built-in, no need to fetch it. :config (setq isearch-lazy-count t) ;; Enable lazy counting to show current match information. (setq lazy-count-prefix-format "(%s/%s) ") ;; Format for displaying current match count. (setq lazy-count-suffix-format nil) ;; Disable suffix formatting for match count. (setq search-whitespace-regexp ".*?") ;; Allow searching across whitespace. :bind (("C-s" . isearch-forward) ;; Bind C-s to forward isearch. ("C-r" . isearch-backward))) ;; Bind C-r to backward isearch. #+end_src * vc #+begin_src emacs-lisp (use-package vc :ensure nil ;; This is built-in, no need to fetch it. :defer t :bind (("C-x v d" . vc-dir) ;; Open VC directory for version control status. ("C-x v =" . vc-diff) ;; Show differences for the current file. ("C-x v D" . vc-root-diff) ;; Show differences for the entire repository. ("C-x v v" . vc-next-action)) ;; Perform the next version control action. :config ;; Better colors for g b (blame file) (setq vc-annotate-color-map '((20 . "#f5e0dc") (40 . "#f2cdcd") (60 . "#f5c2e7") (80 . "#cba6f7") (100 . "#f38ba8") (120 . "#eba0ac") (140 . "#fab387") (160 . "#f9e2af") (180 . "#a6e3a1") (200 . "#94e2d5") (220 . "#89dceb") (240 . "#74c7ec") (260 . "#89b4fa") (280 . "#b4befe")))) #+end_src * ibuffer ibuffer is an integrated buffer management suite. If you have Emacs for a long time open or just like me, like to switch between different projects on a constant, keeping a good overview over you buffers is important. That's why I added the settings which I found [[https://olddeuteronomy.github.io/post/emacs-ibuffer-config/][here]]. I adjusted the config accordingly to my modes which I use on a frequent. #+begin_src emacs-lisp (use-package ibuffer :ensure nil :config (setq ibuffer-expert t) (setq ibuffer-display-summary nil) (setq ibuffer-use-other-window nil) (setq ibuffer-show-empty-filter-groups nil) (setq ibuffer-default-sorting-mode 'filename/process) (setq ibuffer-title-face 'font-lock-doc-face) (setq ibuffer-use-header-line t) (setq ibuffer-default-shrink-to-minimum-size nil) (setq ibuffer-formats '((mark modified read-only locked " " (name 30 30 :left :elide) " " (size 9 -1 :right) " " (mode 16 16 :left :elide) " " filename-and-process) (mark " " (name 16 -1) " " filename))) (setq ibuffer-saved-filter-groups '(("Main" ("Directories" (mode . dired-mode)) ("Rust" (or (mode . rust-mode) (mode . rust-ts-mode))) ("Python" (or (mode . python-ts-mode) (mode . c-mode) (mode . python-mode))) ("Nix" (or (mode . nix-mode) (mode . nix-ts-mode))) ("Scripts" (or (mode . shell-script-mode) (mode . sh-mode) (mode . bash-ts-mode) (mode . lua-mode) (mode . bat-mode))) ("Config" (or (mode . conf-mode) (mode . conf-toml-mode) (mode . toml-ts-mode) (mode . conf-windows-mode) (name . "^\\.clangd$") (name . "^\\.gitignore$") (name . "^Doxyfile$") (name . "^config\\.toml$") (mode . yaml-ts-mode))) ("Web" (or (mode . mhtml-mode) (mode . html-mode) (mode . web-mode) (mode . nxml-mode))) ("CSS" (or (mode . css-mode) (mode . sass-mode))) ("JS" (or (mode . js-mode) (mode . rjsx-mode))) ("Markup" (or (mode . markdown-mode) (mode . adoc-mode))) ("Org" (mode . org-mode)) ("Terminal" (or (mode . ghostel-mode) (mode . eat-mode) (mode . eshell-mode) (mode . shell-mode) (mode . term-mode))) ("LaTeX" (name . "\.tex$")) ("Magit" (or (mode . magit-blame-mode) (mode . magit-cherry-mode) (mode . magit-diff-mode) (mode . magit-log-mode) (mode . magit-process-mode) (mode . magit-status-mode))) ("Elfeed" (or (mode . elfeed-search-mode) (mode . elfeed-show-mode))) ("Fundamental" (or (mode . fundamental-mode) (mode . text-mode))) ("ECA - Editor Code Assistant" (or (mode . eca-chat-mode))) ("ERC" (or (mode . erc-mode))) ("Emacs" (or (mode . emacs-lisp-mode) (name . "^\\*Help\\*$") (name . "^\\*Custom.*") (name . "^\\*Org Agenda\\*$") (name . "^\\*info\\*$") (name . "^\\*scratch\\*$") (name . "^\\*Backtrace\\*$") (name . "^\\*Messages\\*$")))))) :hook (ibuffer-mode . (lambda () (ibuffer-switch-to-saved-filter-groups "Main")))) #+end_src ** nerd-icons-ibuffer To spice things up, I enabled the same Nerd Icons which are visible in dired to ibuffer. Not necessary but an eyecandy to look at! #+begin_src emacs-lisp (use-package nerd-icons :if ek-use-nerd-fonts :ensure t) (use-package nerd-icons-ibuffer :if ek-use-nerd-fonts :ensure t :hook (ibuffer-mode . nerd-icons-ibuffer-mode)) #+end_src * smerge-mode #+begin_src emacs-lisp (use-package smerge-mode :ensure nil ;; This is built-in, no need to fetch it. :defer t :bind (:map smerge-mode-map ("C-c ^ u" . smerge-keep-upper) ;; Keep the changes from the upper version. ("C-c ^ l" . smerge-keep-lower) ;; Keep the changes from the lower version. ("C-c ^ n" . smerge-next) ;; Move to the next conflict. ("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 ** markdown-mode #+begin_src emacs-lisp (use-package markdown-mode :ensure t :hook (markdown-mode . nb/markdown-unhighlight) :init (setq-default abbrev-mode t) :config (defvar nb/current-line '(0 . 0) "(start . end) of current line in current buffer") (make-variable-buffer-local 'nb/current-line) (defun nb/unhide-current-line (limit) "Font-lock function" (let ((start (max (point) (car nb/current-line))) (end (min limit (cdr nb/current-line)))) (when (< start end) (remove-text-properties start end '(invisible t display "" composition "")) (goto-char limit) t))) (defun nb/refontify-on-linemove () "Post-command-hook" (let* ((start (line-beginning-position)) (end (line-beginning-position 2)) (needs-update (not (equal start (car nb/current-line))))) (setq nb/current-line (cons start end)) (when needs-update ;; FIX: Verwende jit-lock-refontify statt font-lock-fontify-block (jit-lock-refontify start end)))) (defun nb/markdown-unhighlight () "Enable markdown concealling" (interactive) (markdown-toggle-markup-hiding 'toggle) (font-lock-add-keywords nil '((nb/unhide-current-line)) t) ;; FIX: Stelle sicher dass jit-lock aktiv ist (jit-lock-register #'font-lock-fontify-region) (add-hook 'post-command-hook #'nb/refontify-on-linemove nil t)) :custom-face (markdown-header-delimiter-face ((t (:foreground "#616161" :height 0.9)))) (markdown-header-face-1 ((t (:height 1.6 :foreground "#A3BE8C" :weight extra-bold :inherit markdown-header-face)))) (markdown-header-face-2 ((t (:height 1.4 :foreground "#EBCB8B" :weight extra-bold :inherit markdown-header-face)))) (markdown-header-face-3 ((t (:height 1.2 :foreground "#D08770" :weight extra-bold :inherit markdown-header-face)))) (markdown-header-face-4 ((t (:height 1.15 :foreground "#BF616A" :weight bold :inherit markdown-header-face)))) (markdown-header-face-5 ((t (:height 1.1 :foreground "#b48ead" :weight bold :inherit markdown-header-face)))) (markdown-header-face-6 ((t (:height 1.05 :foreground "#5e81ac" :weight semi-bold :inherit markdown-header-face))))) #+end_src ** eca /(editor code assistant)/ # 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")) :defer t :custom ;; Chat window (eca-chat-window-side 'left) (eca-chat-window-width 0.35) (eca-chat-use-side-window t) (eca-chat-focus-on-open t) (eca-chat-tab-line t) ;; Diff tools (eca-chat-diff-tool 'ediff) (eca-rewrite-diff-tool 'ediff) ;; Context (eca-chat-auto-add-cursor t) (eca-chat-auto-add-repomap t) (eca-chat-custom-model nil) ;; UI (eca-chat-shrink-called-tools t) (eca-chat-expand-pending-approval-tools t) (eca-chat-table-beautify t) (eca-buttons-allow-mouse t) :config ;; Disable corfu auto-popup in chat buffers (TAB-only completion) (add-hook 'eca-chat-mode-hook (lambda () (setq-local corfu-auto nil))) ;; Evil mode integration (with-eval-after-load 'evil ;; Chat opens in normal state; press i to type in prompt (evil-set-initial-state 'eca-chat-mode 'normal) (evil-set-initial-state 'eca-mcp-details-mode 'normal)) ;; Keybindings via general with which-key labels (with-eval-after-load 'general ;; Normal state bindings in chat buffer (general-def 'normal eca-chat-mode-map "RET" '(eca-chat-send-prompt-at-chat :wk "send prompt") "q" '(eca-chat-toggle-window :wk "close chat") "C-l" '(eca-chat-clear :wk "clear chat") ;; g-prefix: chat navigation/management "gr" '(eca-chat-repeat-prompt :wk "repeat prompt") "gn" '(eca-chat-new :wk "new chat") "gs" '(eca-chat-select :wk "select chat") "gm" '(eca-chat-select-model :wk "select model") "gv" '(eca-chat-select-variant :wk "select variant") "ga" '(eca-chat-select-agent :wk "select agent") "gc" '(eca-chat-cycle-agent :wk "cycle agent") ;; bracket navigation "[[" '(eca-chat-go-to-prev-user-message :wk "prev message") "]]" '(eca-chat-go-to-next-user-message :wk "next message") "[b" '(eca-chat-go-to-prev-expandable-block :wk "prev block") "]b" '(eca-chat-go-to-next-expandable-block :wk "next block") "TAB" '(eca-chat-toggle-expandable-block :wk "toggle block") "zR" '(eca-chat-expand-all-blocks :wk "expand all") "zM" '(eca-chat-collapse-all-blocks :wk "collapse all")) ;; C-c prefix in normal state — labeled group (general-def 'normal eca-chat-mode-map "C-c" '(:ignore t :wk "eca") "C-c a" '(eca-chat-tool-call-accept-all :wk "accept all tools") "C-c A" '(eca-chat-tool-call-accept-all-and-remember :wk "accept & remember") "C-c c" '(eca-chat-stop-prompt :wk "stop") "C-c s" '(eca-chat-save-to-file :wk "save chat") "C-c r" '(eca-chat-rename :wk "rename chat") "C-c ." '(eca-transient-menu :wk "transient menu") "C-c ," '(eca-mcp-details :wk "mcp details")) ;; Insert state bindings (general-def 'insert eca-chat-mode-map "S-" '(newline :wk "newline") "C-" '(eca-chat-repeat-prompt :wk "repeat prompt") "C-c c" '(eca-chat-stop-prompt :wk "stop") "C-c a" '(eca-chat-tool-call-accept-all :wk "accept all tools") "C-c ." '(eca-transient-menu :wk "transient menu")) ;; MCP details buffer (general-def 'normal eca-mcp-details-mode-map "q" '(quit-window :wk "close") "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 =eat= as the terminal backend. #+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 (claude-code-ide-terminal-backend 'eat) ;; 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 75) (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 '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 (use-package eldoc :ensure nil :config (setq eldoc-idle-delay 0.25) (setq eldoc-echo-area-use-multiline-p nil) (setq eldoc-echo-area-display-truncation-message nil) :init (global-eldoc-mode)) (use-package eldoc-box :ensure t :defer t :config (setq eldoc-box-max-pixel-width 800) (setq eldoc-box-max-pixel-height 600)) #+end_src ** eldoc-box #+begin_src emacs-lisp ;; (use-package eldoc-box ;; :ensure t ;; :config ;; ;; (setq eldoc-box-at-point-position-function #'eldoc-box--default-at-point-position-function) ;; (setq eldoc-box-help-at-point-mode t)) #+end_src * flymake #+begin_src emacs-lisp (use-package flymake :ensure nil ;; This is built-in, no need to fetch it. :defer t ;; :hook (prog-mode . flymake-mode) :custom (flymake-margin-indicators-string '((error "!»" compilation-error) (warning "»" compilation-warning) (note "»" compilation-info)))) (setq trusted-content '("~/.emacs.d/config.el" "~/.emacs.d/init.el")) #+end_src * flycheck #+begin_src emacs-lisp (use-package flycheck :ensure t :config (setq flycheck-global-modes '(not org-mode)) (setq flycheck-display-errors-function nil) (add-hook 'after-init-hook #'global-flycheck-mode)) #+end_src ** flycheck-rust #+begin_src emacs-lisp (use-package flycheck-rust :ensure t :config (setq flycheck-rust-check-tests nil) (add-hook 'rust-ts-mode-hook #'flycheck-rust-setup)) #+end_src ** flycheck-ruff #+begin_src emacs-lisp (use-package flycheck-python-ruff :ensure (:host github :repo "v4n6/flycheck-python-ruff") :hook (python-ts-mode . flycheck-python-ruff-setup)) #+end_src ** flycheck eglot #+begin_src emacs-lisp (use-package flycheck-eglot :ensure t :after (flycheck eglot) :config (global-flycheck-eglot-mode 1)) #+end_src ** sideline-flycheck #+begin_src emacs-lisp (use-package sideline :ensure t :hook (flycheck-mode . sideline-mode) :config (setq sideline-delay 0.001) :custom (sideline-backends-right '(sideline-flycheck)) (sideline-display-backend-name t)) (use-package sideline-flycheck :ensure t :hook (flycheck-mode . sideline-flycheck-setup)) #+end_src * xref #+begin_src emacs-lisp (use-package xref :ensure nil) #+end_src * project #+begin_src emacs-lisp (use-package project :ensure nil) #+end_src * org-mode #+begin_src emacs-lisp (setq org-directory "~/org/") (use-package org :ensure nil :custom (org-list-demote-modify-bullet '(("+" . "-") ("-" . "+"))) :config (require 'org-tempo) (custom-set-faces '(org-document-title ((t (:height 1.5)))) '(outline-1 ((t (:height 1.5)))) '(outline-2 ((t (:height 1.4)))) '(outline-3 ((t (:height 1.3)))) '(outline-4 ((t (:height 1.25)))) '(outline-5 ((t (:height 1.2)))) '(outline-6 ((t (:height 1.175)))) '(outline-7 ((t (:height 1.0)))) '(outline-8 ((t (:height 1.0)))) '(outline-9 ((t (:height 1.0))))) (setq org-startup-folded 'overview) (setq ;; org-adapt-indentation t ;; org-time-stamp-rounding-minutes '(5 5) org-return-follows-link t org-hide-leading-stars t org-pretty-entities t org-startup-truncated t org-ellipsis " ") (setq org-src-fontify-natively t org-src-tab-acts-natively t org-edit-src-content-indentation 0) (setq org-log-done t org-auto-align-tags t org-tags-column -80 org-fold-catch-invisible-edits 'show-and-error org-special-ctrl-a/e t org-insert-heading-respect-content t) (add-hook 'org-mode-hook 'variable-pitch-mode) ;; (add-hook 'org-mode-hook 'org-indent-mode) (add-hook 'org-mode-hook 'visual-line-mode) (add-hook 'org-mode-hook (lambda () (electric-indent-local-mode -1))) (add-to-list 'font-lock-extra-managed-props 'display) (font-lock-add-keywords 'org-mode `(("^.*?\( \)\(:[[:alnum:]_@#%:]+:\)$" (1 `(face nil display (space :align-to (- right ,(org-string-width (match-string 2)) 3))) prepend))) t)) #+end_src *** prompt timestamps with time #+begin_src emacs-lisp (defun my-org-read-date-always-with-time (orig-fun &optional org-with-time to-time from-string prompt default-time default-input inactive) (cl-letf (((symbol-function 'org-read-date--get-current-time) (lambda () (or default-time (current-time))))) (let* ((default-input (or default-input (format-time-string "%H:%M" (or default-time (current-time)))))) (funcall orig-fun t to-time from-string prompt default-time default-input inactive)))) (advice-add 'org-read-date :around #'my-org-read-date-always-with-time) #+end_src ** org-crypt Encrypt org headings tagged with =:crypt:= using GPG. Entries are automatically encrypted before save and can be decrypted in-place with =M-x org-decrypt-entry= (or =org-decrypt-entries= for all). #+begin_src emacs-lisp (use-package org-crypt :ensure nil :after org :config (org-crypt-use-before-save-magic) (setq org-crypt-key "1ED9D600A040D0AF193AAF30B877C0AC3B080FBB") (setq org-tags-exclude-from-inheritance '("crypt")) (add-hook 'org-mode-hook (lambda () (setq-local auto-save-default nil)))) #+end_src ** org-appear #+begin_src emacs-lisp (use-package org-appear :ensure t :commands (org-appear-mode) :hook (org-mode . org-appear-mode) :config (setq org-hide-emphasis-markers t) ;; Must be activated for org-appear to work (setq org-appear-autoemphasis t ;; Show bold, italics, verbatim, etc. org-appear-autolinks t ;; Show links org-appear-autosubmarkers t)) ;; Show sub- and superscripts #+end_src ** org-agenda #+begin_src emacs-lisp #+end_src ** render-org TUI-first beautiful rendering for org-mode (replaces org-modern). #+begin_src emacs-lisp ;; (add-to-list 'load-path "~/render-org.el") ;; (require 'render-org) ;; (add-hook 'org-mode-hook #'render-org-mode) ;; ;; Markdown support ;; (require 'render-markdown) ;; (add-hook 'markdown-mode-hook #'render-markdown-mode) ;; ;; SVG scaled headings via kitty-graphics ;; (setq render-org-heading-gfx-enabled 'nil) ;; ;; (setq render-org-heading-gfx-scales '(1.25 1.15 1.05 1.0 1.0 1.0)) ;; ;; (setq render-org-heading-gfx-font-family "MapleMono NF") #+end_src ** kitty-graphics 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 nil :load-path "/home/cashmere/projects/kitty-graphics" :demand t :custom (kitty-gfx-enable-video t) (kitty-gfx-debug nil) (kitty-gfx-shr-scale 'fit) (kitty-gfx-shr-fit-width 0.6) (kitty-gfx-shr-fit-height 20) :hook (dired-mode . kitty-gfx-dired-auto-preview-mode) :config (kitty-graphics-setup)) #+end_src ** org-fancy-properties #+begin_src emacs-lisp (use-package org-fancy-priorities :ensure t :hook (org-mode . org-fancy-priorities-mode) :config (setq org-fancy-priorities-list '("⚡" "⬆" "⬇" "☕"))) (setq org-priority-faces '((?A . (:background "#e91e63" :foreground "#ffffff" :weight bold)) (?B . (:background "#ff9800" :foreground "#1a1a1a" :weight bold)) (?C . (:background "#2196f3" :foreground "#ffffff" :weight bold)) (?D . (:background "#795548" :foreground "#ffffff" :weight bold)))) #+end_src ** TODO org-agenda settings :deprecated: Old config #+begin_src emacs-lisp ;; (setq org-agenda-start-on-weekday nil ;; org-agenda-block-separator nil ;; org-agenda-remove-tags t) ;; (setq org-agenda-skip-scheduled-if-done t ;; org-agenda-skip-deadline-if-done t ;; org-agenda-include-deadlines t ;; org-agenda-block-separator nil ;; org-agenda-compact-blocks t ;; org-agenda-start-day nil ;; org-agenda-span 1 ;; org-agenda-start-on-weekday nil ;; org-agenda-hide-tags-regexp "task" ;; org-agenda-prefix-format ;; '((agenda . " %i %?-12t% s") ;; (todo . " %i ") ;; (tags . " %i ") ;; (search . " %i "))) #+end_src ** org-babel #+begin_src emacs-lisp (with-eval-after-load 'org (setq org-confirm-babel-evaluate nil) (org-babel-do-load-languages 'org-babel-load-languages '((emacs-lisp . t) (shell . t) (python . t) (ruby . t) (perl . t) (C . t) (js . t) (css . t) (sass . t) (sql . t) (sqlite . t) (lua . t) (lisp . t) (scheme . t) (clojure . t) (haskell . t) (ocaml . t) (java . t) (dot . t) (gnuplot . t) (plantuml . t) (ditaa . t) (calc . t) (latex . t) (org . t) (makefile . t) (awk . t) (sed . t) (screen . t) (eshell . t)))) #+end_src ** ob-async #+begin_src emacs-lisp (use-package ob-async :ensure t :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 temporary justfile and runs the default recipe (or a named =:recipe=) via =just=. #+begin_src emacs-lisp (with-eval-after-load 'org (defun org-babel-execute:just (body params) "Execute a justfile source block. BODY is the justfile content. PARAMS may include: :recipe — recipe name to run (default: the default recipe) :dir — working directory (default: `default-directory')" (let* ((recipe (cdr (assq :recipe params))) (dir (or (cdr (assq :dir params)) default-directory)) (tmp (make-temp-file "ob-just-" nil "justfile"))) (unwind-protect (progn (with-temp-file tmp (insert body)) (shell-command-to-string (format "just --justfile %s --working-directory %s %s" (shell-quote-argument tmp) (shell-quote-argument (expand-file-name dir)) (if recipe (shell-quote-argument recipe) "")))) (delete-file tmp)))) (add-to-list 'org-src-lang-modes '("just" . just))) #+end_src ** my/snip-upload Upload buffer, region, or file to bouncer.cashmere.rs and copy URL. #+begin_src emacs-lisp (defun my/snip-upload (&optional file) "Upload FILE, region, or current buffer to bouncer.cashmere.rs. The xonsh `upload' command copies the URL to the clipboard; it is also added to the kill ring." (interactive) (let* ((ext (or (and file (file-name-extension file)) (and (buffer-file-name) (file-name-extension (buffer-file-name))) "txt")) (cmd (format "xonsh -c 'upload %s'" ext))) (if (and file (file-exists-p file)) (call-process-shell-command (format "cat %s | %s" (shell-quote-argument file) cmd)) (shell-command-on-region (if (use-region-p) (region-beginning) (point-min)) (if (use-region-p) (region-end) (point-max)) cmd nil nil)) (let ((url (string-trim (shell-command-to-string "wl-paste")))) (if (string-prefix-p "https://" url) (progn (kill-new url) (message "Uploaded: %s" url)) (message "Upload failed: %s" url))))) (defun my/snip-upload-file () "Prompt for a file and upload it to bouncer.cashmere.rs." (interactive) (my/snip-upload (read-file-name "Upload to bouncer: "))) #+end_src ** my/setup-git-remote Set up dual-push git remotes for the current repository. Prompts for the repo name on dev.cashmere.rs and sr.ht, then configures origin to push to both servers. sr.ht auto-creates repos on first push. #+begin_src emacs-lisp (defun my/setup-git-remote (dev-name srht-name) "Set up origin (dual-push) and backup remotes for the current git repo. DEV-NAME is the repo name on dev.cashmere.rs, SRHT-NAME on sr.ht." (interactive (let* ((dev (read-string "Repo name (dev.cashmere.rs): ")) (srht (read-string (format "Repo name (sr.ht) [%s]: " dev) nil nil dev))) (list dev srht))) (let* ((root (or (locate-dominating-file default-directory ".git") (error "Not in a git repository"))) (default-directory root) (dev-url (format "ssh://git@dev.cashmere.rs/cashmere/%s" dev-name)) (srht-url (format "git@git.sr.ht:~cashmere/%s" srht-name))) (shell-command (format "git remote remove origin 2>/dev/null; git remote add origin %s" dev-url)) (shell-command (format "git remote set-url --add --push origin %s" dev-url)) (shell-command (format "git remote set-url --add --push origin %s" srht-url)) (shell-command (format "git remote remove backup 2>/dev/null; git remote add backup %s" srht-url)) (message "Remotes configured:\n origin -> %s (push to both)\n backup -> %s" dev-url srht-url))) #+end_src ** my/paste-md-to-org [[http://yummymelon.com/devnull/import-markdown-to-org-with-the-clipboard-in-emacs.html][nfdn: Import Markdown to Org with the Clipboard in Emacs]] #+begin_src emacs-lisp (defun my/paste-md-to-org () "Yank Markdown text as Org. This command will convert Markdown text in the top of the `kill-ring' and convert it to Org using the pandoc utility." (interactive) (save-excursion (with-temp-buffer (yank) (shell-command-on-region (point-min) (point-max) "pandoc -f markdown -t org --wrap=preserve" t t) (kill-region (point-min) (point-max))) (yank))) #+end_src ** workflow enhancements These enhancements implement a comprehensive org-mode workflow based on GTD principles and your personal needs. *** org-log settings Track when and why tasks change state - essential for accountability and retrospectives. #+begin_src emacs-lisp (with-eval-after-load 'org ;; Log state changes with timestamps (setq org-log-done 'time org-log-redeadline 'time org-log-reschedule 'time org-log-into-drawer t org-enforce-todo-dependencies t) ;; Update TODO keywords to require notes for WAIT and CANCELLED ;; @ means: prompt for note when entering state ;; ! means: timestamp when leaving state (setq org-todo-keywords '((sequence "TODO(t)" "NEXT(n)" "ACTIVE(a!)" "WAIT(w@/!)" "|" "DONE(d!)" "CANCELLED(c@)"))) ;; Customize note prompts (setq org-log-note-headings '((done . "CLOSING NOTE %t") (state . "State %-12s from %-12S %t") (note . "Note taken on %t") (reschedule . "Rescheduled from %S on %t") (delschedule . "Not scheduled, was %S on %t") (redeadline . "New deadline from %S on %t") (deldeadline . "Removed deadline, was %S on %t") (refile . "Refiled on %t") (clock-out . "")))) #+end_src *** org-clock enhancements Automatic task state management when clocking in/out. Tasks automatically become ACTIVE when you start working. #+begin_src emacs-lisp (use-package org-clock :after org :ensure nil :commands (org-clock-in org-clock-out org-clock-goto) :config ;; Setup hooks for clock persistence (org-clock-persistence-insinuate) (setq org-clock-clocked-in-display 'mode-line org-clock-history-length 23 org-clock-in-switch-to-state 'my/clock-in-to-started org-clock-out-when-done t org-clock-persist t org-clock-persist-query-resume nil) ;; Helper function: detect if heading is a project (defun my/is-project-p () "Any task with a todo keyword subtask." (save-restriction (widen) (let ((has-subtask) (subtree-end (save-excursion (org-end-of-subtree t))) (is-a-task (member (nth 2 (org-heading-components)) org-todo-keywords-1))) (save-excursion (forward-line 1) (while (and (not has-subtask) (< (point) subtree-end) (re-search-forward "^\\*+ " subtree-end t)) (when (member (org-get-todo-state) org-todo-keywords-1) (setq has-subtask t)))) (and is-a-task has-subtask)))) ;; Helper function: detect if heading is a task (defun my/is-task-p () "Any task with a todo keyword and no subtask." (save-restriction (widen) (let ((has-subtask) (subtree-end (save-excursion (org-end-of-subtree t))) (is-a-task (member (nth 2 (org-heading-components)) org-todo-keywords-1))) (save-excursion (forward-line 1) (while (and (not has-subtask) (< (point) subtree-end) (re-search-forward "^\\*+ " subtree-end t)) (when (member (org-get-todo-state) org-todo-keywords-1) (setq has-subtask t)))) (and is-a-task (not has-subtask))))) ;; Auto-change state when clocking in (defun my/clock-in-to-started (kw) "Switch task from TODO/NEXT to ACTIVE when clocking in. Skips capture tasks and projects." (when (not (and (boundp 'org-capture-mode) org-capture-mode)) (cond ;; Change TODO/NEXT tasks to ACTIVE when clocking in ((and (member (org-get-todo-state) (list "TODO" "NEXT")) (my/is-task-p)) "ACTIVE") ;; Change ACTIVE projects back to TODO (projects shouldn't be clocked directly) ((and (member (org-get-todo-state) (list "ACTIVE")) (my/is-project-p)) "TODO")))) :bind (("" . org-clock-goto) ("C-c C-x C-i" . org-clock-in) ("C-c C-x C-o" . org-clock-out))) #+end_src *** effort and complexity management #+begin_src emacs-lisp (with-eval-after-load 'org (setq org-global-properties '(("EFFORT_ALL" . "0:15 0:30 1:00 2:00 3:00 5:00 8:00") ("COMPLEXITY_ALL" . "low medium high") ("PRIORITY_ALL" . "A B C"))) (setq org-columns-default-format "%40ITEM(Task) %TODO %10EFFORT{+} %10CLOCKSUM %10PROGRESS %COMPLEXITY %DEADLINE") ;; Auto-set effort from complexity (already exists, keeping it here for reference) ;; Low complexity = 30 min, Medium = 2h, High = 5h (defun my/org-set-effort-from-complexity () "Auto-set EFFORT property based on COMPLEXITY property." (interactive) (let* ((complexity (org-entry-get (point) "COMPLEXITY")) (effort (org-entry-get (point) "EFFORT"))) (unless effort (cond ((string= complexity "low") (org-set-property "EFFORT" "0:30")) ((string= complexity "medium") (org-set-property "EFFORT" "2:00")) ((string= complexity "high") (org-set-property "EFFORT" "5:00")))))) ;; Keybinding for manual effort setting (define-key org-mode-map (kbd "C-c e") 'my/org-set-effort-from-complexity)) #+end_src *** my/create-project #+begin_src emacs-lisp (defun my/create-project () "Create a new project file with denote and comprehensive project structure." (interactive) (let* ((project-name (read-string "Project name: ")) (tags (completing-read-multiple "Additional tags (comma-separated): " '("work" "personal" "university" "learning"))) (all-tags (append '("project" "agenda") tags)) (deadline-str (org-read-date nil nil nil "Project deadline (optional, RET to skip): ")) (effort-str (completing-read "Estimated total effort: " '("10h" "20h" "40h" "80h" "160h") nil nil "40h")) (file-name (denote project-name all-tags))) (find-file file-name) (goto-char (point-max)) (insert "\n") (insert "* " project-name "\n") (insert ":PROPERTIES:\n") (when (and deadline-str (not (string-empty-p deadline-str))) (insert ":DEADLINE: <" deadline-str ">\n")) (insert ":EFFORT: " effort-str "\n") (insert ":PROGRESS: 0%\n") (insert ":END:\n\n") (insert "** Overview\n\n") (insert "- Goal :: \n") (insert "- Why :: \n") (insert "- Success Criteria :: \n") (insert "- Status :: Planning\n\n") (insert "** Phases\n\n") (insert "*** TODO Phase 1: Planning & Research\n") (when (and deadline-str (not (string-empty-p deadline-str))) (insert "DEADLINE: <" deadline-str ">\n")) (insert ":PROPERTIES:\n") (insert ":CREATED: " (format-time-string "[%Y-%m-%d %a %H:%M]") "\n") (insert ":END:\n\n") (insert "- [ ] Define scope\n") (insert "- [ ] Research requirements\n") (insert "- [ ] Break down into tasks\n\n") (insert "*** TODO Phase 2: Implementation\n") (insert ":PROPERTIES:\n") (insert ":CREATED: " (format-time-string "[%Y-%m-%d %a %H:%M]") "\n") (insert ":END:\n\n") (insert "*** TODO Phase 3: Review & Polish\n") (insert ":PROPERTIES:\n") (insert ":CREATED: " (format-time-string "[%Y-%m-%d %a %H:%M]") "\n") (insert ":END:\n\n") (insert "** Resources\n\n") (insert "- Links :: \n") (insert "- Files :: \n") (insert "- References :: \n\n") (insert "** Time Tracking\n\n") (insert "#+BEGIN: clocktable :scope file :maxlevel 3 :emphasize nil :link t\n") (insert "#+END:\n\n") (insert "Update with C-c C-c on the BEGIN line\n\n") (insert "** Status Updates\n\n") (insert "Use =C-c C-x C-c= for column view to update PROGRESS and see overview.\n\n") (insert "** Retrospective\n\n") (insert "- What worked :: \n") (insert "- What didn't :: \n") (insert "- Lessons learned :: \n") (insert "- Would do differently :: \n") (org-mode) (goto-char (point-min)) (search-forward "- Goal :: " nil t))) #+end_src *** org-capture helpers #+begin_src emacs-lisp (defun my/org-capture-timeblock-to-journal () "Capture function: jump to today's journal for a timeblock." (require 'denote) (require 'denote-journal) (let* ((today (format-time-string "%Y-%m-%d")) (journal-files (denote-directory-files "journal")) (today-journal (seq-find (lambda (file) (string-match-p today (file-name-nondirectory file))) journal-files))) (if today-journal (find-file today-journal) (denote-journal-new-or-existing-entry)) (goto-char (point-max)) (insert "\n* Timeblock\n"))) (defun my/org-capture-recurring-to-journal () "Capture function: jump to today's journal for a recurring task." (require 'denote) (require 'denote-journal) (let* ((today (format-time-string "%Y-%m-%d")) (journal-files (denote-directory-files "journal")) (today-journal (seq-find (lambda (file) (string-match-p today (file-name-nondirectory file))) journal-files))) (if today-journal (find-file today-journal) (denote-journal-new-or-existing-entry)) (goto-char (point-max)) (insert "\n* Recurring\n"))) (defun my/org-capture-meeting-to-journal () "Capture function: jump to today's journal for a meeting note." (require 'denote) (require 'denote-journal) (let* ((today (format-time-string "%Y-%m-%d")) (journal-files (denote-directory-files "journal")) (today-journal (seq-find (lambda (file) (string-match-p today (file-name-nondirectory file))) journal-files))) (if today-journal (find-file today-journal) (denote-journal-new-or-existing-entry)) (goto-char (point-max)) (insert "\n* Meeting: "))) ;; capture templates are merged in the org-capture section below #+end_src ** notation *** org-noter #+begin_src emacs-lisp (use-package org-noter :ensure t :defer t) #+end_src *** nov.el #+begin_src emacs-lisp (use-package nov :ensure t :mode ("\\.epub\\'" . nov-mode)) #+end_src * org-capture *** yequake #+begin_src emacs-lisp (use-package yequake :ensure t :custom (yequake-frames '(("org-capture" (buffer-fns . (yequake-org-capture)) (width . 0.75) (height . 0.5) (frame-parameters . ((name . "org-capture") (undecorated . t) (skip-taskbar . t) (sticky . t)))) ("emacs-everywhere" (buffer-fns . (emacs-everywhere)) (width . 0.75) (height . 0.5) (frame-parameters . ((name . "emacs-everywhere") (undecorated . t) (skip-taskbar . t) (sticky . t))))))) #+end_src *** org-capture combined with denote #+begin_src emacs-lisp (defun my/org-capture-denote-deadline () (let* ((context (read-string "Task with deadline: ")) (deadline (org-read-date t nil nil "Deadline: ")) (file-name (denote nil '("agenda")))) (find-file file-name) (goto-char (point-min)) (forward-line 4) (insert (format "\n* %s\nDEADLINE: <%s>\n\n" context deadline)) (current-buffer))) (defun my/org-capture-denote-scheduled () (let* ((context (read-string "Scheduled task: ")) (schedule (org-read-date t nil nil "Schedule: ")) (file-name (denote nil '("agenda")))) (find-file file-name) (goto-char (point-min)) (forward-line 4) (insert (format "\n* %s\nSCHEDULED: <%s>\n\n" context schedule)) (current-buffer))) (defun my/org-capture-denote-task () (let* ((context (read-string "Task: ")) (todo-state (completing-read "TODO state: " '("ACTIVE" "NEXT" "TODO" "WAIT"))) (file-name (denote nil '("agenda")))) (find-file file-name) (goto-char (point-min)) (forward-line 4) (insert (format "\n* %s %s\n\n" todo-state context)) (point))) (with-eval-after-load 'org (setq org-capture-templates '(("t" "Task" plain (function my/org-capture-denote-task) "" :immediate-finish t :jump-to-captured t) ("d" "Deadline" plain (function my/org-capture-denote-deadline) "" :immediate-finish t :jump-to-captured t) ("s" "Scheduled" plain (function my/org-capture-denote-scheduled) "" :immediate-finish t :jump-to-captured t) ("b" "Timeblock" plain (function my/org-capture-timeblock-to-journal) "" :immediate-finish nil :jump-to-captured t) ("r" "Recurring" plain (function my/org-capture-recurring-to-journal) "" :immediate-finish t :jump-to-captured t) ("m" "Meeting" plain (function my/org-capture-meeting-to-journal) "" :immediate-finish nil :jump-to-captured t)))) #+end_src ** org-super-agenda #+begin_src emacs-lisp (use-package org-super-agenda :ensure t :after org-agenda :init :config (org-super-agenda-mode)) #+end_src ** org-ql #+begin_src emacs-lisp (use-package org-ql :ensure t :after org-super-agenda) #+end_src ** agenda #+begin_src emacs-lisp ;;; --- Agenda display settings --- (setq org-agenda-window-setup 'current-window org-agenda-inhibit-startup t org-agenda-remove-tags t org-agenda-dim-blocked-tasks nil org-agenda-skip-scheduled-if-done t org-agenda-skip-deadline-if-done t) (setq org-agenda-scheduled-leaders '("" "-%2d")) (setq org-agenda-time-grid '((daily today require-timed) (800 1000 1200 1400 1600 1800 2000) "......" "________________")) (setq org-agenda-current-time-string "--- NOW ---------------") (setq org-agenda-prefix-format '((agenda . " %i %?-12t% s") (todo . " %i ") (tags . " %i ") (search . " %i "))) ;;; --- Agenda views --- ;; ;; d - Today: daily driver, what to focus on now ;; w - Week: calendar + upcoming + backlog ;; p - Projects: all project tasks by state ;; o - Overview: schedule + topics (project/dev/blog/self) + backlog (setq org-agenda-custom-commands '( ;; ── Today ────────────────────────────────────────────── ("d" "Today" ( ;; Block 1: standard agenda — time-grid, NOW line, scheduled + deadlines (agenda "" ((org-agenda-span 1) (org-agenda-start-day ".") (org-deadline-warning-days 3) (org-scheduled-past-days 0) (org-agenda-day-face-function (lambda (date) 'org-agenda-date)) (org-agenda-overriding-header "") (org-super-agenda-groups '((:name "Overdue" :scheduled past :deadline past :order 0) (:name "Schedule" :time-grid t :order 1) (:name "Deadlines" :deadline today :order 2) (:discard (:anything t)))))) ;; Block 2: active + next tasks (what you can work on right now) (org-ql-block '(and (todo "ACTIVE" "NEXT") (not (done))) ((org-ql-block-header "In Progress") (org-super-agenda-groups '((:auto-tags t))))) ;; Block 3: waiting items (org-ql-block '(and (todo "WAIT") (not (done))) ((org-ql-block-header "Waiting On") (org-super-agenda-groups '((:auto-tags t))))))) ;; ── Week ─────────────────────────────────────────────── ("w" "Week" ( ;; Block 1: today in detail (agenda "" ((org-agenda-span 1) (org-deadline-warning-days 0) (org-scheduled-past-days 0) (org-agenda-day-face-function (lambda (date) 'org-agenda-date)) (org-agenda-overriding-header "Today") (org-super-agenda-groups '((:name "Overdue" :scheduled past :deadline past :order 0) (:name "Schedule" :time-grid t :order 1) (:discard (:anything t)))))) ;; Block 2: next 6 days (agenda "" ((org-agenda-start-on-weekday nil) (org-agenda-start-day "+1d") (org-agenda-span 6) (org-deadline-warning-days 0) (org-agenda-overriding-header "Next 6 Days") (org-super-agenda-groups '((:anything t))))) ;; Block 3: upcoming deadlines beyond this week (agenda "" ((org-agenda-time-grid nil) (org-agenda-start-on-weekday nil) (org-agenda-start-day "+7d") (org-agenda-span 14) (org-agenda-show-all-dates nil) (org-agenda-entry-types '(:deadline)) (org-agenda-overriding-header "Upcoming Deadlines (+14d)") (org-super-agenda-groups '((:anything t))))) ;; Block 4: unscheduled backlog (org-ql-block '(and (todo) (not (done)) (not (scheduled)) (not (deadline))) ((org-ql-block-header "Backlog (unscheduled)") (org-super-agenda-groups '((:name "ACTIVE" :todo "ACTIVE" :order 1) (:name "NEXT" :todo "NEXT" :order 2) (:name "TODO" :todo "TODO" :order 3) (:name "WAIT" :todo "WAIT" :order 4) (:discard (:anything t)))))))) ;; ── Projects ─────────────────────────────────────────── ("p" "Projects" ((org-ql-block '(and (tags "project") (not (done))) ((org-ql-block-header "All Project Tasks") (org-super-agenda-groups '((:name "ACTIVE" :todo "ACTIVE" :order 1) (:name "NEXT" :todo "NEXT" :order 2) (:name "TODO" :todo "TODO" :order 3) (:name "WAIT" :todo "WAIT" :order 4) (:discard (:anything t)))))))) ;; ── Overview ─────────────────────────────────────────── ;; Full dashboard: schedule + topic sections + backlog ("o" "Overview" ( ;; Block 1: today's agenda — schedule, deadlines, overdue (agenda "" ((org-agenda-span 1) (org-agenda-start-day ".") (org-deadline-warning-days 3) (org-agenda-show-future-repeats nil) (org-agenda-overriding-header "") (org-super-agenda-groups '((:name "Overdue" :scheduled past :deadline past :order 0) (:name "Schedule" :time-grid t :order 1) (:name "Deadlines" :deadline today :order 2) (:discard (:anything t)))))) ;; Block 2: project (org-ql-block '(and (tags "project") (not (done))) ((org-ql-block-header "Project") (org-super-agenda-groups '((:name "ACTIVE" :todo "ACTIVE" :order 1) (:name "NEXT" :todo "NEXT" :order 2) (:name "TODO" :todo "TODO" :order 3) (:name "WAIT" :todo "WAIT" :order 4) (:discard (:anything t)))))) ;; Block 3: development (org-ql-block '(and (tags "development") (not (done))) ((org-ql-block-header "Development") (org-super-agenda-groups '((:name "ACTIVE" :todo "ACTIVE" :order 1) (:name "NEXT" :todo "NEXT" :order 2) (:name "TODO" :todo "TODO" :order 3) (:name "WAIT" :todo "WAIT" :order 4) (:discard (:anything t)))))) ;; Block 4: blog (org-ql-block '(and (tags "blog") (not (done))) ((org-ql-block-header "Blog") (org-super-agenda-groups '((:name "ACTIVE" :todo "ACTIVE" :order 1) (:name "NEXT" :todo "NEXT" :order 2) (:name "TODO" :todo "TODO" :order 3) (:name "WAIT" :todo "WAIT" :order 4) (:discard (:anything t)))))) ;; Block 5: self (org-ql-block '(and (tags "self") (not (done))) ((org-ql-block-header "Self") (org-super-agenda-groups '((:name "ACTIVE" :todo "ACTIVE" :order 1) (:name "NEXT" :todo "NEXT" :order 2) (:name "TODO" :todo "TODO" :order 3) (:name "WAIT" :todo "WAIT" :order 4) (:discard (:anything t)))))) ;; Block 6: backlog — everything not covered above (org-ql-block '(and (todo) (not (done)) (not (tags "project" "development" "blog" "self"))) ((org-ql-block-header "Backlog") (org-super-agenda-groups '((:name "ACTIVE" :todo "ACTIVE" :order 1) (:name "NEXT" :todo "NEXT" :order 2) (:name "TODO" :todo "TODO" :order 3) (:name "WAIT" :todo "WAIT" :order 4) (:discard (:anything t)))))))))) ;;; --- Theme-adaptive TODO keyword faces --- (setq org-todo-keyword-faces '(("TODO" . (:inherit (warning org-todo))) ("NEXT" . (:inherit (font-lock-type-face org-todo))) ("ACTIVE" . (:inherit (bold org-todo))) ("WAIT" . (:inherit (shadow org-todo))) ("DONE" . (:inherit (success org-todo))) ("CANCELLED" . (:inherit (shadow org-done))))) ;; (defun my/update-org-modern-faces () ;; (setq org-modern-todo-faces ;; `(("TODO" :background ,(face-attribute 'warning :foreground) ;; :foreground ,(face-attribute 'default :background)) ;; ("ACTIVE" :background ,(face-attribute 'error :foreground) ;; :foreground ,(face-attribute 'default :background)) ;; ("DONE" :background ,(face-attribute 'success :foreground) ;; :foreground ,(face-attribute 'default :background)) ;; ("CANCELLED" :background ,(face-attribute 'shadow :foreground) ;; :foreground ,(face-attribute 'default :background))))) ;; ;; (add-hook 'after-load-theme-hook #'my/update-org-modern-faces) ;; (add-hook 'after-init-hook #'my/update-org-modern-faces) ;;; --- Tag cleanup in agenda buffer --- (defun my/org-agenda-remove-tags () (save-excursion (goto-char (point-min)) (while (re-search-forward " +:[[:alnum:]_@#%:]+: *$" nil t) (replace-match "")))) (add-hook 'org-agenda-finalize-hook #'my/org-agenda-remove-tags) ;;; --- Late-indicator symbols for overdue items --- ;; ;; Replaces the numeric "days late" extra field with visual indicators: ;; 1-2 days: • 3-6 days: ! 7-13 days: ‼ 14+ days: ✖ (defun my/org-agenda-late-extra (orig-fn extra txt &rest args) "Advice around `org-agenda-format-item' to show overdue indicators." (let* ((agenda-extra (let ((d (when (stringp extra) (abs (string-to-number extra))))) (cond ((not d) "") ((= d 0) "") ((< d 3) " •") ((< d 7) " !") ((< d 14) " ‼") (t " ✖"))))) (apply orig-fn agenda-extra txt args))) (advice-add 'org-agenda-format-item :around #'my/org-agenda-late-extra) ;;; --- Clean up excessive blank lines between agenda blocks --- (defun my/org-agenda-fix-block-spacing () "Collapse triple+ newlines to double in the agenda buffer." (goto-char (point-min)) (while (re-search-forward "\n\n\n+" nil t) (replace-match "\n\n"))) (add-hook 'org-agenda-finalize-hook #'my/org-agenda-fix-block-spacing) #+end_src ** agenda-app Standalone agenda kiosk. Launch with: =emacs --eval '(my/agenda-app)' --no-splash= #+begin_src emacs-lisp (defun my/agenda-app () "Launch Emacs as a dedicated agenda kiosk. Opens the 'd' (Today) agenda view in a clean, distraction-free frame. Usage: emacs --eval '(my/agenda-app)' --no-splash" (interactive) (set-frame-name "Agenda") (delete-other-windows) (org-agenda nil "d") (delete-other-windows) (setq mode-line-format nil) (menu-bar-mode -1) (tool-bar-mode -1) (scroll-bar-mode -1) (when (bound-and-true-p tab-bar-mode) (tab-bar-mode -1)) (when (find-font (font-spec :family "Lato")) (buffer-face-set '(:family "Lato"))) (text-scale-increase 2) (hide-mode-line-mode 1) (winner-mode 1)) #+end_src ** ox-contrib #+begin_src emacs-lisp (use-package org-contrib :ensure (:wait t)) (require 'ox-extra) (ox-extras-activate '(ignore-headlines)) #+end_src ** ox-typst Org Backend exporter for typst templates written and configured in org mode. #+begin_src emacs-lisp (use-package ox-typst :ensure (:host github :repo "jmpunkt/ox-typst") :after org) #+end_src ** org-cliplink #+begin_src emacs-lisp (use-package org-cliplink :ensure t :defer t) #+end_src ** org-download #+begin_src emacs-lisp (use-package org-download :ensure t :hook (org-mode . org-download-enable) :config (setq-default org-download-image-dir "~/org/attachments/") (setq org-download-method 'directory) (setq org-download-timestamp "%Y%m%d-%H%M%S_") (setq org-download-heading-lvl nil)) #+end_src ** org-publish #+begin_src emacs-lisp ;; (setq org-publish-project-alist ;; '(("wiki" ;; :author "cashmere" ;; :email "cashmere@cashmere.rs" ;; :preserve-breaks t ;; :preserve-indent t ;; :with-title t ;; :base-directory "/home/cashmere/wiki/" ;; :base-extension "org" ;; :publishing-directory "/home/cashmere/wiki/html/" ;; :recursive t ;; :publishing-function org-html-publish-to-html ;; :with-toc nil ;; :section-numbers nil ;; :html-head "" ;; :preparation-function (lambda (_) ;; (require 'denote-org) ;; (dolist (file (directory-files-recursively ;; "/home/cashmere/wiki/" "\\.org$")) ;; (with-current-buffer (find-file-noselect file) ;; (ignore-errors ;; (denote-org-extras-convert-links-to-file-type)) ;; (save-buffer))))))) (setq org-publish-project-alist '(("wiki" :base-directory "~/garden" :publishing-directory "~/cashmere.rs/content/wiki" :publishing-function denote-publish-to-md :recursive nil :exclude-tags ("noexport" "draft") :section-numbers nil :with-creator nil :with-toc nil))) (setq org-export-with-broken-links t) #+end_src ** ox-pandoc #+begin_src emacs-lisp (use-package ox-pandoc :ensure t :after org) #+end_src ** ox-slidev Org Backend exporter for Slidev presentations. #+begin_src emacs-lisp (use-package ox-slidev :ensure (:repo "https://dev.cashmere.rs/cashmere/ox-slidev" :files (:defaults "templates")) :after org :config (require 'org-slidev)) #+end_src * denote Denote is objectively the best note taking extension, which you could install. It has a very easy to parse file naming scheme and suports not only org files. It has also transclusion inclusive! if you are interested, then check it out yourself. #+begin_src emacs-lisp (use-package denote :ensure t :hook (dired-mode . denote-dired-mode) :config (setq denote-rename-buffer-format "%t" denote-buffer-name-prefix "" denote-directory (expand-file-name "~/org/")) (denote-rename-buffer-mode 1)) #+end_src ** denote-agenda Denote fitself focused on the last versions to have a core for note taking and to be independent of org. But of course you may extend it with your own extensions, functions and helper or even extensions by its developer. One of these extesions is =denote-agenda=, which allows you to dynamically add and remove org-agendas files based on a denote keyword. I set it to =task=, very simple. When we compare it with org-roam, It took me several days to achieve almost the same workflow, one or two external packages and around of 200 extra lines of code. #+begin_src emacs-lisp (use-package denote-agenda :ensure t :after org :config (setq denote-agenda-include-regexp "_agenda") (denote-agenda-insinuate)) #+end_src ** denote-journal #+begin_src emacs-lisp (use-package denote-journal :ensure 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"))) ;; (advice-add 'denote-journal-new-or-existing-entry :after ;; (lambda (&rest _) (my/denote-journal-template))) #+end_src ** config #+begin_src emacs-lisp (defcustom my/denote-agenda-filename-component-regexp "\\(?:[-_.]\\|:\\)agenda\\(?:[-_.]\\|:\\|$\\)" "Regexp matching a filename component that denotes the 'agenda' tag. Works on the base filename (without extension), e.g. matches \"-agenda\", \":agenda:\", \"_agenda_\"." :type 'string) (defun my/denote--make-unique-filename (dir base ext) "Return a unique filepath in DIR for BASE+EXT by adding -1, -2... if needed." (let* ((extstr (if (and ext (not (string-empty-p ext))) (concat "." ext) "")) (candidate (expand-file-name (concat base extstr) dir)) (count 1)) (while (file-exists-p candidate) (setq candidate (expand-file-name (format "%s-%d%s" base count extstr) dir)) (setq count (1+ count))) candidate)) (defun my/denote-remove-agenda-filetag-and-silent-rename () "If current headline became DONE in a Denote note, remove :agenda: filetag and remove 'agenda' from filename silently. - Only acts if there are no other non-DONE TODOs in the same file. - Does not call Denote's interactive rename command (no buffers opened)." (when (and (derived-mode-p 'org-mode) (string= (org-get-todo-state) "DONE") (buffer-file-name) ;; detect Denote note by presence of #+identifier: (save-excursion (goto-char (point-min)) (re-search-forward "^#\\+identifier:" nil t))) ;; only proceed if no other active TODOs remain in the file (let ((has-other (catch 'has (org-map-entries (lambda () (let ((s (org-get-todo-state))) (when (and s (not (string= s "DONE"))) (throw 'has t)))) nil 'file) nil))) (unless has-other ;; 1) Remove :agenda: from #+filetags: (save-excursion (goto-char (point-min)) (when (re-search-forward "^#\\+filetags:\\s-*\\(.*\\)$" nil t) (let* ((tags (match-string 1)) (clean (replace-regexp-in-string ":agenda:" "" tags)) (clean (replace-regexp-in-string "::+" ":" clean)) (clean (replace-regexp-in-string "^:\\|:$" "" clean))) (if (string-empty-p clean) (delete-region (line-beginning-position) (1+ (line-end-position))) (beginning-of-line) (kill-line) (insert (concat "#+filetags: :" clean ":")))))) ;; 2) Compute new filename (silent, non-interactive) (let* ((file (buffer-file-name)) (dir (file-name-directory file)) (name (file-name-nondirectory file)) (ext (file-name-extension name)) ;; "org" (base (file-name-sans-extension name)) ;; remove the agenda component from the base (new-base (replace-regexp-in-string my/denote-agenda-filename-component-regexp "" base))) (when (and new-base (not (string= new-base base))) ;; cleanup repeated separators: convert multiple '-'/'_'/'..' to single '-' (setq new-base (replace-regexp-in-string "[-_.:]+" "-" new-base)) ;; strip leading/trailing separators (setq new-base (replace-regexp-in-string "\\`[-_.:-]+" "" new-base)) (setq new-base (replace-regexp-in-string "[-_.:-]+\\'" "" new-base)) ;; ensure unique filename to avoid overwrite without prompt (let ((target (my/denote--make-unique-filename dir new-base ext))) (condition-case err (progn (rename-file file target 1) ;; update current buffer to visit the new filename silently (set-visited-file-name target t t) ;; save changes (filetags update + new filename) (save-buffer) (message "Denote: removed 'agenda' component from filename -> %s" (file-name-nondirectory target))) (error (message "Denote rename failed: %s" (error-message-string err))))))))))) (add-hook 'org-after-todo-state-change-hook #'my/denote-remove-agenda-filetag-and-silent-rename) #+end_src ** denote-menu #+begin_src emacs-lisp (use-package denote-menu :ensure t :defer t :config (setq denote-menu-title-column-width 60)) #+end_src ** denote-org Some goodies for better integration with org files. #+begin_src emacs-lisp (use-package denote-org :ensure t) #+end_src ** consult-denote #+begin_src emacs-lisp (use-package consult-denote :ensure (:host github :repo "protesilaos/consult-denote") :defer t) #+end_src ** my/functions #+begin_src emacs-lisp (defun my/convert-all-denote-links-in-directory (directory) "Converts all denote: links to file: links in a specific directory." (interactive "DDirectory: ") (let ((org-files (directory-files directory t "\\.org$"))) (dolist (file org-files) (with-current-buffer (find-file-noselect file) (save-excursion (goto-char (point-min)) (while (re-search-forward "\\[\\[denote:\\([0-9T]+\\)\\]\\(?:\\[\\([^]]*\\)\\]\\)?\\]" nil t) (let* ((id (match-string 1)) (description (match-string 2)) (target-file (seq-find (lambda (f) (string-match-p (regexp-quote id) (file-name-nondirectory f))) org-files))) (when target-file (let* ((target-filename (file-name-nondirectory target-file)) (new-link (if description (format "[[file:./%s][%s]]" target-filename description) (format "[[file:./%s]]" target-filename)))) (replace-match new-link t t)))))) (save-buffer) (kill-buffer))))) #+end_src ** ox-json #+begin_src emacs-lisp (use-package ox-json :ensure t ) #+end_src ** denote-project-notes #+begin_src emacs-lisp #+end_src ** denote-explore #+begin_src emacs-lisp (use-package denote-explore :ensure t) #+end_src ** denote-publish #+begin_src emacs-lisp (use-package ox-gfm :ensure t) (use-package denote-publish :ensure (:repo "https://github.com/vedang/denote-publish")) (setq denote-publish-default-base-dir "~/garden") (setq denote-publish-default-output-dir "~/cashmere.rs/content/wiki") (setq denote-publish-link-class "internal-link") (setq denote-publish-front-matter-fields '(title subtitle identifier date last_updated_at aliases tags category skip_archive has_code og_image og_description og_video_id)) #+end_src ** tmr #+begin_src emacs-lisp (use-package tmr :ensure (:host github :repo "protesilaos/tmr") :config (setq tmr-sound-file "/usr/share/sounds/freedesktop/stereo/alarm-clock-elapsed.oga" tmr-notification-urgency 'normal tmr-description-list 'tmr-description-history) (define-key global-map (kbd "C-c t") #'tmr-prefix-map) ;; ── Timer persistence across Emacs restarts ────────────── (defvar my/tmr-save-file (expand-file-name "tmr-timers.eld" user-emacs-directory) "File to persist active tmr timers across Emacs restarts.") (defun my/tmr-save-timers () "Save active (non-finished) tmr timers to `my/tmr-save-file'." (let ((active (cl-loop for timer in tmr--timers unless (tmr--timer-finishedp timer) collect (list :end-date (tmr--timer-end-date timer) :creation-date (tmr--timer-creation-date timer) :input (tmr--timer-input timer) :description (tmr--timer-description timer) :acknowledgep (tmr--timer-acknowledgep timer) :paused-remaining (tmr--timer-paused-remaining timer))))) (with-temp-file my/tmr-save-file (prin1 active (current-buffer))))) (defun my/tmr-restore-timers () "Restore tmr timers from `my/tmr-save-file'. Timers that expired while Emacs was closed fire immediately." (require 'tmr) (when (file-exists-p my/tmr-save-file) (let ((saved (with-temp-buffer (insert-file-contents my/tmr-save-file) (read (current-buffer))))) (dolist (entry saved) (let* ((end-date (plist-get entry :end-date)) (creation-date (plist-get entry :creation-date)) (input (plist-get entry :input)) (description (plist-get entry :description)) (acknowledgep (plist-get entry :acknowledgep)) (paused-remaining (plist-get entry :paused-remaining)) (remaining (if paused-remaining paused-remaining (round (- (float-time end-date) (float-time))))) (adjusted-end (if (and (not paused-remaining) (<= remaining 0)) (time-add (current-time) 1) end-date)) (timer (tmr--timer-create :creation-date creation-date :end-date adjusted-end :input input :description description :acknowledgep acknowledgep :paused-remaining paused-remaining))) ;; For active timers, schedule the live Emacs timer and store it ;; in the timer-object slot (index 5 in the cl-defstruct vector). ;; We use aset directly to avoid needing setf expanders at ;; macro-expansion time (tmr may not be loaded when config tangles). (unless paused-remaining (let ((live-timer (run-with-timer (max 1 remaining) nil #'tmr--complete timer))) (aset timer 5 live-timer))) (push timer tmr--timers))) (run-hooks 'tmr--update-hook)) ;; Clear the save file so we don't double-restore (delete-file my/tmr-save-file))) (add-hook 'kill-emacs-hook #'my/tmr-save-timers) (add-hook 'emacs-startup-hook #'my/tmr-restore-timers)) #+end_src ** denote-merge #+begin_src emacs-lisp (use-package denote-merge :ensure (:host github :repo "protesilaos/denote-merge") ; not in any package archive ;; You can bind these to keys. They are here so you can learn about them. :commands (denote-merge-file denote-merge-region denote-merge-region-plain denote-merge-region-plain-indented denote-merge-region-org-src denote-merge-region-org-quote denote-merge-region-org-example denote-merge-region-markdown-quote denote-merge-region-markdown-fenced-block)) #+end_src * which-key #+begin_src emacs-lisp (use-package which-key :ensure t :defer t :hook (after-init . which-key-mode) :custom (which-key-idle-delay 0.3)) #+end_src * vertico #+begin_src emacs-lisp (use-package vertico :ensure t :hook (after-init . vertico-mode) :custom (vertico-count 10) (vertico-resize nil) (vertico-cycle nil) :bind (:map vertico-map ("C-j" . vertico-next) ("C-k" . vertico-previous)) :config (advice-add #'vertico--format-candidate :around (lambda (orig cand prefix suffix index _start) (setq cand (funcall orig cand prefix suffix index _start)) (concat (if (= vertico--index index) (propertize "» " 'face '(:foreground "#80adf0" :weight bold)) " ") cand)))) #+end_src ** corfu #+begin_src emacs-lisp (use-package corfu :ensure t :custom (corfu-cycle t) (corfu-auto t) (corfu-auto-delay 0.001) (corfu-auto-prefix 2) :init (global-corfu-mode) (corfu-history-mode) (corfu-popupinfo-mode)) #+end_src On Emacs 32 + a multi-TTY daemon (every ~Super+Return~ spawns =kitty emacsclient -nw -c=), the native tty child-frame popup tries to open a tty device for a closed client (~Could not open file: /dev/pts/N~) and spams the backtrace. =corfu-terminal= renders the popup as an overlay instead, so no frame and no device is ever opened. #+begin_src emacs-lisp (use-package corfu-terminal :ensure t :after corfu :config (corfu-terminal-mode +1)) #+end_src *** cape #+begin_src emacs-lisp (use-package cape :ensure t ;; Bind prefix keymap providing all Cape commands under a mnemonic key. ;; Press C-c p ? to for help. ;; Alternatively bind Cape commands individually. ;; :bind (("C-c p d" . cape-dabbrev) ;; ("C-c p h" . cape-history) ;; ("C-c p f" . cape-file) ;; ...) :init ;; Add to the global default value of `completion-at-point-functions' which is ;; used by `completion-at-point'. The order of the functions matters, the ;; first function returning a result wins. Note that the list of buffer-local ;; completion functions takes precedence over the global list. (add-hook 'completion-at-point-functions #'cape-file) ;; (add-hook 'completion-at-point-functions #'cape-history) ;; ... :config (with-eval-after-load 'eglot (advice-add 'eglot-completion-at-point :around #'cape-wrap-noninterruptible) (advice-add 'eglot-completion-at-point :around #'cape-wrap-buster))) #+end_src * consult #+begin_src emacs-lisp (use-package consult :ensure t :defer t :init (advice-add #'register-preview :override #'consult-register-window) (setq xref-show-xrefs-function #'consult-xref xref-show-definitions-function #'consult-xref) :config (setq consult-buffer-filter '("\\` " "\\`\\*Messages\\*\\'" "\\`\\*Warnings\\*\\'" "\\`\\*Compile-Log\\*\\'" "\\`\\*Backtrace\\*\\'" "\\`\\*Help\\*\\'" "\\`\\*scratch\\*\\'" "\\`\\*Completions\\*\\'" "\\`\\*Flymake" "\\`\\*lsp-help\\*\\'" "\\`\\*eldoc" "\\`\\*elpaca-log\\*\\'" "\\`\\*Native-compile-Log\\*\\'" "\\`\\*Async-native-compile-log\\*\\'"))) #+end_src #+begin_comment markdown-mode moved before eca — see section before eca block #+end_comment * treesit-auto ensures that every available tree sitter is downloaded and set up for you. #+begin_src emacs-lisp ;; (use-package treesit-auto ;; :ensure t ;; :defer t ;; :custom ;; (treesit-auto-install 'prompt) ;; :init ;; (delete 'org treesit-auto-langs) ;; :config ;; (treesit-auto-add-to-auto-mode-alist 'all) ;; (global-treesit-auto-mode t)) #+end_src * embark #+begin_src emacs-lisp (use-package embark :ensure 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 #+begin_src emacs-lisp (use-package embark-consult :ensure t :hook (embark-collect-mode . consult-preview-at-point-mode)) ;; Enable preview in Embark collect mode. #+end_src ** orderless #+begin_src emacs-lisp (use-package orderless :ensure t :defer t ;; Load Orderless on demand. :after vertico ;; Ensure Vertico is loaded before Orderless. :init (setq completion-styles '(orderless basic) ;; Set the completion styles. completion-category-defaults nil ;; Clear default category settings. completion-category-overrides '((file (styles partial-completion))))) ;; Customize file completion styles. #+end_src ** marginalia #+begin_src emacs-lisp (use-package marginalia :ensure t :hook (after-init . marginalia-mode) :config (setq marginalia-align 'center)) #+end_src * programming ** eglot #+begin_src emacs-lisp (use-package eglot :ensure nil :config ;; 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) (setq eglot-events-buffer-size 0) (setq eglot-report-progress nil) (setq jsonrpc-default-request-timeout 10) (add-hook 'eglot-managed-mode-hook (lambda () (eglot-inlay-hints-mode -1))) (setq eglot-ignored-server-capabilities '(:inlayhintprovider :documenthighlightprovider))) #+end_src *** settings #+begin_src emacs-lisp #+end_src *** eglot booster #+begin_src emacs-lisp ;; (use-package eglot-booster ;; :ensure (:repo "https://github.com/jdtsmith/eglot-booster") ;; :after eglot ;; :config (eglot-booster-mode)) #+end_src ** typst-mode #+begin_src emacs-lisp (use-package typst-ts-mode :ensure t :mode "\\.typ\\'" :hook (typst-ts-mode . eglot-ensure) :config (with-eval-after-load 'eglot (add-to-list 'eglot-server-programs `(typst-ts-mode . ,(eglot-alternatives '("tinymist" "lsp")))))) #+end_src ** yasnippet #+begin_src emacs-lisp (use-package yasnippet :ensure t :config (yas-global-mode 1)) (use-package yasnippet-snippets :ensure t :after yasnippet) #+end_src ** nix-ts-mode #+begin_src emacs-lisp (use-package nix-ts-mode :ensure t :mode "\\.nix\\'" :hook (nix-ts-mode . eglot-ensure) :config (with-eval-after-load 'eglot (add-to-list 'eglot-server-programs '(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. #+begin_src emacs-lisp (use-package python :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 python-mode) . ("rass" "--" "pyrefly" "lsp" "--" "ruff" "server"))))) #+end_src *** pyvenv #+begin_src emacs-lisp (use-package pyvenv :ensure t :config (setq pyvenv-mode-line-indicator '(pyvenv-virtual-env-name (" [venv:" pyvenv-virtual-env-name "] "))) (add-hook 'python-ts-mode-hook (lambda () (pyvenv-mode 1)))) #+end_src ** elm-mode #+begin_src emacs-lisp ;; (use-package elm-mode ;; :ensure t ;; :mode "\\.elm\\'" ;; :hook (elm-mode . eglot-ensure) ;; :config ;; (with-eval-after-load 'eglot-server-programs ;; '(elm-mode . ("elm-language-server")))) #+end_src ** Rust mode ily <3. #+begin_src emacs-lisp (use-package rust-ts-mode :ensure nil :mode "\\.rs\\'" :hook (rust-ts-mode . eglot-ensure) :config (with-eval-after-load 'eglot (add-to-list 'eglot-server-programs '(rust-ts-mode . ("rust-analyzer" :initializationOptions (:check (:command "clippy"))))))) #+end_src *** Rustic #+begin_src emacs-lisp ;; (use-package rustic ;; :ensure t ;; :config ;; (setq rustic-format-on-save nil) ;; (setq rustic-lsp-client nil) ;; :custom ;; (rustic-cargo-use-last-stored-arguments t)) #+end_src ** JavaScript / TypeScript / Vue JavaScript and TypeScript support via tree-sitter modes and [[https://github.com/joaotavora/rassumfrassum][rassumfrassum]]. The =rass tslint= preset multiplexes =typescript-language-server= + =eslint=. Vue files use =web-mode= with the =rass vuetail= preset (=vue-language-server= + =tailwindcss-language-server=). *** js-ts-mode #+begin_src emacs-lisp (use-package js :ensure nil :mode ("\\.js\\'" . js-ts-mode) :hook (js-ts-mode . eglot-ensure) :config (setq js-indent-level 2) (with-eval-after-load 'eglot (add-to-list 'eglot-server-programs '(js-ts-mode . ("rass" "--" "typescript-language-server" "--stdio" "--" "vscode-eslint-language-server" "--stdio"))))) #+end_src *** typescript-ts-mode Uses built-in tree-sitter modes for =.ts= and =.tsx= files. #+begin_src emacs-lisp (use-package typescript-ts-mode :ensure nil :mode (("\\.ts\\'" . typescript-ts-mode) ("\\.tsx\\'" . tsx-ts-mode)) :hook ((typescript-ts-mode . eglot-ensure) (tsx-ts-mode . eglot-ensure)) :config (with-eval-after-load 'eglot (add-to-list 'eglot-server-programs '((typescript-ts-mode tsx-ts-mode) . ("rass" "--" "typescript-language-server" "--stdio" "--" "vscode-eslint-language-server" "--stdio"))))) #+end_src *** web-mode (Vue) #+begin_src emacs-lisp (use-package web-mode :ensure t :mode "\\.vue\\'" :hook (web-mode . eglot-ensure) :config (setq web-mode-markup-indent-offset 2) (setq web-mode-css-indent-offset 2) (setq web-mode-code-indent-offset 2) (setq web-mode-style-padding 2) (setq web-mode-script-padding 2) (with-eval-after-load 'eglot (add-to-list 'eglot-server-programs '(web-mode . ("rass" "vuetail"))))) #+end_src *** mint-mode #+begin_src emacs-lisp (use-package mint-mode :ensure (:host github :repo "creatorrr/emacs-mint-mode") :mode "\\.mint\\'" :hook (mint-mode . eglot-ensure) :config (with-eval-after-load 'eglot (add-to-list 'eglot-server-programs '(mint-mode . ("mint" "tool" "ls"))))) #+end_src ** Nushell #+begin_src emacs-lisp ;; (use-package nushell-mode ;; :ensure nil ;; :mode "\\.nu\\'") #+end_src *** nushell org babel #+begin_src emacs-lisp ;; (use-package nushell-ts-babel ;; :ensure (:host github :repo "herbertjones/nushell-ts-babel") ;; :after org ;; :config ;; (org-babel-do-load-languages ;; 'org-babel-load-languages ;; (append org-babel-load-languages ;; '((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) #+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= The executor must also be installed inside the =ob-async= child process — it runs in a fresh Emacs that never loads this config, so a plain =defun= here would be void there. Stashing the installer in a variable lets both the parent and the =ob-async-pre-execute-src-block-hook= share one definition. #+begin_src emacs-lisp (defvar my/ob-xonsh-installer (lambda () (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)))))) "Defines `org-babel-execute:xonsh'; reused in ob-async child processes.") (with-eval-after-load 'org (funcall my/ob-xonsh-installer) (add-to-list 'org-src-lang-modes '("xonsh" . xonsh))) (with-eval-after-load 'ob-async (add-hook 'ob-async-pre-execute-src-block-hook my/ob-xonsh-installer)) #+end_src ** Go mode #+begin_src emacs-lisp (use-package go-mode :ensure t :mode "\\.go\\'" :hook (go-mode . eglot-ensure) :config (with-eval-after-load 'eglot (add-to-list 'eglot-server-programs '(go-mode . ("gopls" :initializationOptions (:staticcheck t :matcher "CaseSensitive" :usePlaceholders t))))) (defun go-mode-setup () (add-hook 'before-save-hook #'eglot-format-buffer -10 t) (add-hook 'before-save-hook (lambda () (when (eglot-managed-p) (eglot-code-action-organize-imports nil t))) nil t)) (add-hook 'go-mode-hook #'go-mode-setup)) #+end_src ** json-ts-mode #+begin_src emacs-lisp (use-package json-ts-mode :ensure nil :mode "\\.json\\'" :hook (json-ts-mode . eglot-ensure) :config (with-eval-after-load 'eglot (add-to-list 'eglot-server-programs '(json-ts-mode . ("vscode-json-language-server" "--stdio"))))) #+end_src ** yaml-ts-mode #+begin_src emacs-lisp (use-package yaml-ts-mode :ensure nil :mode "\\.ya?ml\\'" :hook (yaml-ts-mode . eglot-ensure)) #+end_src ** bash-ts-mode #+begin_src emacs-lisp (use-package bash-ts-mode :ensure nil :mode (("\\.sh\\'" . bash-ts-mode) ("\\.bash\\'" . bash-ts-mode) ("\\.bashrc\\'" . bash-ts-mode) ("\\.bash_profile\\'" . bash-ts-mode)) :hook (bash-ts-mode . eglot-ensure) :config (with-eval-after-load 'eglot (add-to-list 'eglot-server-programs '(bash-ts-mode . ("bash-language-server" "start")))) (defun sh-mode-setup () (add-hook 'before-save-hook #'eglot-format-buffer -10 t)) (add-hook 'bash-ts-mode-hook #'sh-mode-setup)) #+end_src ** jinja-mode #+begin_src emacs-lisp (use-package jinja2-mode :ensure t :defer t) #+end_src ** protobuf-mode #+begin_src emacs-lisp (use-package protobuf-mode :ensure (:host github :repo "protocolbuffers/protobuf" :files ("editors/protobuf-mode.el")) :mode ("\\.proto\\'" . protobuf-mode)) #+end_src ** janet-mode #+begin_src emacs-lisp (use-package janet-mode :mode "\\.janet\\'" :ensure t :config (with-eval-after-load 'eglot (add-to-list 'eglot-server-programs '(janet-mode . ("janet-lsp"))))) #+end_src ** just-mode #+begin_src emacs-lisp (use-package just-mode :ensure t :mode ("[Jj]ustfile\\'" "\\.just\\'") :hook (just-mode . eglot-ensure) :config (with-eval-after-load 'eglot (add-to-list 'eglot-server-programs '(just-mode . ("just-lsp"))))) ;; (use-package justl ;; :ensure t ;; :after just-mode) #+end_src * terminals ** eat #+begin_src emacs-lisp (use-package eat :ensure t :custom (eat-shell "xonsh") (eat-kill-buffer-on-exit t) (eat-enable-shell-prompt-annotation t) (eat-term-name "xterm-256color") :hook (eshell-load . eat-eshell-mode) (eshell-load . eat-eshell-visual-command-mode)) #+end_src * olivetti #+begin_src emacs-lisp (use-package olivetti :ensure t :hook (org-mode . olivetti-mode) :custom (olivetti-style nil) ; Use window margins (fringes disabled in early-init) (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 #+begin_src emacs-lisp (defun my-org-sidecar-left () "Pick an org-mode buffer via consult and display it as a left sidecar." (interactive) (let* ((bufs (cl-remove-if-not (lambda (b) (with-current-buffer b (derived-mode-p 'org-mode))) (buffer-list))) (names (mapcar #'buffer-name bufs)) (choice (consult--read names :prompt "Org sidecar: " :sort nil :require-match t :category 'buffer))) (when-let* ((buf (get-buffer choice))) (display-buffer-in-side-window buf '((side . left) (window-width . 0.25) (slot . 0)))))) #+end_src * visual-fill-column #+begin_src emacs-lisp ;; (use-package visual-fill-column ;; :ensure t ;; :hook ((text-mode . visual-line-mode) ;; Soft-Wrapping aktivieren ;; (text-mode . visual-fill-column-mode)) ;; Das Zentrieren aktivieren ;; :custom ;; (visual-fill-column-width 110) ;; Maximale Breite des Textes (statt 0.67 relativ) ;; (visual-fill-column-center-text nil) ;; (visual-fill-column-enable-sensible-window-split t) ;; :config ;; (when (display-graphic-p) ;; (setq-default visual-fill-column-center-text t))) #+end_src * hl-todo #+begin_src emacs-lisp (use-package hl-todo :ensure (:host github :repo "tarsius/hl-todo") :config (global-hl-todo-mode) (setq hl-todo-keyword-faces '(("FIXME" . "#FF4500") ("BUG" . "#FF0000") ("DEBUG" . "#A020F0") ("HACK" . "#E6DB74") ("TODO" . "#FF8C00") ("REVIEW" . "#2D9574") ("OPTIMIZE" . "#1E90FF") ("STUB" . "#87CEEB") ("DEPRECATED" . "#808080")))) #+end_src * magit #+begin_src emacs-lisp (use-package magit :ensure t :config (if ek-use-nerd-fonts (setopt magit-format-file-function #'magit-format-file-nerd-icons)) :custom (magit-display-buffer-function #'magit-display-buffer-same-window-except-diff-v1)) (defun my/magit-kill-buffers () (interactive) (let ((buffers (magit-mode-get-buffers))) (magit-restore-window-configuration) (mapc #'kill-buffer buffers))) (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 #+begin_src emacs-lisp (use-package indent-guide :defer t :ensure t :hook (prog-mode . indent-guide-mode) ;; Activate indent-guide in programming modes. :config (setq indent-guide-char "│")) ;; Set the character used for the indent guide. #+end_src * keybindings ** evil-mode #+begin_src emacs-lisp (use-package evil :ensure (:wait t) :init (setq evil-want-integration t) (setq evil-want-keybinding nil) (setq evil-want-C-u-delete t) (setq evil-want-C-u-scroll t) (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) (define-key evil-normal-state-map (kbd "C-r") 'undo-tree-redo) (evil-set-initial-state 'help-mode 'emacs) (evil-set-initial-state 'messages-buffer-mode 'normal) (evil-set-initial-state 'dired-mode 'normal) (evil-set-initial-state 'ibuffer-mode 'normal) (evil-set-initial-state 'erc-mode 'normal)) (modify-syntax-entry ?_ "w") (defun my/eldoc-and-jump () "Show documentation at point. Works in both GUI and terminal." (interactive) (if (display-graphic-p) (eldoc-box-help-at-point) (eldoc-doc-buffer t))) (define-key evil-normal-state-map (kbd "K") #'my/eldoc-and-jump) #+end_src *** evil yank/paste keymaps #+begin_src emacs-lisp ;; (setq select-enable-clipboard nil) ;; (setq select-enable-primary nil) ;; (evil-define-operator my/evil-yank-to-clipboard (beg end type register yank-handler) ;; :move-point nil ;; :repeat nil ;; (interactive "") ;; (let ((select-enable-clipboard t)) ;; (evil-yank beg end type register yank-handler))) ;; (defun my/evil-paste-from-clipboard () ;; (interactive) ;; (let ((select-enable-clipboard t)) ;; (evil-paste-before 1 ?+))) ;; (general-def '(normal visual) 'override ;; :prefix "SPC" ;; "yy" 'my/evil-yank-to-clipboard ;; "yp" 'my/evil-paste-from-clipboard) #+end_src ** smart-parens #+begin_src emacs-lisp (use-package smartparens :ensure t :init (smartparens-global-mode 1) :config (require 'smartparens-config)) (use-package evil-smartparens :ensure t :after evil-collection :hook (smartparens-global-mode . evil-smartparens-mode)) #+end_src ** evil-collection #+begin_src emacs-lisp (use-package evil-collection :after evil :ensure (:wait t) :config (evil-collection-init)) #+end_src ** evil-surround #+begin_src emacs-lisp (use-package evil-surround :ensure t :after evil-collection :config (global-evil-surround-mode 1)) #+end_src ** evil-matchit #+begin_src emacs-lisp (use-package evil-matchit :ensure t :after evil-collection :config (global-evil-matchit-mode 1)) #+end_src ** evil-textobjects #+begin_src emacs-lisp (use-package evil-textobj-anyblock :ensure t :after evil :config (define-key evil-inner-text-objects-map "b" 'evil-textobj-anyblock-inner-block) (define-key evil-outer-text-objects-map "b" 'evil-textobj-anyblock-a-block) (setq evil-textobj-anyblock-blocks '(("(" . ")") ("{" . "}") ("\\[" . "\\]") ("<" . ">")))) (evil-define-text-object my-evil-textobj-anyblock-inner-quote (count &optional beg end type) "Select the closest outer quote." (let ((evil-textobj-anyblock-blocks '(("'" . "'") ("\"" . "\"") ("`" . "'") ("“" . "”")))) (evil-textobj-anyblock--make-textobj beg end type count nil))) (evil-define-text-object my-evil-textobj-anyblock-a-quote (count &optional beg end type) "Select the closest outer quote." (let ((evil-textobj-anyblock-blocks '(("'" . "'") ("\"" . "\"") ("`" . "'") ("“" . "”")))) (evil-textobj-anyblock--make-textobj beg end type count t))) (define-key evil-inner-text-objects-map "q" 'my-evil-textobj-anyblock-inner-quote) (define-key evil-outer-text-objects-map "q" 'my-evil-textobj-anyblock-a-quote) #+end_src ** evil multiple cursor #+begin_src emacs-lisp (use-package evil-mc :ensure t :after evil :config (global-evil-mc-mode 1) (defun my/evil-visual-block-p () (and (bound-and-true-p evil-visual-selection) (eq evil-visual-selection 'block))) (defun my/evil-mc-visual-block-insert () "Erstellt Cursor am Anfang des Blocks und wechselt in Insert-Mode." (interactive) (if (my/evil-visual-block-p) (progn (evil-mc-make-cursor-in-visual-selection-beg) (evil-insert 1)) (call-interactively 'evil-insert))) (defun my/evil-mc-visual-block-append () "Erstellt Cursor am Ende des Blocks und wechselt in Insert-Mode." (interactive) (if (my/evil-visual-block-p) (progn (evil-mc-make-cursor-in-visual-selection-end) (evil-append 1)) ;; Fallback: normales Verhalten (call-interactively 'evil-append))) (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 #+begin_src emacs-lisp (use-package flash :ensure (:host github :repo "Prgebish/flash") :after evil :custom (flash-multi-window t) (flash-backdrop t) (flash-case-fold t) (flash-autojump t) (flash-highlight-matches t) (flash-label-position 'overlay) (flash-jump-position 'start) (flash-char-jump-labels t) (flash-char-multi-line nil) (flash-nohlsearch t) (flash-jumplist t) (flash-rainbow t) (flash-rainbow-shade 5) :config ;; Evil integration: binds gs in normal/visual/operator + enhanced f/t/F/T (require 'flash-evil) (flash-evil-setup t) (defun my/flash-visual-extend (orig match) (if (and match (bound-and-true-p evil-local-mode) (eq evil-state 'visual)) (let* ((origin (point)) (mpos (flash-match-pos-value match)) (forward (and mpos (>= mpos origin))) (flash-jump-position (if forward 'end 'start))) (prog1 (funcall orig match) (when (and forward (not (bolp)) (> (point) (point-min))) (backward-char)))) (funcall orig match))) (advice-add 'flash-jump-to-match :around #'my/flash-visual-extend) ;; Restore ; and , after flash-char overwrites them ;; ; is used as prefix for prev-navigation (;b, ;d) ;; , is the local leader (evil-define-key* '(normal visual motion) 'global (kbd ";") nil (kbd ",") nil) ;; Search integration: labels during C-s, /, ? (require 'flash-isearch) (flash-isearch-mode 1)) #+end_src * undo-tree #+begin_src emacs-lisp (use-package undo-tree :defer t :ensure t :hook (after-init . global-undo-tree-mode) :init (setq undo-tree-visualizer-timestamps t undo-tree-visualizer-diff t undo-tree-auto-save-history t undo-tree-history-directory-alist '(("." . "~/.emacs.d/undo-tree-history/")) undo-limit 800000 undo-strong-limit 12000000 undo-outer-limit 120000000) :config (setq undo-tree-mode-lighter "")) #+end_src * rainbow-delimiters #+begin_src emacs-lisp (use-package rainbow-delimiters :defer t :ensure t :hook (prog-mode . rainbow-delimiters-mode)) #+end_src * env-modes ** dotenv #+begin_src emacs-lisp (use-package dotenv-mode :defer t :ensure t :config) #+end_src * org agenda #+begin_src emacs-lisp (add-hook 'org-agenda-finalize-hook #'org-save-all-org-buffers) (use-package org-agenda :ensure nil :config (setq org-agenda-window-setup 'current-window org-agenda-inhibit-startup t org-agenda-use-tag-inheritance nil org-agenda-dim-blocked-tasks nil org-startup-indented nil org-startup-folded 'overview org-agenda-prefix-format '((agenda . " %i %?-12t% s") (todo . " %i ") (tags . " %i ") (search . " %i ")))) #+end_src * evil-org #+begin_src emacs-lisp (use-package evil-org :ensure t :after org :hook (org-mode . evil-org-mode) :config (require 'evil-org-agenda) (evil-org-agenda-set-keys)) #+end_src * nerd icons ** nerd icons dired #+begin_src emacs-lisp (use-package nerd-icons-dired :if ek-use-nerd-fonts ;; Load the package only if the user has configured to use nerd fonts. :ensure t ;; Ensure the package is installed. :defer t ;; Load the package only when needed to improve startup time. :hook (dired-mode . nerd-icons-dired-mode)) #+end_src ** diredfl #+begin_src emacs-lisp (use-package diredfl :ensure t :hook ((dired-mode . diredfl-mode) (dirvish-directory-view-mode . diredfl-mode))) #+end_src ** nerd-icons-completion #+begin_src emacs-lisp (use-package nerd-icons-completion :if ek-use-nerd-fonts ;; Load the package only if the user has configured to use nerd fonts. :ensure t ;; Ensure the package is installed. :after (:all nerd-icons marginalia) ;; Load after `nerd-icons' and `marginalia' to ensure proper integration. :config (nerd-icons-completion-mode) ;; Activate nerd icons for completion interfaces. (add-hook 'marginalia-mode-hook #'nerd-icons-completion-marginalia-setup)) ;; Setup icons in the marginalia mode for enhanced completion display. #+end_src * TODO efs install fn #+begin_src emacs-lisp (defun ek/first-install () "dired" (interactive) ;; Allow this function to be called interactively. (switch-to-buffer "*Messages*") ;; Switch to the *Messages* buffer to display installation messages. (message ">>> All required packages installed.") (message ">>> Configuring Emacs-Kick...") (message ">>> Configuring Tree Sitter parsers...") (require 'treesit-auto) (treesit-auto-install-all) ;; Install all available Tree Sitter grammars. (message ">>> Configuring Nerd Fonts...") (require 'nerd-icons) (nerd-icons-install-fonts) ;; Install all available nerd-fonts (message ">>> Emacs-Kick installed! Press any key to close the installer and open Emacs normally. First boot will compile some extra stuff :)") (read-key) ;; Wait for the user to press any key. (kill-emacs)) ;; Close Emacs after installation is complete. #+end_src * modelines ** hide modeline #+begin_src emacs-lisp (use-package hide-mode-line :ensure t) #+end_src ** punch-line #+begin_src emacs-lisp (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-music-info t) (punch-line-mode 1)) #+end_src * soph #+begin_src emacs-lisp (use-package adaptive-wrap :ensure t :hook ((text-mode . adaptive-wrap-prefix-mode)) :config (add-hook 'org-mode-hook (lambda () (when (bound-and-true-p adaptive-wrap-prefix-mode) (adaptive-wrap-prefix-mode -1))))) (setq package-gnupghome-dir "~/.gnupg") (blink-cursor-mode 0) (setq-default cursor-type 'box) (defvar cashmere/font-height 160) (set-face-attribute 'default nil :family "MapleMono NF" :height 180 :font (font-spec :family "MapleMono NF" :features '(cv04 ss05 zero))) (set-face-attribute 'fixed-pitch nil :family "MapleMono NF" :weight 'regular) (set-face-attribute 'variable-pitch nil :family "MapleMono NF" :weight 'regular :height 1.1) (use-package mixed-pitch :ensure t :defer t :hook ((org-mode . mixed-pitch-mode) (LaTeX-mode . mixed-pitch-mode))) #+end_src * essentials ** grip mode ** grip mode #+begin_src emacs-lisp (use-package grip-mode :ensure t :config (setq grip-command 'auto)) ;; auto, grip, go-grip or mdopen #+end_src * themes ** theme load #+begin_src emacs-lisp (add-to-list 'custom-theme-load-path "~/.emacs.d/themes/") (load-theme 'noctalia t) #+end_src * dirvish #+begin_src emacs-lisp (use-package dirvish :ensure t :init (dirvish-override-dired-mode) :custom (dirvish-quick-access-entries '(("h" "~/" "Home") ("d" "~/Downloads/" "Downloads") ;; ("p" "~/projects/" "Projects") )) (dirvish-reuse-session 'open) (dirvish-attributes '(file-size)) (dirvish-mode-line-format '(:left (sort file-time symlink) :right (omit yank index))) (dirvish-hide-details '(dirvish dirvish-side)) (dirvish-hide-cursor '(dirvish dirvish-side)) (dired-listing-switches "-l --almost-all --human-readable --group-directories-first --no-group") :config (setq dired-dwim-target t) (setq delete-by-moving-to-trash 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 #+begin_src emacs-lisp ;; (use-package tramp-hlo ;; :ensure (:host github :repo "jsadusk/tramp-hlo") ;; :config ;; (tramp-hlo-setup)) #+end_src * tramp-rpc #+begin_src emacs-lisp (add-to-list 'load-path "/home/cashmere/.emacs.d/tramp-rpc/lisp") (require 'tramp-rpc) #+end_src * zoxide #+begin_src emacs-lisp (use-package zoxide :ensure t) #+end_src * rsync #+begin_src emacs-lisp (use-package dired-rsync :ensure t) #+end_src ** rsync-transient #+begin_src emacs-lisp (use-package dired-rsync-transient :ensure t :after (dired-rsync transient)) #+end_src * my/fns #+begin_src emacs-lisp (defun my/reload-config () "Re-tangle config.org and reload the generated config.el in place. Applies changes to customs, hooks, keybindings, and variable settings without restarting Emacs. Packages that are newly added still require a restart since elpaca queues run at init time." (interactive) (let* ((org-file (expand-file-name "config.org" user-emacs-directory)) (el-file (expand-file-name "config.el" user-emacs-directory)) (start (current-time))) (message "Reloading config…") ;; 1. Tangle org → el (skips up-to-date blocks automatically) (org-babel-tangle-file org-file el-file "emacs-lisp") ;; 2. Load the freshly tangled file (load-file el-file) (message "Config reloaded in %.2fs" (float-time (time-since start))))) #+end_src #+begin_src emacs-lisp (setq-default olivetti-body-width 100) (define-globalized-minor-mode my/global-olivetti-mode olivetti-mode (lambda () (olivetti-mode 1))) ;; (my/centered-cursor) ;; (my/global-olivetti-mode) #+end_src * ibuffer hide #+begin_src emacs-lisp (setq ibuffer-never-show-predicates '(;; System buffers ;; "^\\*Messages\\*$" "^\\*scratch\\*$" "^\\*Completions\\*$" "^\\*Help\\*$" "^\\*Apropos\\*$" "^\\*info\\*$" "^\\*Async-native-compile-log\\*$" ;; LSP Buffers "^\\*lsp-log\\*$" "^\\*clojure-lsp\\*$" "^\\*clojure-lsp::stderr\\*$" "^\\*ts-ls\\*$" "^\\*ts-ls::stderr\\*$")) #+end_src * projectile #+begin_src emacs-lisp (use-package projectile :ensure (:wait t) :demand t :config (projectile-mode +1) (setq projectile-completion-system 'default projectile-enable-caching t projectile-indexing-method 'alien projectile-sort-order 'recentf projectile-require-project-root nil projectile-globally-ignored-buffers '("\\*magit.*")) (add-hook 'after-save-hook #'projectile-cache-current-file)) #+end_src ** consult projectile #+begin_src emacs-lisp (use-package consult-projectile :ensure t :after (consult projectile) :defer t :config (setq consult-project-function #'projectile-project-root)) #+end_src ** wgrep #+begin_src emacs-lisp (use-package wgrep :ensure t :defer t :custom (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 :commands (pass) :config (add-to-list 'display-buffer-alist '("\\*Pass.*\\*" (display-buffer-full-frame))) (with-eval-after-load 'evil (general-def 'normal pass-mode-map "q" 'quit-window "j" 'pass-next-entry "k" 'pass-prev-entry "RET" 'pass-view "d" 'pass-kill "y" 'pass-copy "Y" 'pass-copy-field "e" 'pass-edit "a" 'pass-insert "G" 'pass-insert-generated "o" 'pass-otp-options "r" 'pass-rename "/" 'isearch-forward) (general-def 'normal pass-view-mode-map "q" 'quit-window "t" 'pass-view-toggle-password "y" 'pass-copy "Y" 'pass-copy-field))) #+end_src ** auth-source #+begin_src emacs-lisp (use-package auth-source :ensure nil ;; This is built-in, no need to fetch it. :defer t :config ;; Enable password-store integration for secure API key retrieval. (when (require 'auth-source-pass nil t) (auth-source-pass-enable) ;; Add password-store explicitly to auth-sources for compatibility. (add-to-list 'auth-sources 'password-store) ;; Optional: Cache expires after 5 minutes for security. (setq auth-source-cache-expiry 300))) #+end_src * zoom #+begin_src emacs-lisp (use-package zoom :ensure t) (custom-set-variables '(zoom-size '(0.382 . 0.618))) #+end_src * golden-ratio #+begin_src emacs-lisp (use-package golden-ratio :ensure t :config ;; (setq golden-ratio-auto-scale t) ;; (golden-ratio-mode 1) ) #+end_src * pdf-tools #+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 #+begin_src emacs-lisp (defun my/format-buffer () (interactive) (cond ((eq major-mode 'rust-ts-mode) (eglot-format-buffer)) ((eq major-mode 'nix-ts-mode) (eglot-format-buffer)) ((eq major-mode 'python-ts-mode) (eglot-format-buffer)) ((eq major-mode 'c-mode) (eglot-format-buffer)) ((bound-and-true-p eglot--managed-mode) (eglot-format-buffer)) (t (message "No formatter for %s" major-mode)))) (my-leader "SPC" '((lambda () (interactive) (if (projectile-project-p) (consult-projectile-find-file) (projectile-switch-project))) :wk "find file/switch project") "sp" '(consult-projectile :wk "search project") "ss" '(consult-line :wk "search project") "/" '(consult-ripgrep :wk "search project") "." '(find-file :wk "find file") "," '(consult-buffer :wk "switch buffer") ":" (lambda () (interactive) (execute-extended-command nil)) "d" '(:ignore t :wk "denote") "dj" '(denote-journal-new-or-existing-entry :wk "journal") "dd" '(denote-menu-list-notes t :wk "List all notes") "dm" '(:ignore t :wk "Merge Notes") "dmr" '(denote-merge-region :wk "Merge Region") "dmf" '(denote-merge-file :wk "Merge File") "dg" '(consult-denote-grep t :wk "Search") "dl" '(denote-link-or-create t :wk "Link Note") "dn" '(denote t :wk "Create a new note") "dr" '(denote-rename-file t :wk "Rename Note") "dtl" '(tmr-list-timers :wk "list timer") "dtt" '(tmr :wk "set timer") "f" '(:ignore t :wk "files") "fd" '(dired-jump :wk "dired") "fD" '(dired-jump :wk "dired jump") "fr" '(consult-recent-file :wk "recent files") "ff" '(find-file :wk "find file") "fs" '(save-buffer :wk "save file") "b" '(:ignore t :wk "buffer/bookmarks") "bb" '(consult-bookmark :wk "display current bookmarks") "bi" '(ibuffer :wk "ibuffer") "bp" '(projectile-ibuffer :wk "ibuffer project") "bd" '(bookmark-delete :wk "delete bookmark") "bk" '(kill-current-buffer :wk "kill buffer") "bs" '(bookmark-set :wk "save bookmark") "p" '(:ignore t :wk "project") "pp" '(projectile-switch-project :wk "switch project") "pf" '(projectile-find-file :wk "find file") "ps" '(consult-ripgrep :wk "search") "pb" '(consult-projectile-buffer :wk "buffers") "pk" '(projectile-kill-buffers :wk "kill buffers") "pd" '(projectile-remove-known-project :wk "delete project") "pr" '(my/project-replace :wk "project replace (wgrep)") "pa" '(projectile-add-known-project :wk "add project") "pi" '(projectile-invalidate-cache :wk "invalidate cache") "g" '(:ignore t :wk "git") "gc" '(magit-clone :wk "clone") "gg" '(magit-status :wk "status") "gl" '(magit-log-current :wk "log") "gi" '(magit-init :wk "init") "gd" '(xref-find-definitions :wk "go to definition") "gD" '((lambda () (interactive) (let ((current-prefix-arg 4)) (call-interactively #'xref-find-definitions))) :wk "definition other window") "gI" '((lambda () (interactive) (let ((current-prefix-arg 4)) (call-interactively #'eglot-find-implementation))) :wk "implementation other window") "gt" '(eglot-find-typeDefinition :wk "go to type definition") "gr" '(xref-find-references :wk "find references") "gs" '(magit-file-stage :wk "stage file") "gb" '(vc-annotate :wk "blame") "gT" '(my/code-todos-harvest :wk "harvest code TODOs") "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") "hf" '(describe-function :wk "function") "hv" '(describe-variable :wk "variable") "hk" '(describe-key :wk "key") "ht" '(consult-theme :wk "load theme") "w w" '(evil-window-next :wk "Close window") "w c" '(evil-window-delete :wk "Close window") "w o" '(delete-other-windows :wk "Maximize window") "w n" '(evil-window-new :wk "New window") "w s" '(evil-window-split :wk "Horizontal split window") "w v" '(evil-window-vsplit :wk "Vertical split window") "w h" '(evil-window-left :wk "Window left") "w j" '(evil-window-down :wk "Window down") "w k" '(evil-window-up :wk "Window up") "w l" '(evil-window-right :wk "Window right") "w w" '(evil-window-next :wk "Goto next window") "w H" '(buf-move-left :wk "Buffer move left") "w J" '(buf-move-down :wk "Buffer move down") "w K" '(buf-move-up :wk "Buffer move up") "w L" '(buf-move-right :wk "Buffer move right") "c" '(:ignore t :wk "code") "ca" '(eglot-code-actions :wk "code actions") "cr" '(eglot-rename :wk "lsp rename") "cf" '(eglot-format :wk "format buffer") "cs" '(yas-insert-snippet :wk "snippets") "cl" '(flycheck-list-errors :wk "list errors") "q" '(:ignore t :wk "quit") "qq" '(save-buffers-kill-terminal :wk "quit emacs") "qr" '(restart-emacs :wk "restart") "hr" '(my/reload-config :wk "reload config") "x" '(org-capture :wk "capture") ";" '(embark-act :wk "embark") "u" '(undo-tree-visualize :wk "undo tree") "P" '(consult-yank-from-kill-ring :wk "paste history") "t" '(:ignore t :wk "treesitter") "ts" '(flash-treesitter :wk "flash treesitter") "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)) (not (eq major-mode 'dirvish-mode)))) (defun my/s-key-dispatch () (interactive) (if (derived-mode-p 'magit-mode) (call-interactively 'magit-stage) (when (my/flash-enabled-p) (let ((scroll-margin 0) (maximum-scroll-margin 0)) (call-interactively 'flash-evil-jump))))) (general-def '(normal visual operator) 'override "s" 'my/s-key-dispatch) (general-def 'normal 'override "K" 'my/eldoc-and-jump "'d" 'flycheck-next-error ";d" 'flycheck-previous-error "]c" 'diff-hl-next-hunk "[c" 'diff-hl-previous-hunk "'b" 'switch-to-next-buffer ";b" 'switch-to-prev-buffer "]t" 'tab-next "[t" 'tab-previous "P" 'consult-yank-from-kill-ring ;; "?" 'casual-avy-tmenu "gcc" (lambda () (interactive) (unless (use-region-p) (comment-or-uncomment-region (line-beginning-position) (line-end-position))))) (general-def 'visual 'override "gc" (lambda () (interactive) (when (use-region-p) (comment-or-uncomment-region (region-beginning) (region-end))))) #+end_src ** org mode keymaps #+begin_src emacs-lisp (my-leader :keymaps 'org-mode-map "m" '(:ignore :wk "org") "mt" '(org-todo :wk "TODO") "ma" '(org-add-note :wk "add note") "mC" '(org-capture :wk "capture") "mc" '(:wk "set" :ignore) "mcd" '(org-deadline :wk "deadline") "mcs" '(org-schedule :wk "schedule") "mce" '(org-set-effort :wk "effort") "mcr" '(org-clock-report :wk "clock report") "m," '(org-priority :wk "priority") "mI" '(org-clock-in :wk "clock in") "mO" '(org-clock-out :wk "clock out") "l" '(:ignore :wk "link") "lc" '(org-cliplink :wk "cliplink") "li" '(org-download-clipboard :wk "image") "ll" '(org-insert-link :wk "link various things") "n" '(org-toggle-narrow-to-subtree :wk "narrow")) (evil-define-key 'normal org-mode-map (kbd "RET") 'org-open-at-point) #+end_src ** jinja2-mode keymaps #+begin_src emacs-lisp ;; jinja2-mode-map (my-leader :keymaps 'jinja2-mode-map "m" '(:wk "insert" :ignore) "mv" '(jinja2-insert-var :wk "var") "mt" '(jinja2-insert-tag :wk "tag") "mc" '(jinja2-insert-comment :wk "comment") "mf" '(fill-paragraph :wk "fill paragraph")) #+end_src ** rust-ts-mode keymaps #+begin_src emacs-lisp (my-leader :keymaps '(rust-ts-mode-map) "m" '(:wk "rust mode" :ignore) "mr" '(rust-run :wk "run") "mc" '(rust-run-clippy :wk "clippy") "mC" '(rust-check :wk "check")) #+end_src ** denote-menu keymaps #+begin_src emacs-lisp (with-eval-after-load 'denote-menu (general-def 'normal denote-menu-mode-map "r" 'denote-menu-filter "c" 'denote-menu-clear-filters "e" 'denote-menu-export-to-dired "o" 'denote-menu-filter-out-keyword)) #+end_src ** tmr keynmaps #+begin_src emacs-lisp (with-eval-after-load 'tmr (general-def 'normal tmr-tabulated-mode-map "y" 'tmr-clone "c" 'tmr-cancel "d" 'tmr-remove "D" 'tmr-remove-finished "n" 'tmr "N" 'tmr-with-details "e" 'tmr-edit-description "r" 'tmr-reschedule)) #+end_src ** dirvish keymaps #+begin_src emacs-lisp (defun dirvish-next-file (arg) "Move down ARG lines, landing on the filename column. Uses raw line motion so hidden detail lines (permissions, owner, date) and the dired header are never skipped." (interactive "^p") (forward-line arg) (dired-move-to-filename)) (defun dirvish-prev-file (arg) "Move up ARG lines, landing on the filename column." (interactive "^p") (forward-line (- arg)) (dired-move-to-filename)) (general-def 'normal dirvish-mode-map "?" 'dirvish-dispatch "q" 'dirvish-quit "b" 'dirvish-quick-access "f" 'dirvish-file-info-menu "p" 'dirvish-yank "S" 'dirvish-quicksort "F" 'dirvish-layout-toggle "z" 'zoxide-travel "j" 'dirvish-next-file "k" 'dirvish-prev-file "gh" 'dirvish-subtree-up "gl" 'dirvish-subtree-toggle "h" 'dired-up-directory "l" 'dired-find-file "TAB" 'dirvish-subtree-toggle "[h" 'dirvish-history-go-backward "]h" 'dirvish-history-go-forward) (general-def '(normal visual) dirvish-mode-map :prefix "y" "l" 'dirvish-copy-file-true-path "n" 'dirvish-copy-file-name "p" 'dirvish-copy-file-path "y" 'dired-do-copy) (general-def 'normal dirvish-mode-map :prefix "s" "s" 'dirvish-symlink "S" 'dirvish-relative-symlink "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 (my-local-leader "a" '(org-agenda :wk "org agenda") "c" '(my/centered-cursor :wk "center cursor") "f" '(dirvish :wk "file manager") "r" '(async-shell-command :wk "run async") "t" '(ghostel-project :wk "terminal (project)") "T" '(ghostel-list-buffers :wk "terminal (switch)") "z" '(golden-ratio-mode :wk "zoom/golden ratio") "o" '(my/global-olivetti-mode :wk "center buffer") "s" '(my-org-sidecar-left :wk "org sidecar")) #+end_src ** convenient keys #+begin_src emacs-lisp (global-set-key (kbd "C-=") 'text-scale-increase) (global-set-key (kbd "C--") 'text-scale-decrease) #+end_src ** yanking #+begin_src emacs-lisp ;; (defun my/evil-delete-to-blackhole (orig-fn beg end &optional type register &rest args) ;; (apply orig-fn beg end type ?_ args)) ;; (advice-add 'evil-delete :around 'my/evil-delete-to-blackhole) ;; (my-leader ;; "y" '(my/yank-to-clipboard :wk "clipboard" :ignore t) ;; "yy" '(my/yank-to-clipboard :wk "yank to clipboard") ;; "yp" '(my/paste-from-clipboard :wk "paste from clipboard")) #+end_src *** explicit yanking/pasting #+begin_src emacs-lisp ;; (defun my/yank-to-clipboard () ;; (interactive) ;; (if (region-active-p) ;; (let ((select-enable-clipboard t)) ;; (kill-ring-save (region-beginning) (region-end)) ;; (message "Yanked to clipboard")) ;; (message "No region active"))) ;; (defun my/paste-from-clipboard () ;; (interactive) ;; (let ((select-enable-clipboard t)) ;; (yank))) ;; (defun my/delete-to-clipboard () ;; (interactive) ;; (if (region-active-p) ;; (let ((select-enable-clipboard t)) ;; (kill-region (region-beginning) (region-end)) ;; (message "Deleted to clipboard")) ;; (message "No region active"))) #+end_src * elfeed #+begin_src emacs-lisp (use-package elfeed :ensure (:host github :repo "emacs-elfeed/elfeed" :branch "main") :defer t :custom (elfeed-db-directory "~/.emacs.d/elfeed") :config (setq elfeed-search-filter "@2-weeks-ago +unread" elfeed-search-title-max-width 110) (setq elfeed-use-libxml 't) (add-hook 'elfeed-search-mode-hook #'elfeed-update) (setq elfeed-search-sort-function (lambda (a b) (let* ((a-date (elfeed-entry-date a)) (b-date (elfeed-entry-date b)) (a-feed (elfeed-feed-title (elfeed-entry-feed a))) (b-feed (elfeed-feed-title (elfeed-entry-feed b))) (a-title (elfeed-entry-title a)) (b-title (elfeed-entry-title b))) (cond ((> a-date b-date) t) ((< a-date b-date) nil) ((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 :custom (rmh-elfeed-org-files (list "~/org/rss.org")) :config (elfeed-org)) (use-package elfeed-goodies :ensure t :after elfeed :config (elfeed-goodies/setup)) (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) (require 'elfeed-db) (elfeed-db-load) (let ((entries (hash-table-values elfeed-db-entries))) (with-temp-file output-file (insert "id,title,link,date,feed,tags\n") (dolist (entry entries) (insert (format "\"%s\",\"%s\",\"%s\",%s,\"%s\",\"%s\"\n" (elfeed-entry-id entry) (replace-regexp-in-string "\"" "\"\"" (elfeed-entry-title entry)) (elfeed-entry-link entry) (elfeed-entry-date entry) (elfeed-feed-title (elfeed-entry-feed entry)) (mapconcat #'symbol-name (elfeed-entry-tags entry) " ")))) (message "Export abgeschlossen: %d Einträge" (length entries))))) #+end_src ** set default browser #+begin_src emacs-lisp (setq browse-url-browser-function 'browse-url-generic 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) (shrface-default-keybindings)) #+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 (use-package mu4e :ensure nil :defer t :commands (mu4e mu4e-compose-new) :config (setq mu4e-mu-binary (executable-find "mu")) (setq mu4e-split-view 'vertical) (setq mu4e-thread-mode t) (setq mu4e-maildir "~/Mails") (setq mu4e-get-mail-command (concat (executable-find "mbsync") " -a")) (setq mu4e-update-interval 300) (setq mu4e-attachment-dir "~/Downloads") (setq mu4e-change-filenames-when-moving t) (setq mu4e-user-mail-address-list '("cashmeresamurai@autistici.org" "cashmere@cashmere.rs")) (setq mu4e-contexts `(,(make-mu4e-context :name "autistici" :match-func (lambda (msg) (when msg (string-prefix-p "/autistici" (mu4e-message-field msg :maildir)))) :vars '((user-mail-address . "cashmeresamurai@autistici.org") (user-full-name . "cashmere") (mu4e-drafts-folder . "/autistici/Drafts") (mu4e-sent-folder . "/autistici/Sent") (mu4e-trash-folder . "/autistici/Trash") (mu4e-refile-folder . "/autistici/Archive"))) ,(make-mu4e-context :name "cashmere" :match-func (lambda (msg) (when msg (string-prefix-p "/cashmere/cashmere" (mu4e-message-field msg :maildir)))) :vars '((user-mail-address . "cashmere@cashmere.rs") (user-full-name . "cashmere") (mu4e-drafts-folder . "/cashmere/cashmere/Drafts") (mu4e-sent-folder . "/cashmere/cashmere/Sent") (mu4e-trash-folder . "/cashmere/cashmere/Trash") (mu4e-refile-folder . "/cashmere/cashmere/Archive"))))) (setq mu4e-context-policy 'pick-first) (setq mu4e-compose-context-policy 'ask) (setq sendmail-program (executable-find "msmtp")) (setq send-mail-function 'sendmail-send-it) (setq message-send-mail-function 'sendmail-send-it) (setq message-sendmail-envelope-from 'header) (setq message-kill-buffer-on-exit t) (defun mu4e-set-msmtp-account () (if (message-mail-p) (save-excursion (let* ((from (save-restriction (message-narrow-to-headers) (message-fetch-field "from"))) (account (cond ((string-match "cashmeresamurai@autistici.org" from) "autistici") ((string-match "cashmere@cashmere.rs" from) "cashmere/cashmere")))) (setq message-sendmail-extra-arguments (list '"-a" account)))))) (add-hook 'message-send-mail-hook 'mu4e-set-msmtp-account)) #+end_src ** mu4e-alerts #+begin_src emacs-lisp (use-package mu4e-alert :ensure t :after mu4e :config ;; Use libnotify for desktop notifications (notify-send / swaync) (mu4e-alert-set-default-style 'libnotify) ;; Interesting mail query: unread, exclude trash and sent folders (setq mu4e-alert-interesting-mail-query (concat "flag:unread AND NOT flag:trashed" " AND NOT maildir:\"/autistici/Sent\"" " AND NOT maildir:\"/cashmere/cashmere/Sent\"")) ;; Show both count and per-sender subject notifications (setq mu4e-alert-email-notification-types '(count subjects)) ;; Group desktop notifications by maildir (per-account grouping) (setq mu4e-alert-group-by :maildir) ;; Disable X11 urgency hints (does not work on Wayland) (setq mu4e-alert-set-window-urgency nil) ;; Enable desktop notifications (mu4e-alert-enable-notifications) ;; Enable mode-line data updates (populates mu4e-alert-mode-line variable). ;; We strip the default global-mode-string entry afterwards since we use ;; a custom maple-modeline segment instead. (mu4e-alert-enable-mode-line-display) (setq global-mode-string (delete '(:eval mu4e-alert-mode-line) global-mode-string))) #+end_src * color-tools this package provides the ability to display rgb and hex colors. #+begin_src emacs-lisp (use-package denote-project-notes :ensure t :after org :config (setq denote-project-notes-identifier '(project stayem))) #+end_src * emacs-everywhere #+begin_src emacs-lisp (use-package emacs-everywhere :ensure t) #+end_src * profiler #+begin_src emacs-lisp ;; (profiler-start 'cpu) ;; (org-agenda nil "c") ;; (profiler-report) ;; (profiler-stop) #+end_src * arrow.el #+begin_src emacs-lisp (use-package arrow :ensure t :elpaca (arrow :host github :repo "vmargb/arrow.el") :config (setq arrow-mode 1)) #+end_src * colorful-mode #+begin_src emacs-lisp (use-package colorful-mode ;; :diminish :ensure t ; Optional :custom (colorful-use-prefix t) (colorful-only-strings 'only-prog) (css-fontify-colors nil) :config (global-colorful-mode t) (add-to-list 'global-colorful-modes 'helpful-mode)) #+end_src * gleam #+begin_src emacs-lisp (use-package gleam-ts-mode :ensure t :mode "\\.gleam\\'" :hook (gleam-ts-mode . eglot-ensure) :config (add-to-list 'eglot-server-programs '(gleam-ts-mode . ("gleam" "lsp")))) #+end_src * 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 :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 * =tldr.el= #+begin_src emacs-lisp (use-package tldr :ensure t) #+end_src * 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 * ghost.el #+begin_src emacs-lisp (use-package ghostel :ensure t :custom (ghostel-shell "xonsh") :config (evil-define-key 'normal ghostel-mode-map (kbd "g t") #'ghostel-next (kbd "g T") #'ghostel-previous (kbd "g n") #'ghostel (kbd "g b") #'ghostel-list-buffers) (advice-add 'enable-theme :after (lambda (&rest _) (when (fboundp 'ghostel-sync-theme) (ghostel-sync-theme))))) (use-package evil-ghostel :ensure (:host github :repo "dakra/ghostel" :files ("extensions/evil-ghostel/*.el")) :after (ghostel evil) :hook (ghostel-mode . evil-ghostel-mode)) #+end_src * garden #+begin_src emacs-lisp (use-package vui :ensure (:host github :repo "d12frosted/vui.el" :files ("*.el"))) (add-to-list 'load-path (expand-file-name "~/garden/lisp/")) (setq garden-directory (expand-file-name "~/garden/")) (require 'garden-core) (garden-auto-index-mode 1) (autoload 'garden "garden-dashboard" nil t) (autoload 'garden-sidecar "garden-dashboard" nil t) (autoload 'garden-connect "garden-connect" nil t) (autoload 'garden-connect-pick "garden-connect" nil t) (autoload 'garden-player "garden-player" nil t) (autoload 'denote-capf-setup "denote-capf" nil t) (add-hook 'org-mode-hook #'denote-capf-setup) (autoload 'garden-publish "garden-publish" nil t) (autoload 'garden-publish-all "garden-publish" nil t) (autoload 'garden-publish-wiki "garden-publish" nil t) (autoload 'garden-publish-blog "garden-publish" nil t) (autoload 'garden-publish-deploy "garden-publish" nil t) #+end_src * easysessions =easysession-setup= restores the last session at startup and enables easysession-save-mode= for periodic auto-save. It is elpaca- and daemon-aware, so it picks =elpaca-after-init-hook= and per-frame restore on the daemon automatically. mu4e- and ERC-buffers are process-backed and cannot be reconstructed from a file path, so easysession skips them. =my/easysession-restore-services= runs on =easysession-after-load-hook= and re-launches their entry points (=mu4e= in the background, =run-irc=) idempotently — guarded by =mu4e-running-p= and =erc-buffer-list= so switching between sessions never spawns a second instance or reconnects. #+begin_src emacs-lisp (defun my/easysession-restore-services () "Re-launch process-backed apps (mu4e, ERC) after a session is loaded." (unless (and (fboundp 'mu4e-running-p) (mu4e-running-p)) (mu4e t)) (unless (and (fboundp 'erc-buffer-list) (erc-buffer-list)) (run-irc))) (use-package easysession :ensure t :demand t :custom (easysession-save-interval (* 10 60)) :config (add-hook 'easysession-after-load-hook #'my/easysession-restore-services) (easysession-setup)) (my-local-leader "e" '(:wk "easy session" :ignore 't) "ew" '(easysession-switch-to :wk "switch / create") "es" '(easysession-save :wk "save") "el" '(easysession-load :wk "load") "eg" '(easysession-switch-to-and-restore-geometry :wk "switch + geometry") "er" '(easysession-rename :wk "rename") "ed" '(easysession-delete :wk "delete") "ee" '(easysession-edit :wk "edit") "eR" '(easysession-reset :wk "reset to default") "em" '(easysession-save-mode :wk "toggle auto-save")) #+end_src * sops #+begin_src emacs-lisp (use-package sops :ensure (:type git :host github :repo "djgoku/sops") :init (global-sops-mode 1)) #+end_src * provide init #+begin_src emacs-lisp (provide 'init) #+end_src