diff options
| -rw-r--r-- | config.el | 725 | ||||
| -rw-r--r-- | config.org | 808 | ||||
| -rw-r--r-- | custom-vars.el | 25 | ||||
| -rw-r--r-- | elfeed/index | 2 | ||||
| -rw-r--r-- | org-clock-save.el | 4 | ||||
| -rw-r--r-- | themes/noctalia-theme.el | 66 |
6 files changed, 1313 insertions, 317 deletions
@@ -27,6 +27,55 @@ (setq kept-new-versions 5) (setq kept-old-versions 5) +;;; 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)))) + (use-package emacs :ensure nil :custom @@ -59,6 +108,8 @@ (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) @@ -111,6 +162,7 @@ (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) @@ -123,7 +175,7 @@ ;; 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! + ";; Welcome to Emacs! ;; ;; Loading time : %s ;; Packages : %s @@ -221,6 +273,7 @@ (use-package async :ensure t) +(when (my/load-for-hub-p) (use-package erc :ensure nil :defer t @@ -231,7 +284,7 @@ ;; behaviour (erc-join-buffer 'buffer) - (erc-kill-buffer-on-part t) + (erc-kill-buffer-on-part nil) (erc-kill-queries-on-quit t) (erc-kill-server-buffer-on-quit t) @@ -405,6 +458,17 @@ Temporarily disables notifications during the fetch." (when (bound-and-true-p persp-mode) (persp-add-buffer (current-buffer))))) + ;; Prevent accidental ERC buffer kills from parting channels + (add-hook 'kill-buffer-query-functions + (lambda () + (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))) + (general-def 'normal erc-mode-map "q" 'quit-window "Q" 'my/erc-quit-all @@ -421,6 +485,15 @@ Temporarily disables notifications during the fetch." "M-n" 'erc-track-switch-buffer "C-k" 'erc-previous-command "C-j" 'erc-next-command)) +) ;; end (when (my/load-for-hub-p) ...) + +;; 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)) ;; (use-package lambda-themes ;; :ensure (:host github :repo "lambda-emacs/lambda-themes") @@ -590,6 +663,7 @@ Temporarily disables notifications during the fetch." (mode . shell-script-mode) (mode . shell-mode) (mode . sh-mode) + (mode . bash-ts-mode) (mode . lua-mode) (mode . bat-mode))) ("Config" (or @@ -601,7 +675,7 @@ Temporarily disables notifications during the fetch." (name . "^\\.gitignore$") (name . "^Doxyfile$") (name . "^config\\.toml$") - (mode . yaml-mode))) + (mode . yaml-ts-mode))) ("Web" (or (mode . mhtml-mode) (mode . html-mode) @@ -632,6 +706,10 @@ Temporarily disables notifications during the fetch." ("Fundamental" (or (mode . fundamental-mode) (mode . text-mode))) + ("ECA - Editor Code Assistant" + (or + (mode . eca-chat-mode))) + ("Emacs" (or (mode . emacs-lisp-mode) (name . "^\\*Help\\*$") @@ -741,7 +819,89 @@ Temporarily disables notifications during the fetch." (use-package eca :ensure (:host github :repo "editor-code-assistant/eca-emacs" :files ("*.el")) - :defer t) + :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-<return>" '(newline :wk "newline") + "C-<up>" '(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")))) (use-package eldoc :ensure nil @@ -789,12 +949,11 @@ Temporarily disables notifications during the fetch." :ensure t :config (setq flycheck-rust-check-tests nil) - (add-hook 'rust-mode-hook #'flycheck-rust-setup) (add-hook 'rust-ts-mode-hook #'flycheck-rust-setup)) (use-package flycheck-python-ruff :ensure (:host github :repo "v4n6/flycheck-python-ruff") - :hook ((python-mode python-ts-mode) . flycheck-python-ruff-setup)) + :hook (python-ts-mode . flycheck-python-ruff-setup)) (use-package flycheck-eglot :ensure t @@ -903,29 +1062,44 @@ Temporarily disables notifications during the fetch." -(use-package org-modern - :ensure t - :hook (org-mode . org-modern-mode) - :config - (setq org-modern-hide-stars t) - (setq org-modern-star 'replace) - (setq org-modern-fold-stars '(("◉" . "○"))) - (setq org-modern-replace-stars "◉○◉○◉") - (global-org-modern-mode)) +;; (use-package org-modern +;; :ensure t +;; :hook (org-mode . org-modern-mode) +;; :config +;; (setq org-modern-hide-stars t) +;; (setq org-modern-star 'replace) +;; (setq org-modern-fold-stars '(("◉" . "○"))) +;; (setq org-modern-replace-stars "◉○◉○◉") +;; (global-org-modern-mode)) -(use-package kitty-graphics - :ensure (:host github :repo "cashmeredev/kitty-graphics.el") - :if (and (not (display-graphic-p)) (getenv "KITTY_PID")) - :config - (kitty-graphics-mode 1)) +(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") + +;; (use-package kitty-graphics +;; :ensure (:host github :repo "cashmeredev/kitty-graphics.el") +;; :if (and (not (display-graphic-p)) (getenv "KITTY_PID")) +;; :config +;; (kitty-graphics-mode 1)) ;; (with-eval-after-load 'dired ;; (define-key dired-mode-map (kbd "P") #'kitty-gfx-dired-preview)) -;; (when (and (not (display-graphic-p)) (getenv "KITTY_PID")) -;; (load "~/projects/kitty-graphics/kitty-graphics.el") -;; (kitty-graphics-mode 1) -;; (with-eval-after-load 'dired -;; (define-key dired-mode-map (kbd "P") #'kitty-gfx-dired-preview))) +(when (and (not (display-graphic-p)) + (or (getenv "KITTY_PID") ;; Kitty terminal + (string-prefix-p "foot" (or (getenv "TERM") "")))) ;; foot terminal (Sixel) + (load "~/projects/kitty-graphics/kitty-graphics.el") + (kitty-graphics-mode 1) + (with-eval-after-load 'dired + (define-key dired-mode-map (kbd "P") #'kitty-gfx-dired-preview))) (use-package org-fancy-priorities :ensure t @@ -998,6 +1172,27 @@ Temporarily disables notifications during the fetch." :ensure t :after org) +(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))) + (defun my/snip-upload (&optional file) "Upload FILE, region, or current buffer to snips.sh. Passes -ext flag to hint the file type for syntax highlighting. @@ -1631,19 +1826,19 @@ Skips capture tasks and projects." ("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) +;; (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 --- @@ -2249,34 +2444,30 @@ completion-category-overrides '((file (styles partial-completion))))) ;; Customi :ensure t :after yasnippet) -(use-package nix-mode +(use-package nix-ts-mode :ensure t :mode "\\.nix\\'" - :hook (nix-mode . eglot-ensure) + :hook (nix-ts-mode . eglot-ensure) :config (with-eval-after-load 'eglot (add-to-list 'eglot-server-programs - '(nix-mode . ("nixd"))))) - -(use-package envrc - :ensure t - :hook (after-init . envrc-global-mode)) + '(nix-ts-mode . ("nixd"))))) (use-package python :ensure nil - :mode ("\\.py\\'" . python-mode) - :hook (python-mode . eglot-ensure) + :mode ("\\.py\\'" . python-ts-mode) + :hook (python-ts-mode . eglot-ensure) :config (with-eval-after-load 'eglot (add-to-list 'eglot-server-programs - '(python-mode . ("rass" "--" "pyrefly" "lsp" "--" "ruff" "server"))))) + '(python-ts-mode . ("rass" "--" "pyrefly" "lsp" "--" "ruff" "server"))))) (use-package pyvenv :ensure t :config (setq pyvenv-mode-line-indicator '(pyvenv-virtual-env-name (" [venv:" pyvenv-virtual-env-name "] "))) - (add-hook 'python-mode-hook + (add-hook 'python-ts-mode-hook (lambda () (pyvenv-mode 1)))) @@ -2288,14 +2479,14 @@ completion-category-overrides '((file (styles partial-completion))))) ;; Customi ;; (with-eval-after-load 'eglot-server-programs ;; '(elm-mode . ("elm-language-server")))) -(use-package rust-mode - :ensure t +(use-package rust-ts-mode + :ensure nil :mode "\\.rs\\'" - :hook (rust-mode . eglot-ensure) + :hook (rust-ts-mode . eglot-ensure) :config (with-eval-after-load 'eglot (add-to-list 'eglot-server-programs - '((rust-ts-mode rust-mode) . + '(rust-ts-mode . ("rust-analyzer" :initializationOptions (:check (:command "clippy"))))))) @@ -2309,13 +2500,13 @@ completion-category-overrides '((file (styles partial-completion))))) ;; Customi (use-package js :ensure nil - :mode ("\\.js\\'" . js-mode) - :hook (js-mode . eglot-ensure) + :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-mode . ("rass" "--" "typescript-language-server" "--stdio" "--" "vscode-eslint-language-server" "--stdio"))))) + '(js-ts-mode . ("rass" "--" "typescript-language-server" "--stdio" "--" "vscode-eslint-language-server" "--stdio"))))) (use-package typescript-ts-mode :ensure nil @@ -2326,7 +2517,7 @@ completion-category-overrides '((file (styles partial-completion))))) ;; Customi :config (with-eval-after-load 'eglot (add-to-list 'eglot-server-programs - '(js-mode . ("rass" "--" "typescript-language-server" "--stdio" "--" "vscode-eslint-language-server" "--stdio"))))) + '((typescript-ts-mode tsx-ts-mode) . ("rass" "--" "typescript-language-server" "--stdio" "--" "vscode-eslint-language-server" "--stdio"))))) (use-package web-mode :ensure t @@ -2364,55 +2555,57 @@ completion-category-overrides '((file (styles partial-completion))))) ;; Customi ;; (append org-babel-load-languages ;; '((nushell . t))))) -;; (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))))) +(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)) + (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)) + (add-hook 'go-mode-hook #'go-mode-setup)) -(use-package json-mode - :ensure t +(use-package json-ts-mode + :ensure nil :mode "\\.json\\'" - :hook (json-mode . eglot-ensure) + :hook (json-ts-mode . eglot-ensure) :config (with-eval-after-load 'eglot (add-to-list 'eglot-server-programs - '(json-mode . ("vscode-json-language-server" "--stdio"))))) + '(json-ts-mode . ("vscode-json-language-server" "--stdio"))))) -(use-package yaml-mode - :ensure t +(use-package yaml-ts-mode + :ensure nil :mode "\\.ya?ml\\'" - :hook (yaml-mode . eglot-ensure)) + :hook (yaml-ts-mode . eglot-ensure)) -(use-package sh-mode +(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) - (sh-mode . eglot-ensure) :config (with-eval-after-load 'eglot (add-to-list 'eglot-server-programs - '((sh-mode bash-ts-mode) . ("bash-language-server" "start")))) - + '(bash-ts-mode . ("bash-language-server" "start")))) + (defun sh-mode-setup () (add-hook 'before-save-hook #'eglot-format-buffer -10 t)) - - (add-hook 'sh-mode-hook #'sh-mode-setup) + (add-hook 'bash-ts-mode-hook #'sh-mode-setup)) (use-package jinja2-mode @@ -2420,7 +2613,8 @@ completion-category-overrides '((file (styles partial-completion))))) ;; Customi :defer t) (use-package protobuf-mode - :ensure t + :ensure (:host github :repo "protocolbuffers/protobuf" + :files ("editors/protobuf-mode.el")) :mode ("\\.proto\\'" . protobuf-mode)) (use-package janet-mode @@ -2431,6 +2625,19 @@ completion-category-overrides '((file (styles partial-completion))))) ;; Customi (add-to-list 'eglot-server-programs '(janet-mode . ("janet-lsp"))))) +(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) + (use-package vterm :ensure t :config @@ -2441,6 +2648,125 @@ completion-category-overrides '((file (styles partial-completion))))) ;; Customi :ensure t :defer t) +(use-package eat + :ensure t + :custom + (eat-shell "fish") + (eat-kill-buffer-on-exit t) + (eat-enable-shell-prompt-annotation t) + (eat-term-name "xterm-256color") + :hook + ;; Enable shell integration for directory tracking, prompt navigation, etc. + (eshell-load . eat-eshell-mode) + (eshell-load . eat-eshell-visual-command-mode) + :config + ;; Tell yazi (and other programs) that this terminal supports Sixel. + ;; Eat supports Sixel rendering but yazi doesn't recognize it as a known + ;; terminal. Setting TERM_PROGRAM to "foot" (a known Sixel terminal) makes + ;; yazi select the Sixel adapter for image previews. + (advice-add 'eat :around + (lambda (orig-fn &rest args) + (let ((process-environment + (append '("TERM_PROGRAM=foot" + "TERM_PROGRAM_VERSION=1.18.0") + process-environment))) + (apply orig-fn args)))) + ;; ────────────────────────────────────────────────── + ;; Evil integration: make eat behave like vterm + ;; + ;; Strategy: + ;; - "emacs mode" in eat = normal state (vim motions on buffer text) + ;; - "semi-char mode" in eat = insert state (keys pass to terminal) + ;; - ESC switches from semi-char → emacs mode (insert → normal) + ;; - i/a/A etc switch from emacs → semi-char mode (normal → insert) + ;; ────────────────────────────────────────────────── + + ;; Don't move cursor back on ESC (inappropriate in terminal buffers) + (add-hook 'eat-mode-hook (lambda () (setq-local evil-move-cursor-back nil))) + + ;; Start eat in insert state (semi-char mode) so typing goes to shell + (evil-set-initial-state 'eat-mode 'insert) + + ;; -- Normal state: vim motions on terminal text -------------------------- + + ;; Entering insert state switches eat to semi-char mode (passthrough) + (defun my/eat-semi-char-mode-entry () + "Switch eat to semi-char mode when entering evil insert state." + (when (and (derived-mode-p 'eat-mode) + (eat-term-p (buffer-local-value 'eat-terminal (current-buffer)))) + (eat-semi-char-mode))) + + ;; Exiting insert state switches eat to emacs mode (vim motions) + (defun my/eat-emacs-mode-entry () + "Switch eat to emacs mode when entering evil normal state." + (when (and (derived-mode-p 'eat-mode) + (eat-term-p (buffer-local-value 'eat-terminal (current-buffer)))) + (eat-emacs-mode))) + + (add-hook 'evil-insert-state-entry-hook #'my/eat-semi-char-mode-entry) + (add-hook 'evil-normal-state-entry-hook #'my/eat-emacs-mode-entry) + + ;; In normal state, bind standard vim keys to re-enter insert (semi-char) + (evil-define-key 'normal eat-mode-map + "i" (lambda () (interactive) (eat-semi-char-mode) (evil-insert-state)) + "a" (lambda () (interactive) (forward-char 1) (eat-semi-char-mode) (evil-insert-state)) + "A" (lambda () (interactive) (end-of-line) (eat-semi-char-mode) (evil-insert-state)) + "I" (lambda () (interactive) (back-to-indentation) (eat-semi-char-mode) (evil-insert-state)) + "o" (lambda () (interactive) (eat-semi-char-mode) (evil-insert-state) + (eat-self-input 1 ?\n)) + ;; Paste from kill ring in normal mode + "p" (lambda () (interactive) + (eat-semi-char-mode) + (eat-yank) + (eat-emacs-mode)) + ;; Navigate prompts + (kbd "[ [") #'eat-previous-shell-prompt + (kbd "] ]") #'eat-next-shell-prompt) + + ;; In insert state: ESC escapes back to normal (emacs mode intercepts it) + ;; The default eat-semi-char-mode lets C-c, C-x, etc. pass through to Emacs + ;; which is exactly what we want. ESC is also not captured by semi-char mode, + ;; so evil's ESC → normal state transition works naturally. + + ;; -- Multi-eat: manage multiple terminal buffers ------------------------- + + (defvar my/eat-buffer-counter 0 + "Counter for naming eat terminal buffers.") + + (defun my/eat-new () + "Create a new eat terminal buffer." + (interactive) + (cl-incf my/eat-buffer-counter) + (let ((buf (generate-new-buffer + (format "*eat<%d>*" my/eat-buffer-counter))) + (process-environment + (append '("TERM_PROGRAM=foot" "TERM_PROGRAM_VERSION=1.18.0") + process-environment))) + (with-current-buffer buf + (eat-mode) + (eat-exec buf (buffer-name buf) eat-shell nil nil)) + (switch-to-buffer buf))) + + (defun my/eat-toggle () + "Toggle an eat terminal. Create one if none exists. +With prefix arg, always create a new terminal." + (interactive) + (if current-prefix-arg + (my/eat-new) + (let ((eat-bufs (cl-remove-if-not + (lambda (b) (with-current-buffer b + (derived-mode-p 'eat-mode))) + (buffer-list)))) + (cond + ;; Currently in an eat buffer → bury it + ((derived-mode-p 'eat-mode) + (bury-buffer)) + ;; Existing eat buffers → switch to most recent + (eat-bufs + (switch-to-buffer (car eat-bufs))) + ;; No eat buffers → create one + (t (eat))))))) + (use-package olivetti :ensure t :hook (org-mode . olivetti-mode) @@ -2452,16 +2778,25 @@ completion-category-overrides '((file (styles partial-completion))))) ;; Customi ) (defun my-org-sidecar-left () - "Org-Datei interaktiv als Sidecar links (25%)." + "Pick an org-mode buffer via consult and display it as a left sidecar." (interactive) - (let ((file (read-file-name "Org-Sidecar: " nil nil t))) - (display-buffer-in-side-window - (find-file-noselect file) - '((side . left) - (window-width . 0.25) - (slot . 0))))) - -(global-set-key (kbd "C-c o s") #'my-org-sidecar-left) + (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)))))) ;; (use-package visual-fill-column ;; :ensure t @@ -2494,13 +2829,17 @@ completion-category-overrides '((file (styles partial-completion))))) ;; Customi (use-package hl-todo :ensure (:host github :repo "tarsius/hl-todo") :config - (global-hl-todo-mode) + (global-hl-todo-mode) (setq hl-todo-keyword-faces - '(("TODO" . "#FF0000") - ("FIXME" . "#FF0000") - ("DEBUG" . "#A020F0") - ("GOTCHA" . "#FF4500") - ("STUB" . "#1E90FF")))) + '(("FIXME" . "#FF4500") + ("BUG" . "#FF0000") + ("DEBUG" . "#A020F0") + ("HACK" . "#E6DB74") + ("TODO" . "#FF8C00") + ("REVIEW" . "#2D9574") + ("OPTIMIZE" . "#1E90FF") + ("STUB" . "#87CEEB") + ("DEPRECATED" . "#808080")))) (use-package magit :ensure t @@ -2706,7 +3045,7 @@ completion-category-overrides '((file (styles partial-completion))))) ;; Customi (flash-multi-window t) (flash-backdrop t) (flash-case-fold t) - (flash-autojump nil) + (flash-autojump t) (flash-highlight-matches t) (flash-label-position 'overlay) (flash-jump-position 'start) @@ -2714,6 +3053,8 @@ completion-category-overrides '((file (styles partial-completion))))) ;; Customi (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) @@ -3083,11 +3424,17 @@ completion-category-overrides '((file (styles partial-completion))))) ;; Customi (blink-cursor-mode 0) (setq-default cursor-type 'box) -(defvar cashmere/font-height 200) +(defvar cashmere/font-height 160) -(set-face-attribute 'default nil :family "Hack Nerd Font" :weight 'regular :height cashmere/font-height) -(set-face-attribute 'fixed-pitch nil :family "RobotoMono Nerd Font" :weight 'regular) -(set-face-attribute 'variable-pitch nil :family "Poppins" :weight 'regular :height 1.1) + (set-face-attribute 'default nil + :family "MapleMono NF" + :height 160 + :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 @@ -3509,15 +3856,32 @@ completion-category-overrides '((file (styles partial-completion))))) ;; Customi :ensure t :after (dired-rsync transient)) +(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))))) + (setq-default olivetti-body-width 100) (define-globalized-minor-mode my/global-olivetti-mode olivetti-mode (lambda () (olivetti-mode 1))) -(my/centered-cursor) +;; (my/centered-cursor) ;; (my/global-olivetti-mode) -(use-package org-modern-indent - :ensure (:host github :repo "jdtsmith/org-modern-indent") - :hook (org-mode . org-modern-indent-mode)) +;; (use-package org-modern-indent +;; :ensure (:host github :repo "jdtsmith/org-modern-indent") +;; :hook (org-mode . org-modern-indent-mode)) (setq ibuffer-never-show-predicates '(;; System buffers @@ -3568,7 +3932,49 @@ completion-category-overrides '((file (styles partial-completion))))) ;; Customi :config (add-to-list 'display-buffer-alist '("\\*Pass.*\\*" - (display-buffer-full-frame)))) + (display-buffer-full-frame))) + + (defvar-local pass--hidden-overlays nil + "Overlays used to hide the password line.") + + (defun pass-toggle-password () + "Toggle visibility of the first line (password) in a pass-view buffer." + (interactive) + (if pass--hidden-overlays + (progn + (mapc #'delete-overlay pass--hidden-overlays) + (setq pass--hidden-overlays nil)) + (save-excursion + (goto-char (point-min)) + (let ((ov (make-overlay (line-beginning-position) (line-end-position)))) + (overlay-put ov 'display (propertize "••••••••" 'face 'shadow)) + (overlay-put ov 'pass-hidden t) + (push ov pass--hidden-overlays))))) + + (add-hook 'pass-view-mode-hook #'pass-toggle-password) + + (with-eval-after-load 'evil + (general-def 'normal pass-mode-map + "q" 'quit-window + "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-update-buffer + "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-toggle-password + "y" 'pass-copy + "Y" 'pass-copy-field))) (use-package auth-source :ensure nil ;; This is built-in, no need to fetch it. @@ -3604,10 +4010,9 @@ completion-category-overrides '((file (styles partial-completion))))) ;; Customi (defun my/format-buffer () (interactive) (cond - ((eq major-mode 'rust-mode) (eglot-format-buffer)) - ((eq major-mode 'nix-mode) (eglot-format-buffer)) - ((or (eq major-mode 'python-mode) - (eq major-mode 'python-ts-mode)) (eglot-format-buffer)) + ((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)))) @@ -3683,6 +4088,7 @@ completion-category-overrides '((file (styles partial-completion))))) ;; Customi "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") @@ -3721,15 +4127,34 @@ completion-category-overrides '((file (styles partial-completion))))) ;; Customi "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") - "a" '(embark-act :wk "embark") + ";" '(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")) + "ts" '(flash-treesitter :wk "flash treesitter") + + ;; ECA (AI assistant) + "a" '(:ignore t :wk "eca/ai") + "aa" '(eca :wk "open eca") + "ac" '(eca-chat-toggle-window :wk "toggle chat") + "an" '(eca-chat-new :wk "new chat") + "as" '(eca-chat-select :wk "select chat") + "ar" '(eca-rewrite :wk "rewrite region") + "am" '(eca-chat-select-model :wk "select model") + "av" '(eca-chat-select-variant :wk "select variant") + "ag" '(eca-chat-select-agent :wk "select agent") + "at" '(eca-transient-menu :wk "transient menu") + "ad" '(eca-mcp-details :wk "mcp details") + "aR" '(eca-restart :wk "restart eca") + "aS" '(eca-stop :wk "stop eca") + "aw" '(eca-workspaces :wk "workspaces") + "ae" '(eca-show-errors :wk "show errors") + "aC" '(eca-open-global-config :wk "global config")) (defun my/flash-enabled-p () (and (not (derived-mode-p 'magit-mode 'dired-mode 'ibuffer-mode)) @@ -3795,7 +4220,9 @@ completion-category-overrides '((file (styles partial-completion))))) ;; Customi "l" '(:ignore :wk "link") "lc" '(org-cliplink :wk "cliplink") "li" '(org-download-clipboard :wk "image") - "ll" '(org-insert-link :wk "link various things")) + "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) @@ -3810,7 +4237,7 @@ completion-category-overrides '((file (styles partial-completion))))) ;; Customi "mf" '(fill-paragraph :wk "fill paragraph")) (my-leader - :keymaps '(rust-mode-map rust-ts-mode-map) + :keymaps '(rust-ts-mode-map) "m" '(:wk "rust mode" :ignore) "mr" '(rust-run :wk "run") "mc" '(rust-run-clippy :wk "clippy") @@ -3834,6 +4261,20 @@ completion-category-overrides '((file (styles partial-completion))))) ;; Customi "e" 'tmr-edit-description "r" 'tmr-reschedule)) +(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 @@ -3843,6 +4284,8 @@ completion-category-overrides '((file (styles partial-completion))))) ;; Customi "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 @@ -3867,13 +4310,13 @@ completion-category-overrides '((file (styles partial-completion))))) ;; Customi (my-local-leader "a" '(org-agenda :wk "org agenda") "c" '(my/centered-cursor :wk "center cursor") - "f" '(dirvish :wk "file manager") - "t" '(multi-vterm :wk "terminal") + "f" '(dirvish :wk "file manager") + "r" '(async-shell-command :wk "run async") + "t" '(projectile-run-vterm :wk "terminal") + "e" '(eshell :wk "eshell") "z" '(golden-ratio-mode :wk "zoom/golden ratio") - "i" '(persp-irc :wk "irc") - "m" '(mu4e :wk "mail") - "p" '(pass :wk "pass") "o" '(my/global-olivetti-mode :wk "center buffer") + "s" '(my-org-sidecar-left :wk "org sidecar") "g" '(:wk "gptel" :ignore) @@ -4282,4 +4725,30 @@ completion-category-overrides '((file (styles partial-completion))))) ;; Customi (use-package plz :ensure t) +(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")))) + +(use-package envrc + :ensure t + :init (envrc-global-mode)) + +(use-package bbj + :ensure (:host github :repo "bbj-dev/bbj" + :files ("clients/emacs/bbj.el")) + :defer t + :commands (bbj-browse-index bbj-login) + :custom + (bbj-host "bbs.moneyspread.st") + (bbj-port 7099) + ;; (bbj-width 80) + ) + +(use-package tldr + :ensure t) + (provide 'init) @@ -37,6 +37,59 @@ (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 @@ -72,6 +125,8 @@ (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) @@ -124,6 +179,7 @@ (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) @@ -136,7 +192,7 @@ ;; 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! + ";; Welcome to Emacs! ;; ;; Loading time : %s ;; Packages : %s @@ -151,7 +207,6 @@ :group 'appearance) #+end_src - * wayland clipboard fix #+begin_src emacs-lisp @@ -253,6 +308,7 @@ * erc #+begin_src emacs-lisp +(when (my/load-for-hub-p) (use-package erc :ensure nil :defer t @@ -263,7 +319,7 @@ ;; behaviour (erc-join-buffer 'buffer) - (erc-kill-buffer-on-part t) + (erc-kill-buffer-on-part nil) (erc-kill-queries-on-quit t) (erc-kill-server-buffer-on-quit t) @@ -437,6 +493,17 @@ Temporarily disables notifications during the fetch." (when (bound-and-true-p persp-mode) (persp-add-buffer (current-buffer))))) + ;; Prevent accidental ERC buffer kills from parting channels + (add-hook 'kill-buffer-query-functions + (lambda () + (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))) + (general-def 'normal erc-mode-map "q" 'quit-window "Q" 'my/erc-quit-all @@ -453,6 +520,15 @@ Temporarily disables notifications during the fetch." "M-n" 'erc-track-switch-buffer "C-k" 'erc-previous-command "C-j" 'erc-next-command)) +) ;; end (when (my/load-for-hub-p) ...) + +;; 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 @@ -692,6 +768,7 @@ I adjusted the config accordingly to my modes which I use on a frequent. (mode . shell-script-mode) (mode . shell-mode) (mode . sh-mode) + (mode . bash-ts-mode) (mode . lua-mode) (mode . bat-mode))) ("Config" (or @@ -703,7 +780,7 @@ I adjusted the config accordingly to my modes which I use on a frequent. (name . "^\\.gitignore$") (name . "^Doxyfile$") (name . "^config\\.toml$") - (mode . yaml-mode))) + (mode . yaml-ts-mode))) ("Web" (or (mode . mhtml-mode) (mode . html-mode) @@ -734,6 +811,10 @@ I adjusted the config accordingly to my modes which I use on a frequent. ("Fundamental" (or (mode . fundamental-mode) (mode . text-mode))) + ("ECA - Editor Code Assistant" + (or + (mode . eca-chat-mode))) + ("Emacs" (or (mode . emacs-lisp-mode) (name . "^\\*Help\\*$") @@ -869,7 +950,89 @@ Not necessary but an eyecandy to look at! (use-package eca :ensure (:host github :repo "editor-code-assistant/eca-emacs" :files ("*.el")) - :defer t) + :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-<return>" '(newline :wk "newline") + "C-<up>" '(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 * eldoc @@ -938,7 +1101,6 @@ Not necessary but an eyecandy to look at! :ensure t :config (setq flycheck-rust-check-tests nil) - (add-hook 'rust-mode-hook #'flycheck-rust-setup) (add-hook 'rust-ts-mode-hook #'flycheck-rust-setup)) #+end_src @@ -947,7 +1109,7 @@ Not necessary but an eyecandy to look at! #+begin_src emacs-lisp (use-package flycheck-python-ruff :ensure (:host github :repo "v4n6/flycheck-python-ruff") - :hook ((python-mode python-ts-mode) . flycheck-python-ruff-setup)) + :hook (python-ts-mode . flycheck-python-ruff-setup)) #+end_src ** COMMENT flyover @@ -1104,40 +1266,61 @@ automatically encrypted before save and can be decrypted in-place with #+begin_src emacs-lisp #+end_src -** org-modern +** org-modern (disabled in favor of render-org) #+begin_src emacs-lisp -(use-package org-modern - :ensure t - :hook (org-mode . org-modern-mode) - :config - (setq org-modern-hide-stars t) - (setq org-modern-star 'replace) - (setq org-modern-fold-stars '(("◉" . "○"))) - (setq org-modern-replace-stars "◉○◉○◉") - (global-org-modern-mode)) +;; (use-package org-modern +;; :ensure t +;; :hook (org-mode . org-modern-mode) +;; :config +;; (setq org-modern-hide-stars t) +;; (setq org-modern-star 'replace) +;; (setq org-modern-fold-stars '(("◉" . "○"))) +;; (setq org-modern-replace-stars "◉○◉○◉") +;; (global-org-modern-mode)) #+end_src +** 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 (:host github :repo "cashmeredev/kitty-graphics.el") - :if (and (not (display-graphic-p)) (getenv "KITTY_PID")) - :config - (kitty-graphics-mode 1)) +;; (use-package kitty-graphics +;; :ensure (:host github :repo "cashmeredev/kitty-graphics.el") +;; :if (and (not (display-graphic-p)) (getenv "KITTY_PID")) +;; :config +;; (kitty-graphics-mode 1)) ;; (with-eval-after-load 'dired ;; (define-key dired-mode-map (kbd "P") #'kitty-gfx-dired-preview)) -;; (when (and (not (display-graphic-p)) (getenv "KITTY_PID")) -;; (load "~/projects/kitty-graphics/kitty-graphics.el") -;; (kitty-graphics-mode 1) -;; (with-eval-after-load 'dired -;; (define-key dired-mode-map (kbd "P") #'kitty-gfx-dired-preview))) +(when (and (not (display-graphic-p)) + (or (getenv "KITTY_PID") ;; Kitty terminal + (string-prefix-p "foot" (or (getenv "TERM") "")))) ;; foot terminal (Sixel) + (load "~/projects/kitty-graphics/kitty-graphics.el") + (kitty-graphics-mode 1) + (with-eval-after-load 'dired + (define-key dired-mode-map (kbd "P") #'kitty-gfx-dired-preview))) #+end_src ** org-fancy-properties @@ -1229,6 +1412,34 @@ Old config :after org) #+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 snips.sh and copy URL. @@ -1946,19 +2157,19 @@ Skips capture tasks and projects." ("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) +;; (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 --- @@ -2740,42 +2951,32 @@ completion-category-overrides '((file (styles partial-completion))))) ;; Customi :after yasnippet) #+end_src -** nix-mode +** nix-ts-mode #+begin_src emacs-lisp -(use-package nix-mode +(use-package nix-ts-mode :ensure t :mode "\\.nix\\'" - :hook (nix-mode . eglot-ensure) + :hook (nix-ts-mode . eglot-ensure) :config (with-eval-after-load 'eglot (add-to-list 'eglot-server-programs - '(nix-mode . ("nixd"))))) -#+end_src - -*** envrc - -Automatic flake devshell activation. - -#+begin_src emacs-lisp -(use-package envrc - :ensure t - :hook (after-init . envrc-global-mode)) + '(nix-ts-mode . ("nixd"))))) #+end_src -** python-mode +** python-ts-mode Because I have to and not want to. #+begin_src emacs-lisp (use-package python :ensure nil - :mode ("\\.py\\'" . python-mode) - :hook (python-mode . eglot-ensure) + :mode ("\\.py\\'" . python-ts-mode) + :hook (python-ts-mode . eglot-ensure) :config (with-eval-after-load 'eglot (add-to-list 'eglot-server-programs - '(python-mode . ("rass" "--" "pyrefly" "lsp" "--" "ruff" "server"))))) + '(python-ts-mode . ("rass" "--" "pyrefly" "lsp" "--" "ruff" "server"))))) #+end_src *** pyvenv @@ -2786,7 +2987,7 @@ Because I have to and not want to. :config (setq pyvenv-mode-line-indicator '(pyvenv-virtual-env-name (" [venv:" pyvenv-virtual-env-name "] "))) - (add-hook 'python-mode-hook + (add-hook 'python-ts-mode-hook (lambda () (pyvenv-mode 1)))) #+end_src @@ -2808,14 +3009,14 @@ Because I have to and not want to. ily <3. #+begin_src emacs-lisp -(use-package rust-mode - :ensure t +(use-package rust-ts-mode + :ensure nil :mode "\\.rs\\'" - :hook (rust-mode . eglot-ensure) + :hook (rust-ts-mode . eglot-ensure) :config (with-eval-after-load 'eglot (add-to-list 'eglot-server-programs - '((rust-ts-mode rust-mode) . + '(rust-ts-mode . ("rust-analyzer" :initializationOptions (:check (:command "clippy"))))))) #+end_src @@ -2838,24 +3039,23 @@ ily <3. 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-mode +*** js-ts-mode #+begin_src emacs-lisp (use-package js :ensure nil - :mode ("\\.js\\'" . js-mode) - :hook (js-mode . eglot-ensure) + :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-mode . ("rass" "--" "typescript-language-server" "--stdio" "--" "vscode-eslint-language-server" "--stdio"))))) + '(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. -Requires tree-sitter grammars: =M-x treesit-install-language-grammar RET typescript= (and =tsx=). #+begin_src emacs-lisp (use-package typescript-ts-mode @@ -2867,7 +3067,7 @@ Requires tree-sitter grammars: =M-x treesit-install-language-grammar RET typescr :config (with-eval-after-load 'eglot (add-to-list 'eglot-server-programs - '(js-mode . ("rass" "--" "typescript-language-server" "--stdio" "--" "vscode-eslint-language-server" "--stdio"))))) + '((typescript-ts-mode tsx-ts-mode) . ("rass" "--" "typescript-language-server" "--stdio" "--" "vscode-eslint-language-server" "--stdio"))))) #+end_src *** web-mode (Vue) @@ -2925,67 +3125,69 @@ Requires tree-sitter grammars: =M-x treesit-install-language-grammar RET typescr ** 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))))) +(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)) + (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)) + (add-hook 'go-mode-hook #'go-mode-setup)) #+end_src -** json-mode +** json-ts-mode #+begin_src emacs-lisp -(use-package json-mode - :ensure t +(use-package json-ts-mode + :ensure nil :mode "\\.json\\'" - :hook (json-mode . eglot-ensure) + :hook (json-ts-mode . eglot-ensure) :config (with-eval-after-load 'eglot (add-to-list 'eglot-server-programs - '(json-mode . ("vscode-json-language-server" "--stdio"))))) + '(json-ts-mode . ("vscode-json-language-server" "--stdio"))))) #+end_src -** yaml-mode +** yaml-ts-mode #+begin_src emacs-lisp -(use-package yaml-mode - :ensure t +(use-package yaml-ts-mode + :ensure nil :mode "\\.ya?ml\\'" - :hook (yaml-mode . eglot-ensure)) + :hook (yaml-ts-mode . eglot-ensure)) #+end_src -** sh-mode +** bash-ts-mode #+begin_src emacs-lisp -(use-package sh-mode +(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) - (sh-mode . eglot-ensure) :config (with-eval-after-load 'eglot (add-to-list 'eglot-server-programs - '((sh-mode bash-ts-mode) . ("bash-language-server" "start")))) - + '(bash-ts-mode . ("bash-language-server" "start")))) + (defun sh-mode-setup () (add-hook 'before-save-hook #'eglot-format-buffer -10 t)) - - (add-hook 'sh-mode-hook #'sh-mode-setup) + (add-hook 'bash-ts-mode-hook #'sh-mode-setup)) #+end_src @@ -3001,7 +3203,8 @@ Requires tree-sitter grammars: =M-x treesit-install-language-grammar RET typescr #+begin_src emacs-lisp (use-package protobuf-mode - :ensure t + :ensure (:host github :repo "protocolbuffers/protobuf" + :files ("editors/protobuf-mode.el")) :mode ("\\.proto\\'" . protobuf-mode)) #+end_src @@ -3017,7 +3220,26 @@ Requires tree-sitter grammars: =M-x treesit-install-language-grammar RET typescr '(janet-mode . ("janet-lsp"))))) #+end_src -* vterm +** 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 + +** vterm #+begin_src emacs-lisp (use-package vterm @@ -3027,7 +3249,7 @@ Requires tree-sitter grammars: =M-x treesit-install-language-grammar RET typescr (setq vterm-shell "fish")) #+end_src -** multi-vterm +*** multi-vterm #+begin_src emacs-lisp (use-package multi-vterm @@ -3035,6 +3257,129 @@ Requires tree-sitter grammars: =M-x treesit-install-language-grammar RET typescr :defer t) #+end_src +** eat + +#+begin_src emacs-lisp +(use-package eat + :ensure t + :custom + (eat-shell "fish") + (eat-kill-buffer-on-exit t) + (eat-enable-shell-prompt-annotation t) + (eat-term-name "xterm-256color") + :hook + ;; Enable shell integration for directory tracking, prompt navigation, etc. + (eshell-load . eat-eshell-mode) + (eshell-load . eat-eshell-visual-command-mode) + :config + ;; Tell yazi (and other programs) that this terminal supports Sixel. + ;; Eat supports Sixel rendering but yazi doesn't recognize it as a known + ;; terminal. Setting TERM_PROGRAM to "foot" (a known Sixel terminal) makes + ;; yazi select the Sixel adapter for image previews. + (advice-add 'eat :around + (lambda (orig-fn &rest args) + (let ((process-environment + (append '("TERM_PROGRAM=foot" + "TERM_PROGRAM_VERSION=1.18.0") + process-environment))) + (apply orig-fn args)))) + ;; ────────────────────────────────────────────────── + ;; Evil integration: make eat behave like vterm + ;; + ;; Strategy: + ;; - "emacs mode" in eat = normal state (vim motions on buffer text) + ;; - "semi-char mode" in eat = insert state (keys pass to terminal) + ;; - ESC switches from semi-char → emacs mode (insert → normal) + ;; - i/a/A etc switch from emacs → semi-char mode (normal → insert) + ;; ────────────────────────────────────────────────── + + ;; Don't move cursor back on ESC (inappropriate in terminal buffers) + (add-hook 'eat-mode-hook (lambda () (setq-local evil-move-cursor-back nil))) + + ;; Start eat in insert state (semi-char mode) so typing goes to shell + (evil-set-initial-state 'eat-mode 'insert) + + ;; -- Normal state: vim motions on terminal text -------------------------- + + ;; Entering insert state switches eat to semi-char mode (passthrough) + (defun my/eat-semi-char-mode-entry () + "Switch eat to semi-char mode when entering evil insert state." + (when (and (derived-mode-p 'eat-mode) + (eat-term-p (buffer-local-value 'eat-terminal (current-buffer)))) + (eat-semi-char-mode))) + + ;; Exiting insert state switches eat to emacs mode (vim motions) + (defun my/eat-emacs-mode-entry () + "Switch eat to emacs mode when entering evil normal state." + (when (and (derived-mode-p 'eat-mode) + (eat-term-p (buffer-local-value 'eat-terminal (current-buffer)))) + (eat-emacs-mode))) + + (add-hook 'evil-insert-state-entry-hook #'my/eat-semi-char-mode-entry) + (add-hook 'evil-normal-state-entry-hook #'my/eat-emacs-mode-entry) + + ;; In normal state, bind standard vim keys to re-enter insert (semi-char) + (evil-define-key 'normal eat-mode-map + "i" (lambda () (interactive) (eat-semi-char-mode) (evil-insert-state)) + "a" (lambda () (interactive) (forward-char 1) (eat-semi-char-mode) (evil-insert-state)) + "A" (lambda () (interactive) (end-of-line) (eat-semi-char-mode) (evil-insert-state)) + "I" (lambda () (interactive) (back-to-indentation) (eat-semi-char-mode) (evil-insert-state)) + "o" (lambda () (interactive) (eat-semi-char-mode) (evil-insert-state) + (eat-self-input 1 ?\n)) + ;; Paste from kill ring in normal mode + "p" (lambda () (interactive) + (eat-semi-char-mode) + (eat-yank) + (eat-emacs-mode)) + ;; Navigate prompts + (kbd "[ [") #'eat-previous-shell-prompt + (kbd "] ]") #'eat-next-shell-prompt) + + ;; In insert state: ESC escapes back to normal (emacs mode intercepts it) + ;; The default eat-semi-char-mode lets C-c, C-x, etc. pass through to Emacs + ;; which is exactly what we want. ESC is also not captured by semi-char mode, + ;; so evil's ESC → normal state transition works naturally. + + ;; -- Multi-eat: manage multiple terminal buffers ------------------------- + + (defvar my/eat-buffer-counter 0 + "Counter for naming eat terminal buffers.") + + (defun my/eat-new () + "Create a new eat terminal buffer." + (interactive) + (cl-incf my/eat-buffer-counter) + (let ((buf (generate-new-buffer + (format "*eat<%d>*" my/eat-buffer-counter))) + (process-environment + (append '("TERM_PROGRAM=foot" "TERM_PROGRAM_VERSION=1.18.0") + process-environment))) + (with-current-buffer buf + (eat-mode) + (eat-exec buf (buffer-name buf) eat-shell nil nil)) + (switch-to-buffer buf))) + + (defun my/eat-toggle () + "Toggle an eat terminal. Create one if none exists. +With prefix arg, always create a new terminal." + (interactive) + (if current-prefix-arg + (my/eat-new) + (let ((eat-bufs (cl-remove-if-not + (lambda (b) (with-current-buffer b + (derived-mode-p 'eat-mode))) + (buffer-list)))) + (cond + ;; Currently in an eat buffer → bury it + ((derived-mode-p 'eat-mode) + (bury-buffer)) + ;; Existing eat buffers → switch to most recent + (eat-bufs + (switch-to-buffer (car eat-bufs))) + ;; No eat buffers → create one + (t (eat))))))) +#+end_src + * olivetti #+begin_src emacs-lisp @@ -3053,16 +3398,25 @@ Requires tree-sitter grammars: =M-x treesit-install-language-grammar RET typescr #+begin_src emacs-lisp (defun my-org-sidecar-left () - "Org-Datei interaktiv als Sidecar links (25%)." + "Pick an org-mode buffer via consult and display it as a left sidecar." (interactive) - (let ((file (read-file-name "Org-Sidecar: " nil nil t))) - (display-buffer-in-side-window - (find-file-noselect file) - '((side . left) - (window-width . 0.25) - (slot . 0))))) - -(global-set-key (kbd "C-c o s") #'my-org-sidecar-left) + (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 @@ -3107,13 +3461,17 @@ Requires tree-sitter grammars: =M-x treesit-install-language-grammar RET typescr (use-package hl-todo :ensure (:host github :repo "tarsius/hl-todo") :config - (global-hl-todo-mode) + (global-hl-todo-mode) (setq hl-todo-keyword-faces - '(("TODO" . "#FF0000") - ("FIXME" . "#FF0000") - ("DEBUG" . "#A020F0") - ("GOTCHA" . "#FF4500") - ("STUB" . "#1E90FF")))) + '(("FIXME" . "#FF4500") + ("BUG" . "#FF0000") + ("DEBUG" . "#A020F0") + ("HACK" . "#E6DB74") + ("TODO" . "#FF8C00") + ("REVIEW" . "#2D9574") + ("OPTIMIZE" . "#1E90FF") + ("STUB" . "#87CEEB") + ("DEPRECATED" . "#808080")))) #+end_src * magit @@ -3370,7 +3728,7 @@ Requires tree-sitter grammars: =M-x treesit-install-language-grammar RET typescr (flash-multi-window t) (flash-backdrop t) (flash-case-fold t) - (flash-autojump nil) + (flash-autojump t) (flash-highlight-matches t) (flash-label-position 'overlay) (flash-jump-position 'start) @@ -3378,6 +3736,8 @@ Requires tree-sitter grammars: =M-x treesit-install-language-grammar RET typescr (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) @@ -4107,11 +4467,17 @@ every =emacsclient -c= frame gets its own set of perspectives. (blink-cursor-mode 0) (setq-default cursor-type 'box) -(defvar cashmere/font-height 200) +(defvar cashmere/font-height 160) -(set-face-attribute 'default nil :family "Hack Nerd Font" :weight 'regular :height cashmere/font-height) -(set-face-attribute 'fixed-pitch nil :family "RobotoMono Nerd Font" :weight 'regular) -(set-face-attribute 'variable-pitch nil :family "Poppins" :weight 'regular :height 1.1) + (set-face-attribute 'default nil + :family "MapleMono NF" + :height 160 + :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 @@ -4598,19 +4964,38 @@ every =emacsclient -c= frame gets its own set of perspectives. * 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/centered-cursor) ;; (my/global-olivetti-mode) #+end_src -* org-modern-indent +* org-modern-indent (disabled in favor of render-org) #+begin_src emacs-lisp -(use-package org-modern-indent - :ensure (:host github :repo "jdtsmith/org-modern-indent") - :hook (org-mode . org-modern-indent-mode)) +;; (use-package org-modern-indent +;; :ensure (:host github :repo "jdtsmith/org-modern-indent") +;; :hook (org-mode . org-modern-indent-mode)) #+end_src * ibuffer hide @@ -4677,7 +5062,49 @@ every =emacsclient -c= frame gets its own set of perspectives. :config (add-to-list 'display-buffer-alist '("\\*Pass.*\\*" - (display-buffer-full-frame)))) + (display-buffer-full-frame))) + + (defvar-local pass--hidden-overlays nil + "Overlays used to hide the password line.") + + (defun pass-toggle-password () + "Toggle visibility of the first line (password) in a pass-view buffer." + (interactive) + (if pass--hidden-overlays + (progn + (mapc #'delete-overlay pass--hidden-overlays) + (setq pass--hidden-overlays nil)) + (save-excursion + (goto-char (point-min)) + (let ((ov (make-overlay (line-beginning-position) (line-end-position)))) + (overlay-put ov 'display (propertize "••••••••" 'face 'shadow)) + (overlay-put ov 'pass-hidden t) + (push ov pass--hidden-overlays))))) + + (add-hook 'pass-view-mode-hook #'pass-toggle-password) + + (with-eval-after-load 'evil + (general-def 'normal pass-mode-map + "q" 'quit-window + "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-update-buffer + "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-toggle-password + "y" 'pass-copy + "Y" 'pass-copy-field))) #+end_src ** auth-source @@ -4733,10 +5160,9 @@ every =emacsclient -c= frame gets its own set of perspectives. (defun my/format-buffer () (interactive) (cond - ((eq major-mode 'rust-mode) (eglot-format-buffer)) - ((eq major-mode 'nix-mode) (eglot-format-buffer)) - ((or (eq major-mode 'python-mode) - (eq major-mode 'python-ts-mode)) (eglot-format-buffer)) + ((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)))) @@ -4812,6 +5238,7 @@ every =emacsclient -c= frame gets its own set of perspectives. "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") @@ -4850,15 +5277,34 @@ every =emacsclient -c= frame gets its own set of perspectives. "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") - "a" '(embark-act :wk "embark") + ";" '(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")) + "ts" '(flash-treesitter :wk "flash treesitter") + + ;; ECA (AI assistant) + "a" '(:ignore t :wk "eca/ai") + "aa" '(eca :wk "open eca") + "ac" '(eca-chat-toggle-window :wk "toggle chat") + "an" '(eca-chat-new :wk "new chat") + "as" '(eca-chat-select :wk "select chat") + "ar" '(eca-rewrite :wk "rewrite region") + "am" '(eca-chat-select-model :wk "select model") + "av" '(eca-chat-select-variant :wk "select variant") + "ag" '(eca-chat-select-agent :wk "select agent") + "at" '(eca-transient-menu :wk "transient menu") + "ad" '(eca-mcp-details :wk "mcp details") + "aR" '(eca-restart :wk "restart eca") + "aS" '(eca-stop :wk "stop eca") + "aw" '(eca-workspaces :wk "workspaces") + "ae" '(eca-show-errors :wk "show errors") + "aC" '(eca-open-global-config :wk "global config")) (defun my/flash-enabled-p () (and (not (derived-mode-p 'magit-mode 'dired-mode 'ibuffer-mode)) @@ -4931,7 +5377,9 @@ every =emacsclient -c= frame gets its own set of perspectives. "l" '(:ignore :wk "link") "lc" '(org-cliplink :wk "cliplink") "li" '(org-download-clipboard :wk "image") - "ll" '(org-insert-link :wk "link various things")) + "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 @@ -4951,11 +5399,11 @@ every =emacsclient -c= frame gets its own set of perspectives. "mf" '(fill-paragraph :wk "fill paragraph")) #+end_src -** rust-mode keymaps +** rust-ts-mode keymaps #+begin_src emacs-lisp (my-leader - :keymaps '(rust-mode-map rust-ts-mode-map) + :keymaps '(rust-ts-mode-map) "m" '(:wk "rust mode" :ignore) "mr" '(rust-run :wk "run") "mc" '(rust-run-clippy :wk "clippy") @@ -4991,6 +5439,20 @@ every =emacsclient -c= frame gets its own set of perspectives. ** 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 @@ -5000,6 +5462,8 @@ every =emacsclient -c= frame gets its own set of perspectives. "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 @@ -5028,13 +5492,13 @@ every =emacsclient -c= frame gets its own set of perspectives. (my-local-leader "a" '(org-agenda :wk "org agenda") "c" '(my/centered-cursor :wk "center cursor") - "f" '(dirvish :wk "file manager") - "t" '(multi-vterm :wk "terminal") + "f" '(dirvish :wk "file manager") + "r" '(async-shell-command :wk "run async") + "t" '(projectile-run-vterm :wk "terminal") + "e" '(eshell :wk "eshell") "z" '(golden-ratio-mode :wk "zoom/golden ratio") - "i" '(persp-irc :wk "irc") - "m" '(mu4e :wk "mail") - "p" '(pass :wk "pass") "o" '(my/global-olivetti-mode :wk "center buffer") + "s" '(my-org-sidecar-left :wk "org sidecar") "g" '(:wk "gptel" :ignore) @@ -5516,9 +5980,59 @@ this package provides the ability to display rgb and hex colors. :ensure t) #+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. + +#+begin_src emacs-lisp +(use-package envrc + :ensure t + :init (envrc-global-mode)) +#+end_src +* bbj + +#+begin_src emacs-lisp +(use-package bbj + :ensure (:host github :repo "bbj-dev/bbj" + :files ("clients/emacs/bbj.el")) + :defer t + :commands (bbj-browse-index bbj-login) + :custom + (bbj-host "bbs.moneyspread.st") + (bbj-port 7099) + ;; (bbj-width 80) + ) +#+end_src + +* =tldr.el= + +#+begin_src emacs-lisp +(use-package tldr + :ensure t) +#+end_src +* holo-layer + +Visual enhancement overlay for Emacs (cursor animation, window borders, etc). +Installed via Nix emacs package set — no elpaca needed. + + * provide init #+begin_src emacs-lisp (provide 'init) #+end_src + diff --git a/custom-vars.el b/custom-vars.el index caa47ea..ce3e826 100644 --- a/custom-vars.el +++ b/custom-vars.el @@ -5,13 +5,26 @@ ;; Your init file should contain only one such instance. ;; If there is more than one, they won't work right. '(custom-safe-themes - '("087eeb44683d978242c77bc18afd0e047a9df914128143756b402c3b5f101080" - "d43860349c9f7a5b96a090ecf5f698ff23a8eb49cd1e5c8a83bb2068f24ea563" - default)) + '("d43860349c9f7a5b96a090ecf5f698ff23a8eb49cd1e5c8a83bb2068f24ea563" + default)) + '(erc-modules + '(autojoin button completion fill irccontrols list match + move-to-prompt netsplit networks nicks noncommands + notifications readonly ring scrolltobottom stamp track) nil nil "Customized with use-package erc") + '(package-selected-packages + '(adaptive-wrap async corfu-terminal cursory edit-indirect flycheck + gptel marginalia markdown-mode mu4e nov + nushell-ts-mode offlineimap org-modern pdf-tools + ultra-scroll)) '(safe-local-variable-values - '((eval progn - (add-to-list 'auto-mode-alist '("\\.html\\'" . jinja2-mode)) - (add-to-list 'auto-mode-alist '("\\.j2\\'" . jinja2-mode))))) + '((eglot-server-programs + ((python-ts-mode python-mode) "rass" "--" "ty" "server" "--" + "ruff" "server")) + (eglot-server-programs (nix-mode "devenv" "lsp")) + (magit-todos-mode) + (eval progn + (add-to-list 'auto-mode-alist '("\\.html\\'" . jinja2-mode)) + (add-to-list 'auto-mode-alist '("\\.j2\\'" . jinja2-mode))))) '(zoom-size '(0.382 . 0.618))) (custom-set-faces ;; custom-set-faces was added by Custom. diff --git a/elfeed/index b/elfeed/index index c74ac53..71ea54d 100644 --- a/elfeed/index +++ b/elfeed/index @@ -4,4 +4,4 @@ (:version "0.0.3" :feeds #s(hash-table test equal) :entries #s(hash-table test equal) :index [cl-struct-avl-tree- [nil nil nil 0] elfeed-db-compare]) ;; Real index: -(:version 4 :feeds #s(hash-table test equal) :entries #s(hash-table test equal) :index #s(avl-tree- [nil nil nil 0] elfeed-db-compare))
\ No newline at end of file +(:version 4 :feeds #s(hash-table test equal data ("https://tilde.news/rss" #s(elfeed-feed "https://tilde.news/rss" nil nil nil (:title "tilde news")) "https://lobste.rs/rss" #s(elfeed-feed "https://lobste.rs/rss" nil nil nil (:title "lobsters")) "https://cyrneko.eu/blog/feed.atom" #s(elfeed-feed "https://cyrneko.eu/blog/feed.atom" nil nil nil (:title "Alexia's Blog!")) "https://mtwb.blog/posts/index.xml" #s(elfeed-feed "https://mtwb.blog/posts/index.xml" nil nil nil (:title "Posts on Matt's Blog")) "https://doingfedtime.com/rss/" #s(elfeed-feed "https://doingfedtime.com/rss/" nil nil nil (:title "Sam Bent (DoingFedTime)")) "https://joarvarndt.se/rss.xml" #s(elfeed-feed "https://joarvarndt.se/rss.xml" nil nil nil (:title "Joar von Arndt")) "https://lazybea.rs/index.xml" #s(elfeed-feed "https://lazybea.rs/index.xml" nil nil nil (:title "Lazybear")) "https://doubleloop.net/feed/" #s(elfeed-feed "https://doubleloop.net/feed/" nil nil nil (:title "Home - ratfactor")) "https://blog.tymscar.com/index.xml" #s(elfeed-feed "https://blog.tymscar.com/index.xml" nil nil nil (:title "The Tymscar Blog")) "https://akselmo.dev/feed.xml" #s(elfeed-feed "https://akselmo.dev/feed.xml" nil nil nil (:title "AksDev")) "https://phala.isatty.net/~amber/hacks/index.atom" #s(elfeed-feed "https://phala.isatty.net/~amber/hacks/index.atom" nil nil nil (:title "Amber's Hacks")) "https://bowshock.nl/index.xml" #s(elfeed-feed "https://bowshock.nl/index.xml" nil nil nil (:title "Bow Shock Systems Consulting")) "https://coredumped.dev/index.xml" #s(elfeed-feed "https://coredumped.dev/index.xml" nil nil nil (:title "Core Dumped")) "https://hugo.md/index.xml" #s(elfeed-feed "https://hugo.md/index.xml" nil nil nil (:title "Hugo's Blog")) "https://michael.stapelberg.ch/feed.xml" #s(elfeed-feed "https://michael.stapelberg.ch/feed.xml" nil nil nil (:title "Michael Stapelbergs Website")) "https://sebastiano.tronto.net/blog/feed.xml" #s(elfeed-feed "https://sebastiano.tronto.net/blog/feed.xml" nil nil nil (:title "Sebastiano Tronto's blog")) "https://olddeuteronomy.github.io/index.xml" #s(elfeed-feed "https://olddeuteronomy.github.io/index.xml" nil nil nil (:title "The Emacs Cat")) "https://themkat.net/feed.xml" #s(elfeed-feed "https://themkat.net/feed.xml" nil nil nil (:title "TheMKat's blog")) "https://voidcruiser.nl/index.xml" #s(elfeed-feed "https://voidcruiser.nl/index.xml" nil nil nil (:title "The Voidcruiser")) "https://mau.fi/blog/index.rss" #s(elfeed-feed "https://mau.fi/blog/index.rss" nil nil nil (:title "Tulir Asokan")) "https://whhone.com/index.xml" #s(elfeed-feed "https://whhone.com/index.xml" nil nil nil (:title "Wai Hon's Blog")) "https://www.al3rez.com/rss" #s(elfeed-feed "https://www.al3rez.com/rss" nil nil nil (:title "Alireza Bashiri")) "https://joelchrono.xyz/feed.xml" #s(elfeed-feed "https://joelchrono.xyz/feed.xml" nil nil nil (:title "joelchrono's blog")) "https://kersed.net/index.xml" #s(elfeed-feed "https://kersed.net/index.xml" nil nil nil (:title "Kersed")) "https://doubleloop.net/" #s(elfeed-feed "https://doubleloop.net/" nil nil nil (:title "doubleloop - tech + politics + nature + culture")) "https://jeffbradberry.com/feeds/all.atom.xml" #s(elfeed-feed "https://jeffbradberry.com/feeds/all.atom.xml" nil nil nil (:title "Jeff Bradberry")) "https://donovan-ratefison.mg/atom.xml" #s(elfeed-feed "https://donovan-ratefison.mg/atom.xml" nil nil nil (:title "Donovan R.")) "https://lina.sh/blog/rss.xml" #s(elfeed-feed "https://lina.sh/blog/rss.xml" nil nil nil (:title "lina.sh")) "https://planet.emacslife.com/atom.xml" #s(elfeed-feed "https://planet.emacslife.com/atom.xml" nil nil nil (:title "Planet Emacslife")) "https://donovan-ratefison.mg/rss2.xml" #s(elfeed-feed "https://donovan-ratefison.mg/rss2.xml" nil nil nil (:title "Donovan R.")) "https://joshblais.com/rss.xml" #s(elfeed-feed "https://joshblais.com/rss.xml" nil nil nil (:title "Joshua Blais")) "https://jvns.ca/atom.xml" #s(elfeed-feed "https://jvns.ca/atom.xml" nil nil nil (:title "Julia Evans")) "https://lepisma.xyz/" #s(elfeed-feed "https://lepisma.xyz/" nil nil nil (:title "lepisma.xyz")) "https://blog.avas.space/feed/" #s(elfeed-feed "https://blog.avas.space/feed/" nil nil nil (:title "ava's blog")) "https://weakty.com/posts/atom.xml" #s(elfeed-feed "https://weakty.com/posts/atom.xml" nil nil nil (:title "Weakty - Posts")) "https://feeds.transistor.fm/full-time-nix" #s(elfeed-feed "https://feeds.transistor.fm/full-time-nix" nil nil nil (:title "Full Time Nix")) "https://wiki.nixos.org/w/api.php?hidebots=1&translations=filter&urlversion=1&days=7&limit=50&action=feedrecentchanges&feedformat=atom" #s(elfeed-feed "https://wiki.nixos.org/w/api.php?hidebots=1&translations=filter&urlversion=1&days=7&limit=50&action=feedrecentchanges&feedformat=atom" nil nil nil (:title "NixOS Wiki - Recent changes {en}")) "https://craftering.systemcrafters.net/Craftering.opml" #s(elfeed-feed "https://craftering.systemcrafters.net/Craftering.opml" nil nil nil (:title "A webring made of the System Crafters community member sites.")) "https://nicholassobolev.com/index.xml" #s(elfeed-feed "https://nicholassobolev.com/index.xml" nil nil nil (:title "Home on Nicholas Sobolev")) "https://blog.kagi.com/rss.xml" #s(elfeed-feed "https://blog.kagi.com/rss.xml" nil nil nil (:title "Kagi Blog")) "https://tildes.net/topics.rss" #s(elfeed-feed "https://tildes.net/topics.rss" nil nil nil (:title "Tildes")) "https://kevquirk.com/feed.xml" #s(elfeed-feed "https://kevquirk.com/feed.xml" nil nil nil (:title "Kev Quirk")) "https://deskto.ps/rss" #s(elfeed-feed "https://deskto.ps/rss" nil nil nil (:title "deskto.ps")) "https://christiantietze.de/feed.atom" #s(elfeed-feed "https://christiantietze.de/feed.atom" nil nil nil (:title "Worklog: Articles and Blog Posts by Christian Tietze")) "https://writequit.org/posts.xml" #s(elfeed-feed "https://writequit.org/posts.xml" nil nil nil (:title "(:wq - Feed)")) "https://forum.zettelkasten.de/discussions/feed.rss" #s(elfeed-feed "https://forum.zettelkasten.de/discussions/feed.rss" nil nil nil (:title "Zettelkasten Forum")) "https://whynothugo.nl/" #s(elfeed-feed "https://whynothugo.nl/" nil nil nil (:title "Hugo's weblog")) "https://ma.ttias.be/" #s(elfeed-feed "https://ma.ttias.be/" nil nil nil (:title "Mattias Geniar")) "https://www.ludlowinstitute.org/articles?format=rss" #s(elfeed-feed "https://www.ludlowinstitute.org/articles?format=rss" nil nil nil (:title "Articles - Ludlow Institute")) "https://www.d12frosted.io/feed.xml" #s(elfeed-feed "https://www.d12frosted.io/feed.xml" nil nil nil (:title "Boris Buliga")) "https://www.youtube.com/feeds/videos.xml?channel_id=UCzH549YlZhdhIqhtvz7XHmQ" #s(elfeed-feed "https://www.youtube.com/feeds/videos.xml?channel_id=UCzH549YlZhdhIqhtvz7XHmQ" nil nil nil (:title "Alex")) "https://www.youtube.com/feeds/videos.xml?channel_id=UCxaaULLk6UCnRl5VKRc7G0A" #s(elfeed-feed "https://www.youtube.com/feeds/videos.xml?channel_id=UCxaaULLk6UCnRl5VKRc7G0A" nil nil nil (:title "No Text To Speech")) "https://www.youtube.com/feeds/videos.xml?channel_id=UCT6AJiTYspOILBK3hMWEq2g" #s(elfeed-feed "https://www.youtube.com/feeds/videos.xml?channel_id=UCT6AJiTYspOILBK3hMWEq2g" nil nil nil (:title "Switch and Click")) "https://www.youtube.com/feeds/videos.xml?channel_id=UCXuqSBlHAE6Xw-yeJA0Tunw" #s(elfeed-feed "https://www.youtube.com/feeds/videos.xml?channel_id=UCXuqSBlHAE6Xw-yeJA0Tunw" nil nil nil (:title "Linus Tech Tips")) "https://www.youtube.com/feeds/videos.xml?channel_id=UCUP5UhD6cMfpN4vxW3FYJLQ" #s(elfeed-feed "https://www.youtube.com/feeds/videos.xml?channel_id=UCUP5UhD6cMfpN4vxW3FYJLQ" nil nil nil (:title "Sam Bent")) "https://www.youtube.com/feeds/videos.xml?channel_id=UCtMVHI3AJD4Qk4hcbZnI9ZQ" #s(elfeed-feed "https://www.youtube.com/feeds/videos.xml?channel_id=UCtMVHI3AJD4Qk4hcbZnI9ZQ" nil nil nil (:title "SomeOrdinaryGamers")) "https://www.youtube.com/feeds/videos.xml?channel_id=UCg6gPGh8HU2U01vaFCAsvmQ" #s(elfeed-feed "https://www.youtube.com/feeds/videos.xml?channel_id=UCg6gPGh8HU2U01vaFCAsvmQ" nil nil nil (:title "Chris Titus Tech")))) :entries #s(hash-table test equal) :index #s(avl-tree- [nil nil nil 0] elfeed-db-compare))
\ No newline at end of file diff --git a/org-clock-save.el b/org-clock-save.el index d8ea93b..bbfc2d9 100644 --- a/org-clock-save.el +++ b/org-clock-save.el @@ -1,2 +1,2 @@ -;; org-clock-save.el - md at <2026-03-18 Wed 04:00> -*- lexical-binding: t; -*- -(setq org-clock-stored-history '(("/home/cashmere/org/journal/20260318T005111--wednesday-18-march-2026__agenda_journal.org" . 241) ("/home/cashmere/org/journal/20260317T014434--tuesday-17-march-2026__agenda_journal.org" . 911) ("/home/cashmere/org/journal/20260317T014434--tuesday-17-march-2026__agenda_journal.org" . 137))) +;; org-clock-save.el - md at <2026-04-05 Sun 19:56> -*- lexical-binding: t; -*- +(setq org-clock-stored-history '(("/home/cashmere/org/journal/20260324T152154--tuesday-24-march-2026__journal.org" . 232))) diff --git a/themes/noctalia-theme.el b/themes/noctalia-theme.el index c53dce6..66500e4 100644 --- a/themes/noctalia-theme.el +++ b/themes/noctalia-theme.el @@ -19,55 +19,55 @@ (deftheme noctalia "Noctalia theme with proper color contrast.") (let* (;; Core surface colors - (bg "#222327") - (fg "#ffffff") - (surface "#222327") - (on-surface "#ffffff") - (on-surface-variant "#dedede") - (surface-variant "#393939") - (surface-container "#393939") - (surface-container-low "#2d2e30") - (surface-container-lowest "#26272a") - (surface-container-high "#434343") - (surface-container-highest "#4d4d4d") + (bg "#161616") + (fg "#f2f4f8") + (surface "#161616") + (on-surface "#f2f4f8") + (on-surface-variant "#b6b8bb") + (surface-variant "#353535") + (surface-container "#353535") + (surface-container-low "#252525") + (surface-container-lowest "#1c1c1c") + (surface-container-high "#3f3f3f") + (surface-container-highest "#494949") ;; Accent colors (always readable on surface) - (primary "#4e9dff") - (secondary "#6adf73") - (tertiary "#cd7bf6") - (err "#ff6e65") + (primary "#78a9ff") + (secondary "#25be6a") + (tertiary "#be95ff") + (err "#ee5396") ;; Container colors (for BACKGROUNDS only, never as foreground text) - (primary-container "#00459b") - (secondary-container "#0f8719") - (tertiary-container "#7f00be") - (err-container "#b20a00") + (primary-container "#0047c5") + (secondary-container "#074623") + (tertiary-container "#5700e2") + (err-container "#8c023e") ;; On-color text (for text ON accent backgrounds) - (on-primary "#222327") - (on-secondary "#222327") - (on-tertiary "#222327") - (on-err "#222327") + (on-primary "#161616") + (on-secondary "#161616") + (on-tertiary "#161616") + (on-err "#161616") ;; On-container text (for text on container backgrounds) - (on-primary-container "#cce3ff") - (on-secondary-container "#f5fdf5") - (on-tertiary-container "#edcffc") - (on-err-container "#ffcfcc") + (on-primary-container "#ccdfff") + (on-secondary-container "#d4f7e4") + (on-tertiary-container "#e0ccff") + (on-err-container "#fad1e3") ;; Outline colors - (outline-color "#6d6d6d") - (outline-variant "#474747") + (outline-color "#636363") + (outline-variant "#3d3d3d") ;; Shadow (shadow "#000000") ;; Auto-lightness variants: lighten in dark mode, darken in light mode ;; Used for heading levels 5-7, rainbow delimiters 5-7, terminal variants - (primary-variant "#0173ff") - (secondary-variant "#2dd039") - (tertiary-variant "#b233f1") - (err-variant "#ff2618")) + (primary-variant "#2c78ff") + (secondary-variant "#52dd91") + (tertiary-variant "#8f48ff") + (err-variant "#df166d")) (custom-theme-set-faces 'noctalia |
