Files
emacs-doom/config.el
Daneel 184b204a95 revert: restore functional defaults, keep only safe optimizations
Reverted (not needed with Zoom 'when typing'):
- mouse-autoselect-window/focus-follows-mouse → ON
- corfu-auto-delay → 0.15
- doom-modeline-refresh-rate → 1.0
- which-key, org-idle-time, show-paren, hl-line → defaults
- flycheck, diff-hl, auto-revert → defaults removed

Kept (safe, no functional impact):
- centered-cursor-mode OFF (still available via M-x)
- GCMH higher thresholds (less GC pressure)
- persp-mode 2.5s cache timer cancel
- auto-save idle timer: 5→10s (compromise)
2026-02-20 11:20:37 +01:00

795 lines
28 KiB
EmacsLisp
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
;;; $DOOMDIR/config.el -*- lexical-binding: t; -*-
;; --------------------------------------------------
;; Theme / UI
;; --------------------------------------------------
(setq doom-theme 'modus-vivendi-deuteranopia
doom-font (font-spec :family "JetBrains Mono" :size 14)
doom-variable-pitch-font nil)
(setq display-line-numbers-type t)
;; --------------------------------------------------
;; macOS / UX
;; --------------------------------------------------
(setq mouse-autoselect-window t
focus-follows-mouse t
select-enable-clipboard t
select-enable-primary t
inhibit-splash-screen t)
;; ;; --------------------------------------------------
;; ;; Completion (Company) minimum, bezpečné
;; ;; --------------------------------------------------
;; (after! company
;; (setq company-idle-delay 0.1
;; company-minimum-prefix-length 2
;; company-selection-wrap-around t
;; company-tooltip-limit 14
;; company-show-numbers t
;; company-require-match nil)
;; ;; Spolehlivé vyvolání v TTY
;; (map! :i "C-." #'company-complete
;; :n "C-." #'company-complete))
;; ;; --------------------------------------------------
;; ;; Robustní file completion "kdekoliv pod kurzorem"
;; ;; --------------------------------------------------
;; (defun martin/complete-file-name-at-point ()
;; "Doplň název souboru kolem kurzoru pomocí standardní file completion tabulky.
;; Funguje v libovolném textu, včetně Markdown linků (např. [x](./...))."
;; (interactive)
;; (let* ((stop-chars '(?\s ?\t ?\n ?\r ?\" ?\' ?\( ?\) ?\[ ?\] ?\< ?\> ?\{ ?\} ?, ?\; ))
;; (start (save-excursion
;; (while (and (> (point) (point-min))
;; (let ((c (char-before)))
;; (and c (not (memq c stop-chars)))))
;; (backward-char))
;; (point)))
;; (end (save-excursion
;; (while (and (< (point) (point-max))
;; (let ((c (char-after)))
;; (and c (not (memq c stop-chars)))))
;; (forward-char))
;; (point))))
;; (when (= start end)
;; (setq start (point) end (point)))
;; (let ((completion-category-defaults nil)
;; (completion-category-overrides '((file (styles basic partial-completion)))))
;; (completion-in-region start end #'completion-file-name-table))))
;; (map! :i "C-c f" #'martin/complete-file-name-at-point
;; :n "C-c f" #'martin/complete-file-name-at-point)
;; (after! markdown-mode
;; (add-hook 'markdown-mode-hook (lambda () (setq-local company-minimum-prefix-length 1)))
;; (add-hook 'gfm-mode-hook (lambda () (setq-local company-minimum-prefix-length 1))))
;; ------------------------------------------------------------
;; ORG (Doom-friendly, minimal, bez zbytečných menu)
;; ------------------------------------------------------------
(after! org
;; 0) require packages
(require 'ox-hugo)
;; 1) Kde máš org soubory
;; Uprav si cestu podle sebe. Tohle je typický Doom default.
(setq org-directory "~/org/")
(setq org-default-notes-file (expand-file-name "inbox.org" org-directory))
(setq org-agenda-files (list org-directory
(expand-file-name "projects" org-directory)
(expand-file-name "roam" org-directory)
(expand-file-name "notes" org-directory)))
(setq org-todo-keywords
'((sequence "TODO(t)" "NEXT(n)" "WAIT(w@/!)" "|" "DONE(d!)" "CANCELLED(c@)")))
(setq org-log-done 'time)
(setq org-refile-targets '((org-agenda-files :maxlevel . 5))
org-outline-path-complete-in-steps nil
org-refile-use-outline-path 'file)
(defun my/project-org-file ()
"Return path to ./project.org in current Projectile project, if it exists."
(when-let ((root (projectile-project-root)))
(let ((f (expand-file-name "project.org" root)))
(when (file-exists-p f) f))))
;; Pomocná funkce: vrátí plnou cestu k souboru v org-directory
(defun ms/org-file (name)
"Return absolute path to NAME inside `org-directory`."
(expand-file-name name org-directory))
;; 2) Org-capture templates
;; Zachovává tvoje i/n/p a jen doplňuje chybějící věci.
(setq org-capture-templates
`(
;; --- Tvoje původní šablony (beze změn) ---
("i" "Inbox task" entry
(file ,(ms/org-file "inbox.org"))
"* TODO %?\n%U\n%a\n")
("n" "Note" entry
(file+headline ,(ms/org-file "inbox.org") "Notes")
"* %?\n%U\n%a\n")
("p" "Project task" entry
(file ,(ms/org-file "inbox.org"))
"* TODO %? :project:\n%U\n%a\n")
;; --- Doplňky (minimalisticky) ---
;; Subtask do právě clockované položky
("s" "Clocked subtask" entry (clock)
"* TODO %?\n%U\n%a\n%i"
:empty-lines 1)
;; Journal do journal.org (datetree) + automatické měření času
("j" "Journal" entry
(file+olp+datetree ,(ms/org-file "journal.org"))
"\n* %<%I:%M %p> - Journal :journal:\n\n%?\n\n"
:clock-in :clock-resume
:empty-lines 1)
;; Meeting do journal.org (datetree) + měření času
("m" "Meeting" entry
(file+olp+datetree ,(ms/org-file "journal.org"))
"* %<%I:%M %p> - %^{Meeting title} :meetings:\nContext: %a\n\n%?\n\n"
:clock-in :clock-resume
:empty-lines 1)
;; Checking Email do journal datetree
("e" "Checking Email" entry
(file+olp+datetree ,(ms/org-file "journal.org"))
"* Checking Email :email:\n\n%?"
:clock-in :clock-resume
:empty-lines 1)
;; Weight metrika do tabulky v metrics.org pod headline "Weight"
("w" "Weight" table-line
(file+headline ,(ms/org-file "metrics.org") "Weight")
"| %U | %^{Weight} | %^{Notes} |"
:kill-buffer t)
)))
;; ------------------------------------------------------------
;; (Volitelné) Kvalita života nic navíc, jen užitečné defaulty
;; ------------------------------------------------------------
(after! org
;; ať se po capture vrací do stejného okna (někomu pomáhá)
(setq org-capture-restore-window-after-quit t))
(after! org
(add-hook 'org-export-before-processing-hook
(lambda (_backend)
(org-update-all-dblocks))))
;; --------------------------------------------------
;; Dired
;; --------------------------------------------------
(after! dired
(put 'dired-find-alternate-file 'disabled nil)
(map! :map dired-mode-map
"RET" #'dired-find-alternate-file
"^" #'dired-up-directory))
;; --------------------------------------------------
;; PlantUML (server)
;; --------------------------------------------------
(add-to-list 'auto-mode-alist '("\\.puml\\'" . plantuml-mode))
(add-to-list 'auto-mode-alist '("\\.plantuml\\'" . plantuml-mode))
(after! plantuml-mode
(setq plantuml-default-exec-mode 'server
plantuml-server-url "https://www.plantuml.com/plantuml"
plantuml-output-type "svg"
plantuml-verbose t))
(defun my/plantuml-encode-hex (text)
"PlantUML HEX encoding: ~h + hex(UTF-8 bytes)."
(let* ((utf8 (encode-coding-string text 'utf-8 t)))
(concat "~h"
(apply #'concat
(mapcar (lambda (b) (format "%02x" b))
(append utf8 nil))))))
(defun my/plantuml-fix-png-header (file)
"Odstraní vše před PNG signaturou."
(let ((sig (unibyte-string #x89 ?P ?N ?G ?\r ?\n #x1a ?\n)))
(with-temp-buffer
(set-buffer-multibyte nil)
(insert-file-contents-literally file)
(goto-char (point-min))
(unless (looking-at (regexp-quote sig))
(let ((pos (search-forward sig nil t)))
(unless pos (user-error "PNG signature nenalezena"))
(delete-region (point-min) (- pos (length sig)))
(let ((coding-system-for-write 'binary))
(write-region (point-min) (point-max) file nil 'silent)))))))
(defun my/plantuml-render-server (type)
"Render aktuální .puml přes PlantUML server do PNG nebo SVG."
(interactive (list (completing-read "Type: " '("png" "svg") nil t "png")))
(unless buffer-file-name (user-error "Otevři .puml jako soubor"))
(let* ((text (buffer-substring-no-properties (point-min) (point-max)))
(encoded (my/plantuml-encode-hex text))
(server (string-remove-suffix "/" plantuml-server-url))
(url (format "%s/%s/%s" server type encoded))
(out (concat (file-name-sans-extension buffer-file-name) "." type)))
(url-copy-file url out t)
(when (string-equal type "png")
(my/plantuml-fix-png-header out))
(message "PlantUML uložen: %s" out)
out))
(after! plantuml-mode
(define-key plantuml-mode-map (kbd "C-c C-p")
(lambda () (interactive) (my/plantuml-render-server "png")))
(define-key plantuml-mode-map (kbd "C-c C-s")
(lambda () (interactive) (my/plantuml-render-server "svg"))))
;; --------------------------------------------------
;; PATH fix for MacTeX
;; --------------------------------------------------
(setenv "PATH" (concat "/Library/TeX/texbin:" (getenv "PATH")))
(add-to-list 'exec-path "/Library/TeX/texbin")
;; --------------------------------------------------
;; Python
;; --------------------------------------------------
(setq python-shell-interpreter "python3")
(after! org
(setq org-babel-python-command "python3")
(require 'ob-python))
;;; GPTel + OpenWebUI (OpenRouter behind it)
(use-package! gptel
:config
;; 1) API key z env (bez klíčů v configu)
(defun my/openwebui-key ()
(or (getenv "OPENWEBUI_API_KEY")
(user-error "Missing OPENWEBUI_API_KEY env var")))
;; 2) Stáhnout seznam modelů z OpenWebUI /api/models
(defun my/openwebui-fetch-model-ids ()
"Return list of model ids from OpenWebUI /api/models (field data[].id)."
(require 'url)
(require 'json)
(let* ((url-request-method "GET")
(url-request-extra-headers
`(("Authorization" . ,(concat "Bearer " (funcall #'my/openwebui-key))))))
(with-current-buffer (url-retrieve-synchronously
"https://ai.apps.sukany.cz/api/models" t t 15)
(goto-char (point-min))
(re-search-forward "\n\n" nil 'move) ;; přeskočit HTTP hlavičky
(let* ((json-object-type 'alist)
(json-array-type 'list)
(json-key-type 'symbol)
(obj (json-read))
(data (alist-get 'data obj))
(ids (delq nil (mapcar (lambda (it) (alist-get 'id it)) data))))
(kill-buffer (current-buffer))
ids))))
(defvar my/openwebui-models-cache nil)
(defun my/openwebui-models ()
"Cached list of model ids. Falls back to a minimal list if API fails."
(or my/openwebui-models-cache
(setq my/openwebui-models-cache
(condition-case err
(my/openwebui-fetch-model-ids)
(error
(message "OpenWebUI models fetch failed: %s" err)
;; fallback zkus běžné OpenRouter ids (může, ale nemusí být dostupné)
'("openai/gpt-4o-mini" "openai/gpt-4.1-mini"))))))
(defun my/openwebui-refresh-models ()
"Clear cache and refetch OpenWebUI model list."
(interactive)
(setq my/openwebui-models-cache nil)
(message "OpenWebUI models refreshed: %d" (length (my/openwebui-models))))
;; 3) Backend pro OpenWebUI (OpenAI-compatible chat/completions)
(setq gptel-backend
(gptel-make-openai "OpenWebUI"
:host "ai.apps.sukany.cz"
:protocol "https"
:key #'my/openwebui-key
:endpoint "/api/chat/completions"
:stream t
;; často stabilnější přes reverzní proxy/ingress:
:curl-args '("--http1.1")
:models (my/openwebui-models)))
;; 4) Default model: ekonomický a praktický
;; Preferuj openai/gpt-5.2-mini (levný, rychlý), jinak první dostupný model.
;; (GPT-5.2 mini je běžně uváděn jako “fast, affordable” a na OpenRouter má id openai/gpt-4o-mini) :contentReference[oaicite:2]{index=2}
(let* ((models (my/openwebui-models))
(preferred "openai/gpt-5-mini"))
(setq gptel-model (if (member preferred models)
preferred
(car models))))
;; 5) Presety (rychlé přepínání podle úlohy) :contentReference[oaicite:3]{index=3}
;; Pozn.: Presety jen nastavují model/backend/system atd. žádná magie navíc.
(gptel-make-preset 'fast
:description "Default (rychlý/levný) běžná práce"
:backend "OpenWebUI"
:model "openai/gpt-4o-mini"
:system "Odpovídej česky. Buď konkrétní, krokový. Neomáčej to."
:temperature 0.2)
(gptel-make-preset 'coding
:description "Kód / refaktor / review (silnější model, když je potřeba)"
:backend "OpenWebUI"
;; nastav si sem model, který opravdu máš v /api/models:
:model "openai/gpt-4.1-mini"
:system "Jsi přísný code reviewer. Navrhuj konkrétní změny a rizika."
:temperature 0.1)
(gptel-make-preset 'deep
:description "Náročná analýza / architektura"
:backend "OpenWebUI"
;; nastav si sem něco silnějšího z tvého seznamu:
:model "openai/gpt-4.1"
:system "Postupuj systematicky. Dej varianty, tradeoffs a doporučení."
:temperature 0.2)
;; 6) (Volitelné) Debug log, když něco zlobí:
;; (setq gptel-log-level 'debug)
)
;; 7) Mini CLI: volání z command line přes `emacs --batch`
;; Použití níže v příkladech
(defun my/gptel-cli (prompt &optional model system)
"Send PROMPT via gptel and print response to stdout."
(require 'gptel)
(let* ((done nil)
(result nil)
(gptel-model (or model gptel-model))
(gptel--system-message (or system gptel--system-message)))
(gptel-request prompt
:callback (lambda (response _info)
(setq result response)
(setq done t)))
(while (not done)
(accept-process-output nil 0.05))
(princ result)))
;; --------------------------------------------------
;; GPTel keybindings (safe in Doom) SPC o g ...
;; --------------------------------------------------
(after! gptel
(map! :leader
(:prefix ("o g" . "GPTel")
:desc "GPTel send (region or buffer)" "s" #'gptel-send
:desc "GPTel menu (model/scope/preset)" "m" #'gptel-menu
:desc "GPTel chat buffer" "c" #'gptel
:desc "GPTel abort request" "x" #'gptel-abort
:desc "Refresh OpenWebUI models" "R" #'my/openwebui-refresh-models)))
;; performance
(setq which-key-idle-delay 0)
;; auto save
(setq auto-save-default nil) ;; zruší #file# bordel
(defun my/save-all-buffers ()
(save-some-buffers t))
(run-with-idle-timer 10 t #'my/save-all-buffers)
;; centered cursor mode
(use-package! centered-cursor-mode
:config
(setq ccm-vpos-init 0.5) ;; 0.5 = střed okna
;; VYPNUTO — koliduje s macOS Zoom focus follower (způsobuje skákání obrazu)
;; (global-centered-cursor-mode +1)
)
;; Profiling
(setq gc-cons-threshold (* 100 1024 1024) ;; 100 MB
gc-cons-percentage 0.6)
;; GCMH — Doom's GC manager. Zvýšit idle delay (default 15s, ale gcmh ho mění)
;; gcmh-idle-delay ovlivňuje jak často se spouští GC v idle → redisplay → Zoom jump
(after! gcmh
(setq gcmh-idle-delay 'auto ;; nebo konkrétní číslo, např. 60
gcmh-auto-idle-delay-factor 20 ;; default 10, zvýšeno pro méně GC
gcmh-high-cons-threshold (* 200 1024 1024))) ;; 200MB → GC se spustí méně často
(add-hook 'focus-out-hook #'garbage-collect)
(setq doom-modeline-refresh-rate 1.0)
(setq which-key-idle-delay 0.8
which-key-idle-secondary-delay 0.05)
(setq org-idle-time 1.0)
;; --------------------------------------------------
;; macOS Zoom accessibility — minimalizace redraws
;; --------------------------------------------------
;; persp-mode (workspaces) — hlavní viník skákání (2.5s timer)
;; Necháme persp-mode normálně nastartovat, ale po 3s zrušíme jeho cache timer
(run-with-timer 3 nil
(lambda ()
(when (and (boundp 'persp-frame-buffer-predicate-buffer-list-cache--timer)
(timerp persp-frame-buffer-predicate-buffer-list-cache--timer))
(cancel-timer persp-frame-buffer-predicate-buffer-list-cache--timer)
(setq persp-frame-buffer-predicate-buffer-list-cache--timer nil)
(message "persp-mode 2.5s cache timer cancelled for Zoom accessibility"))))
;; --- macOS clipboard: pbcopy/pbpaste (funguje i v terminal Emacs) ---
(defun my/pbcopy (text &optional _push)
"Send TEXT to the macOS clipboard using pbcopy."
(let ((process-connection-type nil))
(let ((proc (start-process "pbcopy" "*pbcopy*" "pbcopy")))
(process-send-string proc text)
(process-send-eof proc))))
(defun my/pbpaste ()
"Return text from the macOS clipboard using pbpaste."
(when (executable-find "pbpaste")
(string-trim-right
(shell-command-to-string "pbpaste"))))
(setq select-enable-clipboard t
select-enable-primary t)
;; Emacs -> system clipboard
(setq interprogram-cut-function #'my/pbcopy)
;; system clipboard -> Emacs
(setq interprogram-paste-function #'my/pbpaste)
;; Ať Evil používá clipboard (y/d/c budou do systému)
(after! evil
(setq evil-want-clipboard t))
;; !!! DANGEROUS: disable TLS verification globally !!!
(setq gnutls-verify-error nil)
(setq gnutls-algorithm-priority "NORMAL:-VERS-TLS1.3")
(after! projectile
(setq projectile-enable-caching nil
projectile-indexing-method 'alien)
(when (executable-find "fd")
(setq projectile-generic-command
"fd . -0 --type f --hidden --follow --exclude .git --color=never")))
;; mu4e
(add-to-list 'load-path (expand-file-name "/opt/homebrew/opt/mu/share/emacs/site-lisp/mu/mu4e"))
(after! mu4e
(setq mu4e-maildir "~/.mail"
mu4e-get-mail-command "mbsync personal"
mu4e-update-interval 300
mu4e-change-filenames-when-moving t
mu4e-view-show-images t))
(after! mu4e
(setq mu4e-maildir (expand-file-name "~/.mail")
mu4e-get-mail-command "mbsync personal"
mu4e-update-interval 300
mu4e-change-filenames-when-moving t
mu4e-view-show-images t
;; TADY je to důležité:
mu4e-sent-folder "/personal/Sent"
mu4e-drafts-folder "/personal/Drafts"
mu4e-trash-folder "/personal/Trash"
mu4e-refile-folder "/personal/Archive"))
(after! mu4e
(setq sendmail-program "msmtp"
message-send-mail-function #'message-send-mail-with-sendmail
mail-specify-envelope-from t
message-sendmail-envelope-from 'header))
(setq user-mail-address "martin@sukany.cz"
user-full-name "Martin Sukany")
(after! mu4e
;; thread view
(setq mu4e-headers-show-threads t)
(setq mu4e-headers-include-related t)
;; pěkné zobrazení
(setq mu4e-headers-fields
'((:human-date . 12)
(:flags . 6)
(:from . 22)
(:subject)))
;; strom vláken (lepší orientace)
(setq mu4e-use-fancy-chars t))
(after! mu4e
(setq mu4e-headers-mark-for-thread t))
;;; ================================
;;; Emacspeak robust ON/OFF for Doom
;;; Default: OFF (won't auto-restart)
;;; Keys:
;;; SPC t s -> Speech ON
;;; SPC t S -> Speech OFF
;;; ================================
(defconst my/emacspeak-dir (expand-file-name "~/.emacspeak"))
(defconst my/emacspeak-wrapper (expand-file-name "~/.local/bin/emacspeak-mac"))
;; Emacspeak uses this to start the speech server.
(setq dtk-program my/emacspeak-wrapper)
;;(setq dtk-program "espeak")
;; State flags
(defvar my/emacspeak-loaded nil)
(defvar my/emacspeak-enabled nil)
;; Hard inhibit: when non-nil, Emacspeak is NOT allowed to start/restart the server.
(defvar my/emacspeak-inhibit-server t)
(defun my/emacspeak--ensure-loaded ()
"Load Emacspeak once, safely, without breaking Doom startup."
(unless my/emacspeak-loaded
(setq my/emacspeak-loaded t)
(setq emacspeak-directory my/emacspeak-dir)
;; Load late-ish (but still inside the command that enables it)
(load-file (expand-file-name "lisp/emacspeak-setup.el" emacspeak-directory))
;; After Emacspeak is present, install inhibition advices.
;; These prevent the common 'it restarts by itself' problem.
(with-eval-after-load 'dtk-speak
(dolist (fn '(dtk-initialize dtk-start-process dtk-speak))
(when (fboundp fn)
(advice-add
fn :around
(lambda (orig &rest args)
(if my/emacspeak-inhibit-server
;; OFF mode: do nothing (and crucially: don't restart speaker)
nil
(apply orig args)))))))))
(defun my/emacspeak-on ()
"Enable speech (and allow server start)."
(interactive)
(setq my/emacspeak-inhibit-server nil)
(my/emacspeak--ensure-loaded)
(setq my/emacspeak-enabled t)
(when (fboundp 'dtk-restart)
(ignore-errors (dtk-restart)))
(when (fboundp 'dtk-speak)
(ignore-errors (dtk-speak "Emacspeak on.")))
(message "Emacspeak ON"))
(defun my/emacspeak-off ()
"Disable speech robustly (stop + prevent auto-restart)."
(interactive)
;; First: inhibit any future attempts to start/restart.
(setq my/emacspeak-enabled nil)
(setq my/emacspeak-inhibit-server t)
;; Stop current speech if any.
(when (fboundp 'dtk-stop)
(ignore-errors (dtk-stop)))
;; Kill the server process hard (if it exists).
(when (boundp 'dtk-speaker-process)
(let ((p dtk-speaker-process))
(when (processp p)
(ignore-errors (set-process-sentinel p nil))
(ignore-errors (delete-process p))))
(setq dtk-speaker-process nil))
(message "Emacspeak OFF (server restart inhibited)"))
;; Doom leader keys
(map! :leader
(:prefix ("t" . "toggle")
:desc "Speech ON" "s" #'my/emacspeak-on
:desc "Speech OFF" "S" #'my/emacspeak-off))
;; ----------------------------
;; Emacspeak defaults (global)
;; ----------------------------
(with-eval-after-load 'dtk-speak
;; Default punctuation mode: none / some / all
;; (Emacspeak manual: dtk-set-punctuations supports 'none 'some 'all)
(setq dtk-speech-rate-base 300)
(setq-default dtk-punctuation-mode 'none))
(with-eval-after-load 'emacspeak
;; Typing feedback:
;; nechceš znaky, chceš slova + řádky
;; (Emacspeak manual: character/word/line echo)
(setq-default emacspeak-character-echo nil)
(setq-default emacspeak-word-echo t)
(setq-default emacspeak-line-echo t))
(map! :leader
(:prefix ("h" . "help")
:desc "Describe bindings (buffer-local)" "B" #'describe-bindings))
;; 1) ulož si globální default hodnotu
(setq dtk-default-speech-rate 400)
;; 2) aplikuj ji ve chvíli, kdy je TTS už inicializované
(with-eval-after-load 'dtk-speak
(defun my/dtk-apply-global-default-rate (&rest _)
"Apply global default speech rate after TTS init/restart."
(when (fboundp 'dtk-set-rate)
;; PREFIX arg => set GLOBAL default (per Emacspeak manual)
(ignore-errors (dtk-set-rate dtk-default-speech-rate t))))
;; po každé inicializaci/restartu TTS
(advice-add 'dtk-initialize :after #'my/dtk-apply-global-default-rate))
;; ElFeed (RSS)
(map! :leader
:desc "Elfeed" "o r" #'elfeed)
(after! org
;; pokud máš org soubory jinde než ~/org, nastav org-directory sem:
;; (setq org-directory "~/org")
;; elfeed-org: řekni mu explicitně, kde je elfeed.org
(setq rmh-elfeed-org-files
(list (expand-file-name "elfeed.org" org-directory))))
(after! elfeed
;; elfeed-org musí být inicializovaný, jinak se elfeed.org nemusí načítat
(require 'elfeed-org)
(elfeed-org))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; CORFU CONFIGURATION (modern completion UI)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(after! corfu
;; automatické completions
(setq corfu-auto t
corfu-auto-delay 0.15
corfu-auto-prefix 2
;; cyklování kandidátů
corfu-cycle t
;; předvybrat první kandidát
corfu-preselect 'prompt
;; lepší UX
corfu-quit-no-match 'separator
corfu-preview-current nil)
;; zapnout globálně
(global-corfu-mode))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; CAPE — zdroje completion
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Corfu
(after! corfu
(setq corfu-auto t
corfu-auto-delay 0.15
corfu-auto-prefix 2
corfu-cycle t)
(global-corfu-mode))
;;; Cape (zdroje completion-at-point)
(use-package! cape
:after corfu
:config
(defun martin/cape-capf-setup ()
"Zdroje doplňování použitelné téměř všude (bez LSP)."
;; Slova z bufferů
(add-to-list 'completion-at-point-functions #'cape-dabbrev 0)
;; Cesty k souborům
(add-to-list 'completion-at-point-functions #'cape-file 0)
;; Keywords (užitečné hlavně v prog-mode)
(add-to-list 'completion-at-point-functions #'cape-keyword 0)
;; Elisp symboly (jen když píšeš elisp, ale nevadí ani jinde)
(add-to-list 'completion-at-point-functions #'cape-elisp-symbol 0))
(add-hook 'prog-mode-hook #'martin/cape-capf-setup)
(add-hook 'text-mode-hook #'martin/cape-capf-setup))
;; Corfu popup i v terminálu (iTerm2 / ssh / tmux)
(use-package! corfu-terminal
:when (not (display-graphic-p))
:after corfu
:config
(corfu-terminal-mode +1))
;; TRAMP
(after! tramp
(setq projectile-git-command "git ls-files -zco --exclude-standard"
projectile-indexing-method 'alien))
;; Vypnout VC a projectile přes TRAMP — hlavní příčina visení
(setq vc-ignore-dir-regexp
(format "%s\\|%s" vc-ignore-dir-regexp tramp-file-name-regexp))
;; Vypnout projectile zcela pro remote
(defadvice projectile-project-root (around ignore-remote first activate)
(unless (file-remote-p default-directory) ad-do-it))
;; TRAMP cache — neopakovat drahé remote dotazy
(setq remote-file-name-inhibit-cache nil
tramp-verbose 1)
;; Latex org export - hack pro spravne tabulky
(defun my/org-count-table-columns (line)
"Spočítej počet datových sloupců v Org table LINE."
(length (cl-remove-if
(lambda (s) (string-match-p "^-*$" (string-trim s)))
(cdr (butlast (split-string line "|"))))))
(defun my/org-table-attr-latex-spec (ncols)
"Vygeneruj column spec pro tabularx: první sloupec l, zbytek Y (centered X)."
(concat "l" (make-string (max 0 (1- ncols)) ?Y)))
(defun my/org-auto-tabularx (backend)
"Automaticky přidej #+ATTR_LATEX tabularx před každou tabulku při LaTeX exportu."
(when (org-export-derived-backend-p backend 'latex)
(save-excursion
(goto-char (point-min))
(while (not (eobp))
(cond
;; Řádek začíná | — může být začátek tabulky
((looking-at "^|")
(let ((prev-line (save-excursion
(forward-line -1)
(buffer-substring-no-properties
(line-beginning-position) (line-end-position)))))
;; Je to PRVNÍ řádek tabulky? (předchozí řádek NEzačíná |)
(when (not (string-match-p "^|" prev-line))
;; Chybí #+ATTR_LATEX?
(when (not (string-match-p "^#\\+ATTR_LATEX" prev-line))
(let* ((table-line (buffer-substring-no-properties
(line-beginning-position) (line-end-position)))
(ncols (my/org-count-table-columns table-line))
(spec (my/org-table-attr-latex-spec ncols))
(attr (format "#+ATTR_LATEX: :environment tabularx :width \\textwidth :align %s\n"
spec)))
(when (> ncols 0)
(insert attr))))))
(forward-line))
(t
(forward-line)))))))
;; Zaregistruj hook — spustí se před každým exportem
(add-hook 'org-export-before-processing-hook #'my/org-auto-tabularx)
;; Volitelně: zapni booktabs styl (horizontal rules v tabulkách)
;; (setq org-latex-tables-booktabs t)
;;
;; Jak použít:
;; 1. Zkopíruj tento obsah do ~/.config/doom/config.el
;; 2. Spusť: doom sync (nebo M-x doom/reload)
;; 3. Exportuj dokument: SPC m e l p
;; Tabulky se automaticky obalí do tabularx — nic nemusíš přidávat ručně.
;;
;; Chceš jiný výchozí column spec? Uprav my/org-table-attr-latex-spec.
;; Například pro "všechny sloupce rovnoměrně": (make-string ncols ?Y)