Literate and modular Doom Emacs configuration.
The conventions of this org file:
- First level heading respect Doom’s order
- Configured module goes to second level heading
-
- Prerequisite packages tangled into packages.toml (
Archonly) - Bootstrap scripts tangled to install.d/
- Comment a module with C-c ; at heading
- Prerequisite packages tangled into packages.toml (
- One line config goes into * Tangle
- Often a declare in init.el
(doom!
:input
<<input>>
:completion
<<completion>>
:ui
<<ui>>
hl-todo
indent-guides
(ligatures +extra)
nav-flash
ophints
smooth-scroll
(vc-gutter +pretty)
(window-select +numbers)
workspaces
;zen
:editor
<<editor>>
format
;file-templates
;multiple-cursors
parinfer
word-wrap
:emacs
<<emacs>>
electric
;ibuffer
undo
vc
:term
<<term>>
:checkers
<<checkers>>
(syntax +childframe)
:tools
<<tools>>
(lsp +eglot)
biblio
debugger
;editorconfig
(eval +overlay)
tree-sitter
;upload
:os
;tty
:lang
markdown
<<lang>>
;(cc +lsp)
emacs-lisp
json
:email
<<email>>
:app
calendar
<<app>>
:config
literate
(default +bindings +smartparens))The order of modules in above list make sense
Extra packages from MELPA
Default tangle to config.el
(load (expand-file-name "private/identity.el" doom-user-dir) nil 'nomessage)
<<predefines>>Default install all packages in packages.toml
enable_default=true(defun +my/is-utility-daemon ()
"If current session run from daemon called `utility`."
(equal (daemonp) "utility"))Open link with host Librewolf browser
(if (featurep :system 'wsl)
(setq browse-url-firefox-program "librewolf.exe"))(map! :leader
:desc "Eval expression" ":" #'pp-eval-expression
:desc "M-x" ";" #'execute-extended-command)(defun +my/ace-swap-buffer-keep-focus ()
"Swap current window's buffer with ace-selected window, keep cursor here."
(interactive)
(require 'ace-window)
(let ((cur-win (selected-window))
(cur-buf (current-buffer)))
(aw-select " Ace - Swap Buffer"
(lambda (win)
(set-window-buffer cur-win (window-buffer win))
(set-window-buffer win cur-buf)))))
(map! :leader :prefix "w"
:desc "Swap buffer keep focus" "X" #'+my/ace-swap-buffer-keep-focus)(add-hook 'ediff-prepare-buffer-hook
(lambda ()
(+word-wrap-mode 1)
(when (derived-mode-p 'org-mode)
(org-fold-show-all))))[chinese]
packages=["base-devel",
"librime" # for +rime
](chinese<<chinese-flags()>>)Default simplified Chinese input
(after! liberime
(liberime-try-select-schema "luna_pinyin_simp")
(setq pyim-default-scheme 'rime-quanpin))This is useful for allowing breaking after CJK characters and improves the word-wrapping for CJK text mixed with Latin text.
(setq word-wrap-by-category t)Disable autoload generation for names to suppress obsolete defadvice warnings.
(package! names
:recipe (:build (:not autoloads)))GitHub - kanglmf/emacs-chinese-word-segmentation
kanglmf/emacs-chinese-word-segmentation#7
(package! cns
:recipe (:host github :repo "kanglmf/emacs-chinese-word-segmentation"
:pre-build ("make")
:files ("*.el" "cnws" "cppjieba/dict")))(use-package! cns
:commands (cns-mode global-cns-mode cns-auto-enable)
:config
(defvar cns-packages-path
(expand-file-name (format "straight/build-%s/cns" emacs-version) doom-local-dir))
(setq cns-process-type 'shell
cns-prog (expand-file-name "cnws" cns-packages-path)
cns-dict-directory (expand-file-name "dict" cns-packages-path)))
;; :hook
;; (find-file . cns-auto-enable))[corfu]
packages=["words"](corfu +icons +dabbrev)(setq corfu-on-exact-match 'show)
(map! :after cape :i "C-c p" cape-prefix-map)
;; dabb_ only match dabbrev not Dabbrev!
(after! dabbrev
(setq dabbrev-case-fold-search nil
;; NEVER complete Chinese
dabbrev-abbrev-char-regexp "[-_A-Za-z0-9]"))(vertico +icons +childframe)tumashu/vertico-posframe#16 Disable vertico-posframe when Emacs runs in terminal
(after! vertico-multiform
(add-to-list 'vertico-multiform-commands
'(consult-line
posframe
(vertico-posframe-fallback-mode . vertico-buffer-mode))))(after! vertico-posframe
(setq vertico-posframe-width 88))(package! ewal-doom-themes)(use-package! ewal-doom-themes)Register and automatically refresh the dynamically generated Dank Emacs theme.
(defvar +my/dank-theme-watch nil)
(defun +my/theme-file-path (theme)
(locate-file (format "%s-theme.el" theme)
(cl-remove-if-not #'stringp custom-theme-load-path)))
(defun +my/dank-theme-watch-sync ()
(cond
((and (eq (car custom-enabled-themes) 'dank-emacs)
(not +my/dank-theme-watch))
(require 'filenotify)
(setq +my/dank-theme-watch
(file-notify-add-watch
(+my/theme-file-path 'dank-emacs) '(change)
(lambda (_)
(when (eq (car custom-enabled-themes) 'dank-emacs)
(load-theme 'dank-emacs t))))))
((and (not (eq (car custom-enabled-themes) 'dank-emacs))
+my/dank-theme-watch)
(file-notify-rm-watch +my/dank-theme-watch)
(setq +my/dank-theme-watch nil))))
(add-hook 'load-theme-hook #'+my/dank-theme-watch-sync)load wal theme from command line
emacs-client -e "(load-theme 'ewal-doom-themes t)"GitHub - donniebreve/rose-pine-doom-emacs: Soho vibes for DOOM Emacs
doomrestore last selected theme
(defun load-in-doom-dir (file-name &optional dir)
(let* ((dir (or dir doom-user-dir))
(full-name (expand-file-name file-name dir)))
(if (file-exists-p full-name)
(load full-name))))
(load-in-doom-dir "theme.el" doom-cache-dir)difficult to choose theme? random it
(defun +my/random-theme ()
(interactive)
(let* ((all-themes (custom-available-themes))
(next-theme (nth (random (length all-themes)) all-themes)))
(consult-theme next-theme)
(message (format "switch to theme: %s" next-theme))))
(map! :leader
(:prefix "t"
:desc "Random theme" "t" #'+my/random-theme))track current theme for later load
(defun +my/save-theme (prev new-theme &rest args)
(let
((theme-config-file (expand-file-name "theme.el" doom-cache-dir)))
(write-region
(format "(setq doom-theme '%s)\n" new-theme) nil theme-config-file)
(message "Switch to theme: %s" new-theme)))
(add-variable-watcher 'doom-theme #'+my/save-theme)bold italic underline stride
Set default font size, WSL currently not aware dpi settings in
~/.Xresources while float size makes it work on Linux.
- doomemacs/doomemacs#6131 DPI settings are not respected by “setq doom-font”
- Change my fonts - doom/docs/faq
(setq +my/font-size (* (if (featurep :system 'wsl) 1.5 1) 12.0))
(setq doom-font (font-spec :family "Maple6 NF" :size +my/font-size))(add-hook 'doom-load-theme-hook
(lambda ()
;; quoted text in info
(set-face-attribute 'fixed-pitch-serif nil :slant 'italic :foreground "tomato")
;; prefer italic comment font
(set-face-attribute 'font-lock-comment-face nil :slant 'italic)))dashboard(setq fancy-splash-image (expand-file-name "assets/bitmap_512x.png" doom-user-dir))[modeline]
packages=["otf-comicshanns-nerd"]modelinecustom doom-modeline font, valid on startup and persist after cmd:doom/reload-theme
(defun +my/set-mode-line-font ()
(set-face-font 'mode-line (font-spec :family "ComicShannsMono Nerd Font" :size (+ +my/font-size 1.5)))
(set-face-font 'mode-line-inactive (font-spec :family "ComicShannsMono Nerd Font" :size (+ +my/font-size 1.5))))
(add-hook 'doom-load-theme-hook #'+my/set-mode-line-font 90)(package! keycast)tarsius/keycast#7 Add support for moody and doom-modeline.
(use-package! keycast
:commands (keycast-mode)
:init
(map! :leader
(:prefix "t"
:desc "Toggle keycast" "k" #'keycast-mode))
:config
(define-minor-mode keycast-mode
"Show current command and its key binding in the mode line (fix for use with doom-mode-line)."
:global t
(if keycast-mode
(progn (add-hook 'pre-command-hook 'keycast--update t)
(setq keycast-mode-line-window-predicate
'keycast-active-frame-bottom-right-p))
(remove-hook 'pre-command-hook 'keycast--update)
(setq keycast-mode-line-window-predicate 'ignore)))
(add-to-list 'global-mode-string '("" keycast-mode-line)))(popup +defaults)(setq split-width-threshold 120)Prefer stack at right for following info windows, since they are fill-columned
(set-popup-rules!
'(("^\\*\\([Hh]elp\\|Apropos\\)" ; help messages
:side right :size 80 :slot 2 :vslot -8 :select t)
("^\\*\\(?:Wo\\)?Man "
:side right :size 80 :vslot -6 :select t)
("^\\*info\\*$"
:side right :size 80 :slot 2 :vslot 2 :select t)))[unicode]
packages=[
"ttf-symbola", # required by doom
"quivira", # org ellipsis ⤵, ℤ
"ttf-dejavu", # org heading ◉ ✸ ∈
"ttf-sarasa_ui-sc", # ¬
"noto-fonts-emoji", # color emoji
"maplemono-cn", # Chinese font
"ttf-nerd-fonts-symbols-mono" # nerd font
]unicodeTo get unicode block name for a character, kbd:SPC h ’ on it to get it’s lexical code, and search in Plane (Unicode) - Wikipedia
Doom’s way of change unicode font, but it will be shadowed by fn:doom-init-fonts-h if var:doom-symbol-font is set.
(after! unicode-fonts
;; ℕ, ⤵, 𝔹
(dolist (unicode-block '("Letterlike Symbols" "Supplemental Arrows-B" "Mathematical Alphanumeric Symbols"))
(push "Quivira" (cadr (assoc unicode-block unicode-fonts-block-font-mapping))))
;; ⨂
(dolist (unicode-block '("Supplemental Mathematical Operators"))
(push "DejaVu Math TeX Gyre" (cadr (assoc unicode-block unicode-fonts-block-font-mapping))))
;; ∈ ∅
(dolist (unicode-block '("Mathematical Operators"))
(push "DejaVu Sans" (cadr (assoc unicode-block unicode-fonts-block-font-mapping))))
;; ¬
(dolist (unicode-block '("Halfwidth and Fullwidth Forms"))
(push "Sarasa UI SC" (cadr (assoc unicode-block unicode-fonts-block-font-mapping)))))Add to var:after-setting-font-hook not work well, font display diffs after cmd:doom/reload-theme
(defun +my/unicode-fonts ()
(dolist (unicode-block '("Letterlike Symbols" "Supplemental Arrows-B"))
(push "Quivira" (cadr (assoc unicode-block unicode-fonts-block-font-mapping)))))
(add-hook 'after-setting-font-hook #'+my/unicode-fonts 60)Add hook but with fn:set-fontset-font succeed
(defun +my/unicode-fonts ()
; Supplemental Arrows-B, include ⤵
(set-fontset-font t '(#x2900 . #x297f) "Quivira")
; Mathematical symbols, 𝔹
(set-fontset-font t '(#x1d400 . #x1d7ff) "Quivira")
; Mathematical operators, ⨂
(set-fontset-font t '(#x2208 . #x22ff) "DejaVu Math TeX Gyre")
(dolist (chars '("¬")) ; keywords =not= in code ligatures
(set-fontset-font t (string-to-char chars) "Sarasa Gothic SC")))
(add-hook 'after-setting-font-hook #'+my/unicode-fonts 60)see more in Emacs, fonts and fontsets
Rongcui Dong’s Site - 如何在 Doom Emacs 中设置中文
Check alignment between Chinese and English.
Emacs is the advanced, extensible, customizable, self-documenting editor.
# Emacs is the advanced, extensible, customizable, self-documenting editor.
Emacs 是一款可扩展可自定义且自带文档的高级 editor.These settings will not work in daemon mode if added instead to
doom-init-ui-hook, as they might be overridden by unicode-fonts-setup.
Additionally, consider changing the font for all fontset instead of just
(frame-parameter nil 'font). If the emoji font is not set here, you will need
to use doom/reload-font later to enable proper color emoji display.
- GitHub - hick/emacs-chinese: Emacs 相关中文问题以及解决方案
- fonts - How do I get colour emoji to display in Emacs - Emacs Stack Exchange
(defun +my/cjk-font ()
(dolist (charset '(kana han cjk-misc bopomofo))
(set-fontset-font t charset
(font-spec :family "Maple Mono CN")))
;; why not set color emoji font at the same time
(set-fontset-font t 'emoji "Noto Color Emoji"))
(add-hook 'after-setting-font-hook #'+my/cjk-font)(package! rainbow-mode)(defun +my/kill-emacs-without-persp-save ()
"Kill Emacs without saving perspectives."
(interactive)
(let ((persp-auto-save-opt 0)
(confirm-kill-emacs nil))
(save-buffers-kill-emacs t)))
(map! :leader
:desc "Kill Emacs (no persp save)"
:prefix "q"
"Q" #'+my/kill-emacs-without-persp-save)(after! projectile
(add-to-list 'projectile-globally-ignored-directories
"*\\.run\\.tmp$")
(dolist (m '("helpful-mode" "dired-mode"))
(cl-pushnew m projectile-globally-ignored-modes :test #'equal))
;; why I have a mass of projects
(setq projectile-track-known-projects-automatically nil)
(add-to-list 'projectile-globally-ignored-buffers "*doom*")
;; Prefer .git over .projectile so nested git repos are recognized correctly
(setq projectile-project-root-functions
'(projectile-root-local
projectile-root-bottom-up
projectile-root-marked
projectile-root-top-down
projectile-root-top-down-recurring)))For non git project, better add following in project root .dir-locals.el
((nil . ((projectile-indexing-method . hybrid))))and add ignored files in .projectile.
(after! nerd-icons
(setq nerd-icons-scale-factor 0.9))Transparency
(defun my/toggle-transparency ()
"Toggle between opaque and semi-transparent frame."
(interactive)
(let* ((opaque 100)
(transparent 92)
(cur (or (frame-parameter nil 'alpha-background) opaque))
(new (if (= cur opaque) transparent opaque)))
(dolist (frame (frame-list))
(set-frame-parameter frame 'alpha-background new))
(setf (alist-get 'alpha-background default-frame-alist) new)))
(map! :leader
(:prefix "t"
:desc "Toggle transparency" "T" #'my/toggle-transparency))
(my/toggle-transparency)Line numbers
(setq display-line-numbers-type nil)(whitespace +guess +trim)(with-eval-after-load 'parinfer-rust-mode
(setq parinfer-rust-check-before-enable nil))(evil +everywhere)(package! evil-textobj-tree-sitter
:recipe (:host github :repo "meain/evil-textobj-tree-sitter"))(after! evil
(setq evil-kill-on-visual-paste nil)
;; Disabling cursor movement when exiting insert mode
(setq evil-move-cursor-back nil)
;; keep previous layout, always!
(setq evil-auto-balance-windows nil))(defvar my/evil-textobj-call-queries
'((python-mode . ((call arguments: (argument_list) @call.inner)
(call) @call.outer))
(python-ts-mode . ((call arguments: (argument_list) @call.inner)
(call) @call.outer))))(use-package! evil-textobj-tree-sitter
:after evil
:config
(define-key evil-inner-text-objects-map "F"
(evil-textobj-tree-sitter-get-textobj "call.inner"
my/evil-textobj-call-queries))
(define-key evil-outer-text-objects-map "F"
(evil-textobj-tree-sitter-get-textobj "call.outer"
my/evil-textobj-call-queries)))(after! evil-surround
;; I want surround (..), not ( .. )
(evil--add-to-alist
evil-surround-pairs-alist
?\( '("(" . ")")
?\[ '("[" . "]")
?\{ '("{" . "}")
?\) '("( " . " )")
?\] '("[ " . " ]")
?\} '("{ " . " }")))Shortcut for evil normal mode
(map! :n "g U" #'browse-url-xdg-open
:n "g u" #'evil-upcase
:n "g d" #'evil-downcase
:n "g R" #'normal-mode)snippets(setq +snippets-dir
(expand-file-name "~/Documents/Templates/snippets"))foldwork for org-ellipsis and fold in code mode
(setq +fold-ellipsis "⤵")
(after! org
(setq org-startup-folded 'fold
org-hide-drawer-startup nil))
;; org-hide-block-startup nil(with-eval-after-load 'undo-fu-session
(defun my/undo-fu-session-drop-text-property-undo (undo-list)
(delq nil
(mapcar (lambda (entry)
;; Text-property undo entries have the form:
;; (nil PROP VALUE BEG . END)
;; Drop them so session files only persist textual edits.
(unless (and (consp entry) (null (car entry)))
entry))
undo-list)))
(defun my/undo-fu-session-save-without-text-properties (fn &rest args)
(let ((buffer-undo-list
(my/undo-fu-session-drop-text-property-undo buffer-undo-list))
(pending-undo-list
(my/undo-fu-session-drop-text-property-undo pending-undo-list)))
(apply fn args)))
(advice-add 'undo-fu-session--save-impl :around
#'my/undo-fu-session-save-without-text-properties))- EmacsWiki: Unfill Paragraph
- GitHub - purcell/unfill: Functions providing the inverse of Emacs’ fill-paragraph
;;; Stefan Monnier <foo at acm.org>. It is the opposite of fill-paragraph
(defun unfill-paragraph (&optional region)
"Takes a multi-line paragraph and makes it into a single line of text."
(interactive (progn (barf-if-buffer-read-only) '(t)))
(let ((fill-column (point-max))
;; This would override `fill-column' if it's an integer.
(emacs-lisp-docstring-fill-column t))
(fill-paragraph nil region)))
;; Handy key definition
(define-key global-map "\M-Q" #'unfill-paragraph)(dired +dirvish +icons)(after! dired
(setq delete-by-moving-to-trash t)
(setq dired-listing-switches
"-l --almost-all --sort=time --human-readable --time-style=long-iso --group-directories-first --no-group")
;; Dirvish respects all the keybindings in `dired-mode-map'
(map! :map dired-mode-map
:n "e" #'dired-create-empty-file
:n "." #'dired-omit-mode))Better trash/restore file with trash-put, trash-restore from trash-cli package.
How restore file from trash?
gio trash in dired
(defun system-move-file-to-trash (filename)
"Trash FILENAME using the 'gio trash' command.
FILENAME should be an absolute path or resolvable from the
current 'default-directory'."
(interactive "fTrash file: ")
(let ((expanded-file (expand-file-name filename)))
(unless (executable-find "gio")
(error "'gio' command not found in your PATH."))
(let ((exit-status (call-process "gio" nil nil nil "trash" expanded-file)))
(unless (zerop exit-status)
(error "Command 'gio trash %s' failed with exit status %d"
(shell-quote-argument expanded-file) exit-status)))))(after! dired-x
;; Make dired-omit-mode hide all "dotfiles"
(setq dired-omit-files
(concat dired-omit-files "\\|^\\..*$")))[dirvish]
packages=[
"fd",
"libvips",
"imagemagick",
"ffmpegthumbnailer", # may require pipewire-jack
"mediainfo",
"tar",
"unzip"
](after! dirvish
(setq dirvish-quick-access-entries
'(("h" "~/" "Home")
("d" "~/Downloads/" "Downloads")
("D" "~/Documents/" "Documents")
("p" "~/Pictures/" "Pictures")
("l" "~/lib/" "Library")
("c" "~/.config/" "Config")
("n" "~/.Nextcloud/" "Nextcloud")
("m" "/mnt/" "Mounts")
("t" "~/.local/share/Trash/files/" "TrashCan"))))(after! dirvish
(setq dirvish-side-width 25)
(setq dirvish-reuse-session 'quit)
(map!
:map dirvish-mode-map
:gn "S" #'dirvish-cd-into-vterm
;; remap previous =S= to =o=
:gn "o" #'dirvish-quicksort))(defun dirvish-open-binaries-externally (file fn)
"When FN is not `dired', open binary FILE externally."
(when-let* (((not (eq fn 'dired)))
((file-exists-p file))
((not (file-directory-p file)))
((member (downcase (or (file-name-extension file) ""))
dirvish-binary-exts)))
;; return t to terminate `dirvish--find-entry'.
(prog1 t (dired-do-open))))
(add-hook 'dirvish-find-entry-hook #'dirvish-open-binaries-externally)
(after! dirvish
;; remove pdf
(setq dirvish-binary-exts (remove "pdf" dirvish-binary-exts)))Replace /home/$user to ~
(defun +my/home-to-tilde (file)
"Replace /home/$user in FILE to ~."
(let ((home (getenv "HOME"))
(file-name (concat file)))
(if (s-starts-with? home file-name)
(s-replace home "~" file-name)
file-name)))
(defun +my/dirvish-copy-file-path (&optional multi-line)
"Copy filepath of marked files.
If MULTI-LINE, make every path occupy a new line."
(interactive "P")
(let* ((files (mapcar #'file-local-name (dired-get-marked-files)))
(related-files (mapcar #'+my/home-to-tilde files))
(names (mapconcat #'identity related-files (if multi-line "\n" " "))))
(dirvish--kill-and-echo (if multi-line (concat "\n" names) names))))
(after! dirvish
(advice-add 'dirvish-copy-file-path :override #'+my/dirvish-copy-file-path))Goto random line, use with fn:dirvish-fd
(defun goto-random-line ()
"Go to a random line in the current buffer."
(interactive)
(let* ((total-lines (count-lines (point-min) (point-max)))
(random-line (1+ (random total-lines))))
(goto-line random-line)))set dirvish as default directory opener.
xdg-mime default dirvish.desktop inode/directorytrampdirvish/docs/CUSTOMIZING.org at main
(with-eval-after-load 'tramp
;; Enable full-featured Dirvish over TRAMP on ssh connections
;; https://www.gnu.org/software/tramp/#Improving-performance-of-asynchronous-remote-processes
(connection-local-set-profile-variables
'remote-direct-async-process
'((tramp-direct-async-process . t)))
(connection-local-set-profiles
'(:application tramp :protocol "rpc")
'remote-direct-async-process))- akermu/emacs-libvterm#689 Tramp-login-shells custom doesn’t match type
- akermu/emacs-libvterm#706 Allow using the default login shell for remote connections
GitHub - ArthurHeymans/emacs-tramp-rpc
ArthurHeymans/emacs-tramp-rpc#28
(package! tramp-rpc
:recipe (:host github
:repo "ArthurHeymans/emacs-tramp-rpc"
:files ("lisp/*.el")
:build (:not autoloads)))
(package! msgpack)(use-package tramp-rpc
:after tramp
:config
;; Local cache directory (default: ~/.emacs.d/tramp-rpc/)
(setq tramp-rpc-deploy-local-cache-directory "~/.cache/tramp-rpc-binaries")
;; Remote installation directory (default: ~/.cache/tramp-rpc)
(setq tramp-rpc-deploy-remote-directory "~/.local/bin/tramp-rpc"))File preview is not supported in this connection.
- Please check if you have GNU ls installed over remote host.
- Adjust your `direct-async’ tramp settings, for example:
See (info “(tramp) Improving performance of asynchronous remote processes”) for details.
(setq url-proxy-services
`(("no_proxy" . "^\\(localhost\\|10\\..*\\|192\\.168\\..*\\)")
("http" . ,(getenv "ALL_PROXY"))
("https" . ,(getenv "ALL_PROXY"))))GitHub - dakra/ghostel is an Emacs terminal emulator powered by libghostty-vt
(Ghostty’s VT engine), inspired by emacs-libvterm.
The native dynamic module is downloaded automatically on first use: a prebuilt
binary exists for x86_64-linux, governed by ghostel-module-auto-install (default
ask). Trigger it manually with M-x ghostel-download-module if needed.
ghostel[ghostel]
packages=[
"inetutils" # =hostname= command
](setq ghostel-module-auto-install 'download
ghostel-module-directory
(expand-file-name "ghostel/" doom-data-dir))
(after! ghostel
(setq ghostel-term "xterm-256color")
;; recenter
(define-key ghostel-semi-char-mode-map (kbd "C-l") nil)
;; switch workspace
(dolist (num (number-sequence 0 9))
(define-key ghostel-semi-char-mode-map (kbd (format "M-%d" num)) nil))
;; Alt+q acts as Escape
(define-key ghostel-semi-char-mode-map (kbd "M-q")
(lambda () (interactive) (ghostel-send-key "escape"))))Using xterm-256color avoids terminfo lookup failures across sudo, SSH sessions, and containers that do not provide xterm-ghostty. This compatibility fallback disables Ghostel-specific capability advertisement, so advanced features such as synchronized output and the Kitty keyboard protocol may be unavailable.
[spell]
packages=["aspell", "aspell-en"](spell +aspell
+everywhere)(after! ispell
(setq ispell-personal-dictionary
(expand-file-name ".pws" "~/.Nextcloud/ispell/")))(dolist (face '(org-level-1 org-level-2 org-level-3
font-lock-function-name-face help-key-binding))
(cl-pushnew face (alist-get 'org-mode +spell-excluded-faces-alist)))
(dolist (face '(font-lock-constant-face tex-math font-lock-comment-face))
(cl-pushnew face (alist-get 'latex-mode +spell-excluded-faces-alist)))Only hint spell error for English words!
(defconst +my/spell-fu-english-word-regexp
"\\b\\([A-Za-z]+\\(['’][A-Za-z]+\\)?\\)\\b")
(add-hook 'spell-fu-mode-hook
(lambda ()
(setq-local spell-fu-word-regexp
+my/spell-fu-english-word-regexp)))[lookup]
packages=["sqlite", "wordnet-common"](lookup
+docsets
+dictionary)(add-to-list '+lookup-provider-url-alist '("Brave" "https://search.brave.com/search?q=%s"))
(add-to-list '+lookup-provider-url-alist '("Inspire" "https://inspirehep.net/literature?q=%s"))llm(with-eval-after-load 'gptel
(setq gptel-include-reasoning 'ignore
gptel-expert-commands t
gptel-track-media t
gptel-log-level 'info)
;; avoid headings as prompt
(setf (alist-get 'org-mode gptel-prompt-prefix-alist) "** "
(alist-get 'org-mode gptel-response-prefix-alist) "*Response:*\n")
(add-hook! 'gptel-pre-response-hook
(outline-previous-heading)
(recenter-top-bottom 1))
;; (add-hook 'gptel-post-stream-hook 'gptel-auto-scroll)
;; (add-hook 'gptel-post-response-functions 'gptel-end-of-response))
(load-in-doom-dir "private/gpt.el"))After cmd:gptel-org-set-topic, remove the top heading for collect conversation.
(defun +my/gptel-org-rm-upper-heading ()
"Remove upper level heading line."
(while (re-search-backward "\\(?:^\\* .+$\\)[[:space:]]+" nil t)
(delete-region (match-beginning 0) (match-end 0))))
(add-hook 'gptel-prompt-filter-hook #'+my/gptel-org-rm-upper-heading)(defun gptel-shift-response-heading-level (start end)
"Shift all Org headings in region so smallest is level 3 (***).
If the last line at END starts with '** ', exclude it from shifting."
(interactive "r")
(save-excursion
;; Adjust end if needed
(goto-char end)
(when (and (beginning-of-line) (looking-at "^\\*\\* "))
(setq end (max start (line-end-position -1))))
;; Find min level and shift
(let ((min-level (save-excursion
(goto-char start)
(cl-loop while (re-search-forward "^\\(\\*+\\) " end t)
minimize (length (match-string 1))))))
(when (and min-level (< min-level 3))
(let ((shift (- 3 min-level)))
(goto-char start)
(while (re-search-forward "^\\(\\*+\\) " end t)
(replace-match (concat (make-string (+ (length (match-string 1)) shift) ?*) " ") t t)))))))
(add-hook 'gptel-post-response-functions #'gptel-shift-response-heading-level)Tools collection · karthink/gptel Wik…
(package! gptel-got
:recipe (:host nil
:repo "https://git.bajsicki.com/phil/gptel-got"))(use-package gptel-got :after gptel)codel have merged into this llm-tool-collection fork. see ultronozm/codel.el#2 Errors with gpte…
(package! llm-tool-collection
:recipe (:type git :host github :repo "ultronozm/llm-tool-collection"))(use-package llm-tool-collection
:after gptel
:config
(mapcar (apply-partially #'apply #'gptel-make-tool)
(llm-tool-collection-get-all)))And more in GitHub - munen/emacs.d: My emacs conf…
(package! gptel-agent)(use-package gptel-agent :defer t)(after! gptel-quick
(setq gptel-quick-model 'qwen3:14b)
(setq gptel-quick-backend (cdr (assoc-string "ollama" gptel--known-backends))))[agent]
packages=[
"claude-agent-acp",
"codex-acp"
](package! shell-maker)
(package! acp)
(package! agent-shell)
(package! agent-shell-notifications
:recipe (:host github :repo "zackattackz/agent-shell-notifications"))
(package! agent-shell-org-transcript
:recipe (:host github :repo "lllShamanlll/agent-shell-org-transcript"))(use-package! agent-shell
:commands (agent-shell)
:bind (:map agent-shell-mode-map
("S-<return>" . newline))
:init
(map! :leader
(:prefix "o"
:desc "agent-shell" "a" #'agent-shell))
:config
(setq agent-shell-context-sources '(files region error)
agent-shell-show-welcome-message nil
agent-shell-header-style 'text
agent-shell-session-restore-verbosity 'full
agent-shell-display-action
'((display-buffer-in-side-window)
(side . right)
(window-width . 0.4)))
(add-hook 'agent-shell-mode-hook
(lambda ()
(remove-hook 'completion-at-point-functions
#'comint-completion-at-point t)))
;; Configure *agent-shell-diff* buffers to start in Emacs state
(add-hook 'diff-mode-hook
(lambda ()
(when (string-match-p "\\*agent-shell-diff\\*" (buffer-name))
(evil-emacs-state))))
(add-to-list 'agent-shell-mcp-servers
'((name . "emcp")
(type . "http")
(headers . ())
(url . (lambda ()
(require 'emcp)
(let ((server (emcp-start emcp-default-profile)))
(emcp-server-url server))))))
)(use-package agent-shell-notifications
:after agent-shell
:hook (agent-shell-mode . agent-shell-notifications-mode))(use-package agent-shell-org-transcript
:after agent-shell
:config
(setq agent-shell-org-transcript-directory "~/Documents/org/agent-shell/"))web-server is needed by the MCP HTTP server; declare it explicitly so straight
pulls it on a fresh sync.
(package! claude-code-ide
:recipe (:host github :repo "manzaltu/claude-code-ide.el"))(use-package! claude-code-ide
:init
;; Use ghostel (libghostty-vt) as the terminal backend instead of the
;; default vterm. Valid values: 'vterm, 'eat, 'ghostel.
(setq claude-code-ide-terminal-backend 'ghostel)
(map! :leader
(:prefix "o"
:desc "claude-code-ide" "c" #'claude-code-ide-menu))
:config
(claude-code-ide-emacs-tools-setup)) ; Optionally enable Emacs MCP toolsThe vterm backend renames its buffer from shell OSC titles via
vterm-buffer-name-string. Agent CLIs push a volatile activity title (e.g. a
spinner prefix), so the buffer name flips between save and restore and
persp-mode drops the window on workspace switch (see Persistence). Pin a
stable name on agent terminals by disabling title-driven renaming there. This
is buffer-local and leaves ordinary vterm buffers untouched.
(defun +my/vterm-disable-title-rename-for-agents-h ()
"Disable OSC-title buffer renaming in claude/codex vterm buffers.
Keeps the buffer name stable so `persp-mode' restores its window."
(when (string-match-p "\\*\\(claude-code\\|codex\\)" (buffer-name))
(setq-local vterm-buffer-name-string nil)))
(add-hook 'vterm-mode-hook #'+my/vterm-disable-title-rename-for-agents-h)(package! emcp)(use-package! emcp
:after agent-shell
:config
(add-hook 'emcp-confirm-mode-hook #'evil-emacs-state))magit(defun yadm-status ()
(interactive)
(magit-status "/yadm::"))
(map! :leader
(:prefix "g"
:desc "yadm-status" "a" #'yadm-status))
;; see `man yadm'
(with-eval-after-load 'tramp
(add-to-list 'tramp-methods
'("yadm"
(tramp-remote-shell "/bin/bash")
(tramp-remote-shell-args ("-c"))
(tramp-login-program "yadm")
(tramp-login-args (("enter"))))))Restore focus to origin window after viewing a commit and pressing q.
(defvar +my/magit-origin-window nil)
(with-eval-after-load 'magit
(advice-add 'magit-show-commit :before
(lambda (&rest _)
(setq +my/magit-origin-window (selected-window))))
(advice-add 'magit-mode-quit-window :after
(lambda (&rest _)
(when (and +my/magit-origin-window
(window-live-p +my/magit-origin-window))
(select-window +my/magit-origin-window)
(setq +my/magit-origin-window nil)))))If you use fish shell, you may change fish_prompt. see ~/.config/fish/config.fish
cmd:magit-stage (visually stage hunks) may not work in yadm, which cause emacs
to hang, use kbd:E s (cmd:magit-ediff-stage) instead.
magit/magit#719 Magit process hangs when trying to stage a hunk
- ~/.config/emacs/modules/tools/magit/config.el
- magit/transient#338 Transient window doesn’t come back if display-action is set
- karthink/gptel#583 Transient issue when selecting a model in `gptel-menu`
(after! (transient magit gptel)
(setq transient-display-buffer-action
'(display-buffer-below-selected
(dedicated . t)
(inhibit-same-window . t)))
(setq transient-show-during-minibuffer-read t))see also karthink/gptel#583 Transient issue when selecting a model in `gptel-menu`
transient-posframe/transient-posframe
(with-eval-after-load 'transient
(defun my/transient-posframe-display-buffer (buffer alist)
"Display transient BUFFER in a child frame."
(when (and (fboundp 'posframe-show)
(fboundp 'posframe-workable-p)
(posframe-workable-p))
(let ((pf (posframe-show
buffer
:poshandler #'posframe-poshandler-frame-bottom-center
:accept-focus nil
:font "ComicShannsMono Nerd Font 8"
:min-width 40
:border-width 2
:border-color "gray50")))
(redisplay t)
(and (frame-live-p pf) (frame-root-window pf)))))
;; `transient--fit-window-to-buffer' calls `fit-frame-to-buffer' when the
;; window has no parent (i.e. posframe root window). The posframe child frame
;; shares the parent frame's minibuffer window; `fit-frame-to-buffer' includes
;; that shared window in its layout calculation and resizes it, causing the
;; parent frame's minibuffer to grow unexpectedly. Skip `fit-frame-to-buffer'
;; for child frames and fit the window directly instead.
(defadvice! +my/transient--fit-window-skip-child-frame-a (orig-fn window)
:around #'transient--fit-window-to-buffer
(if (frame-parameter (window-frame window) 'parent-frame)
(let ((fit-window-to-buffer-horizontally t)
(window-resize-pixelwise t)
(window-size-fixed nil))
(set-window-parameter window 'window-preserved-size nil)
(fit-window-to-buffer window nil 1)
(set-window-parameter window 'window-preserved-size
(list (window-buffer window)
(window-body-width window t)
(window-body-height window t))))
(funcall orig-fn window))))- [ ] still buggy, only work after manually execute this block.
- Notes:
- daemon mode still has a first-popup issue: the first transient posframe in a new client frame may fail to appear, while later ones usually work
- this block intentionally keeps the current workaround behavior because it is stable enough after that first failure
(after! (:or magit dirvish gptel)
(setq transient-display-buffer-action
'(my/transient-posframe-display-buffer
(dedicated . t)
(inhibit-same-window . t))))- [ ] try to fixed transient menu in
dirvish
(defun transient-posframe--delete ()
"Delete transient posframe."
(posframe-delete-frame transient--buffer-name)
(posframe--kill-buffer transient--buffer-name))
(advice-add 'transient--delete-window :override #'transient-posframe--delete)pdfdefault pdf viewer in emacs
(after! pdf-tools
(setq-default pdf-view-display-size 'fit-width))
(after! latex (setq +latex-viewers '(pdf-tools evince okular)))
;; to have the buffer refresh after compilation
(add-hook 'TeX-after-compilation-finished-functions
#'TeX-revert-document-buffer)
;; always use midnight view mode
(add-hook! 'pdf-view-mode-hook #'pdf-view-midnight-minor-mode)Selection in pdf-tools when evil mode enabled
Correct the file name path if it is a WSL path in Windows or an absolute path
inadvertently synced with a network disk. For pdf-sync-view, the source file
was correctly identified only after making this adjustment.
(defun +my/synced-true-path (filename)
"Rewrite the FILENAME assuming it from synced netdisk (or WSL).
When from netdisk, ensure they have same directory structure with
respect to your home."
(if (s-starts-with-p "//wsl.localhost" filename)
(setq filename (replace-regexp-in-string "^//wsl.localhost/\\w+" "" filename)))
(unless (s-starts-with-p (getenv "HOME") filename)
(setq filename (replace-regexp-in-string "^/home/\\w+" (getenv "HOME") filename)))
filename)
(defun +my/pdf-sync-backward-search (x y &optional page)
"Go to the source corresponding to image coordinates X, Y.
Try to find the exact position, if
`pdf-sync-backward-use-heuristic' is non-nil."
(cl-destructuring-bind (source finder)
(pdf-sync-backward-correlate x y page)
(setq source (+my/synced-true-path source))
(pop-to-buffer (or (find-buffer-visiting source)
(find-file-noselect source))
pdf-sync-backward-display-action)
(push-mark)
(funcall finder)
(run-hooks 'pdf-sync-backward-hook)))
(advice-add 'pdf-sync-backward-search :override #'+my/pdf-sync-backward-search)Auto view .ps file
(add-hook 'ps-mode-hook 'doc-view-toggle-display)(package! gt)(use-package gt
:bind ("C-c t" . gt-translate)
:config
(setq gt-langs '(en zh))
(setq gt-default-translator
(gt-translator
:engines (list (gt-youdao-dict-engine) (gt-youdao-suggest-engine))
;; :engines (gt-google-engine)
:render (gt-buffer-render)))
(set-popup-rule!
"^\\*gt-result\\*"
:side 'left :size 60 :slot -2 :select t))(package! pacdiff)(use-package! pacdiff)(package! tldr)[latex]
enabled="not is_wsl"
packages=[
"miktex", "texlab",
# for `latexindent.pl` to work, which is called by `+format/buffer`
"perl-yaml-tiny", "perl-file-homedir"
](latex +lsp +fold +cdlatex)Invoke latex.exe on windows.
(if (featurep :system 'wsl)
(setq LaTeX-command "latex.exe"
TeX-command "latex.exe"))(after! evil-tex
(setq evil-tex-include-newlines-in-envs nil
evil-tex-select-newlines-with-envs nil))(with-eval-after-load 'latex
(setq LaTeX-clean-intermediate-suffixes
(delete "\\.synctex\\.gz" (copy-sequence LaTeX-clean-intermediate-suffixes)))
(dolist (suffix '("\\.loc" "\\.soc" "Notes\\.bib"))
(add-to-list 'LaTeX-clean-intermediate-suffixes suffix))
(dolist (suffix '("\\.bbl" "\\.synctex\\.gz"))
(add-to-list 'LaTeX-clean-output-suffixes suffix)))fn:latex-indent cmd:LaTeX-fill-buffer
cmd:+format/buffer default installed by miktex
(after! apheleia
(set-formatter! 'latexindent '("latexindent" "-l" "-r" "--logfile=/dev/null")
:modes '(LaTeX-mode)))add XeTeX mode in TeX/LaTeX
Most convenient way, still use LATEX as compile command.
% -*- TeX-engine: xetex; -*-cdlatex
(map! :map cdlatex-mode-map
:i "TAB" #'cdlatex-tab)formatting - LaTeXTidy in Emacs - TeX - LaTeX Stack Exchange
(after! cdlatex
(setq cdlatex-insert-auto-labels-in-env-templates nil))(with-eval-after-load 'cdlatex
;; add/override math modifier after "=":
;; =b -> \mathbm{...}
;; =B -> \mathbbm{...}
;;
;; Note: you still need the corresponding LaTeX packages:
;; \usepackage{bm} for \mathbm (usually via \bm / \boldsymbol)
;; \usepackage{bbm} for \mathbbm
(dolist (it '((?b "\\mathbm" nil t nil nil)
(?B "\\mathbbm" nil t nil nil)))
(setf (alist-get (car it) cdlatex-math-modify-alist)
(cdr it))))[lua]
enabled="not is_wsl"
packages=["lua-language-server",
"stylua" # deterministic Lua formatter
](lua +lsp)lsp support
(after! lua-mode
(setq lsp-clients-lua-language-server-bin "/usr/bin/lua-language-server")
(setq lsp-clients-lua-language-server-main-location "/usr/lib/lua-language-server/bin/main.lua")
(setq lsp-clients-lua-language-server-args '("-E" "--logpath" "/tmp/lua-language-server"))
;; (lsp-clients-lua-language-server-command '("lua-language-server" "-E"))
(setq lsp-clients-lua-language-server-command nil))ligatures
(after! lua-mode
(set-ligatures! 'lua-mode
:def "function"
:return "return"
:and "and"
:or "or"
:not "not"
:true "true"
:false "false"
:for "for"))[org]
packages=[
"xclip",
"maim",
"graphviz"
](org
+hugo
+dragndrop
+jupyter
+noter
+present
+pandoc
+pretty
+roam)Fix function link recognized as footnote.
(use-package org-modern
:init
(setq org-modern-footnote nil)
:config
(setq org-modern-star 'replace))Not the configuration of org-modern package, but they should be in same
category.
(defun +my/org-stronger-emphasis ()
"Make *bold* and /italic/ stand out more in org buffers."
(face-remap-add-relative 'bold
'(:foreground "orange red" :weight extra-bold))
(face-remap-add-relative 'italic
'(:foreground "deep sky blue" :slant italic)))
(add-hook 'org-mode-hook #'+my/org-stronger-emphasis)Very useful package for “automatic visibility toggling depending on cursor position”! Move on following latex to test.
α1 + β2
(after! org-appear
(setq org-appear-autolinks t
org-appear-autosubmarkers t
org-appear-inside-latex t))(setq org-directory "~/Documents/org/"
org-agenda-files '("agenda/todos.org" "agenda/projects.org")
org-agenda-start-with-log-mode t
org-agenda-prefix-format '((agenda . " %i %-12:c%?-12t% s")
(todo . " ")
(tags . " %i %-12:c")
(search . " %i %-12:c"))
org-log-done 'time
org-log-into-drawer t
org-startup-numerated t
org-image-actual-width 400
org-duration-format '((special . h:mm))
org-startup-with-inline-images t
org-refile-targets '(("archive.org" :maxlevel . 1)
("projects.org")))(map! :leader
:desc "Org agenda" "=" #'org-agenda)saving - How do I automatically save org-mode buffers? - Emacs Stack Exchange
(after! org
;;(org-clock-persist 'history)
(org-clock-persistence-insinuate)
(advice-add 'org-refile :after 'org-save-all-org-buffers)
(advice-add 'org-agenda-quit :before 'org-save-all-org-buffers))custom agenda view from
(setq org-agenda-custom-commands
'(("g" "Get Things Done (GTD)"
((agenda ""
((org-agenda-skip-function
'(org-agenda-skip-entry-if 'deadline))
(org-deadline-warning-days 0)
(org-agenda-start-day "-1d")
(org-agenda-span 4)))
(todo "STRT"
((org-agenda-skip-function
'(org-agenda-skip-entry-if 'deadline))
(org-agenda-prefix-format " %i %-12:c [%e] ")
(org-agenda-overriding-header "\nTasks\n")))
(tags-todo "inbox"
((org-agenda-prefix-format " %?-12t% s")
(org-agenda-overriding-header "\nInbox\n")))
(tags "CLOSED>=\"<today>\""
((org-agenda-overriding-header "\nCompleted today\n")))))))(after! org-capture
(setq org-capture-templates
`(("i" "Inbox" entry (file "agenda/todos.org")
"* TODO %?\n%U\n%i" :empty-lines 1 :prepend t)
("@" "Inbox [mu4e]" entry (file "agenda/todos.org")
"* TODO Reply to \"%a\"\n%U\n%i" :empty-lines 1 :prepend t)
("n" "Inbox [note]" entry (file "agenda/todos.org")
"* TODO [%a] %? %^G\n%U\n%i" :empty-lines 1 :prepend t))))- emacs - org-mode capture : dynamic file name - Stack Overflow
- emacs-from-scratch/init.el at c55d0f5e309f7ed8ffa3c00bc35c75937a5184e4 · davi…
Skip executing org source blocks within commented headings. To optimize, consider advising fn:org-babel-map-executables
(defun +my/org-babel-execute-buffer (&optional arg)
"Execute source code blocks in a buffer.
Call `org-babel-execute-src-block' on every source block in
the current buffer."
(interactive "P")
(org-babel-eval-wipe-error-buffer)
(org-save-outline-visibility t
(org-babel-map-executables nil
(unless (org-in-commented-heading-p)
(if (memq (org-element-type (org-element-context))
'(babel-call inline-babel-call))
(org-babel-lob-execute-maybe)
(org-babel-execute-src-block arg))))))
(advice-add 'org-babel-execute-buffer :override #'+my/org-babel-execute-buffer)Restore window-start after execute subtree. For hook based implement for all fn:narrow-to-region see:
(defun +my/org-babel-execute-subtree (&optional arg)
"Execute source code blocks in a subtree.
Call `org-babel-execute-src-block' on every source block in
the current subtree, passing over the prefix argument ARG."
(interactive "P")
(let ((original-start (window-start)))
(save-restriction
(save-excursion
(org-narrow-to-subtree)
(org-babel-execute-buffer arg)))
(set-window-start (selected-window) original-start)))
(advice-add 'org-babel-execute-subtree :override #'+my/org-babel-execute-subtree)[jupyter]
packages=["jupyter-notebook"]start session only when exactly execute it.
(after! jupyter
(setq jupyter-org-auto-connect nil))doomemacs/doomemacs#7354 Jupyter fails to function after upgrade
(with-eval-after-load 'ob-jupyter
(org-babel-jupyter-aliases-from-kernelspecs))(after! ob-jupyter
;; (push :text/html jupyter-org-mime-types)
(set-popup-rule!
"^\\*jupyter-traceback"
:side 'bottom :size 10 :slot -2 :select t))emacs-jupyter/jupyter#607 ‘wrong-type…
(defun my/jupyter-org-results-drawer-pre-blank-fix (element)
"Advice to ensure the RESULTS drawer has a :pre-blank 0 property.
This prevents 'wrong-type-argument wholenump nil' errors in newer Org versions."
(if (and element (eq (org-element-type element) 'drawer))
(progn
(org-element-put-property element :pre-blank 0)
(org-element-put-property element :post-blank 1)
element)
element))
(advice-add 'jupyter-org-results-drawer
:filter-return
#'my/jupyter-org-results-drawer-pre-blank-fix)To view contents of .ipynb file, see
Possible issues
- emacs-jupyter/jupyter#584 `jupyter-or…
- doomemacs/doomemacs#3171 if: Need a v…
- emacs-jupyter/jupyter#575 Remote kern…
- [ ] emacs-jupyter/jupyter#558 Jupyter and…
- [ ]
(wrong-type-argument stringp nil)
(package! jupyter-ext
:recipe (:host github
:repo "fakeGenuis/jupyter-ext"))(use-package! jupyter-ext
:commands jupyter-org-transient
:init
(map! :map org-mode-map
:n ";" #'jupyter-org-transient)
:config
;; A tweaked completion at point function for corfu
(advice-add 'jupyter-completion-at-point :override #'jupyter-ext-completion-at-point))(after! ob-core
;; with multiple output and =:async yes=, text mass up after `example` block
(setq org-babel-min-lines-for-block-output 256))Intent mainly with file:::wolfram in jupyter, respect to doom’s org babel lazy load
(defvar +my/jupyter-langs '()
"A list of language that use jupyter override.")
(add-hook '+org-babel-load-functions
(defun +org-babel-load-jupyter-override-h (lang)
;; don't multi run `org-babel-jupyter-override-src-block'
(unless (boundp 'org-babel-header-args:jupyter)
(require 'ob-jupyter))
;; or even org-babel-header-args:%s will be reset
(when-let ((lang-name (symbol-name lang))
(_ (member lang-name +my/jupyter-langs)))
(set (intern (format "org-babel-default-header-args:jupyter-%s" lang-name))
(symbol-value (intern (format "org-babel-default-header-args:%s" lang-name))))
(org-babel-jupyter-override-src-block lang-name)))
-90)Session async have been include in org mode, see how to implement async using built in method.
- GitHub - jackkamm/ob-session-async: Asynchronous org-mode session evaluation
- emacs/etc/ORG-NEWS at a7cb220523d881449a2dba683e7358b3312fd482 · emacs-mirror…
This branch mainly fix apply: Wrong number of arguments of advice
cmd:ob-async-org-babel-execute-src-block
(package! ob-async
:pin nil
:recipe (:host github
:repo "stsquad/ob-async"
:branch "update-signature-skip-session"))(after! org-noter
(org-noter-set-doc-split-fraction '(0.75 . 0.25)))(setq org-roam-directory (expand-file-name "roam/" org-directory))
(after! org-roam
(setq org-roam-dailies-capture-templates
'(("d" "default" entry "* %?\n[%<%Y-%m-%d %H:%M>]\n"
:if-new (file+head "%<%Y-%m-%d>.org" "#+title: %<%Y-%m-%d>\n")))))(after! org-roam
(setq org-roam-graph-viewer "librewolf")
(setq org-roam-graph-link-hidden-types
'("file" "http" "https" "attachment" "zotero"
"fuzzy" "doom-module" "kbd" "fn")))(package! zotxt)(defun org-zotxt-get-attachment-path ()
"Get attachment file path"
(interactive "P")
(let ((item-id (org-zotxt-extract-link-id-at-point)))
(deferred:$
(zotxt--request-deferred
(format "%s/items" zotxt-url-base)
:params `(("key" . ,item-id) ("format" . "paths"))
:parser 'json-read)
(deferred:nextc it
(lambda (response)
(let ((paths (cdr (assq 'paths (elt (request-response-data response) 0)))))
(org-zotxt-choose-path paths))))
(deferred:error it #'zotxt--deferred-handle-error)
(if zotxt--debug-sync (deferred:sync! it)
(deferred:nextc it
(lambda (path) path))))))
(defun +my/tilde-home-path (path)
"covert path starts with /home/$usr/ to '~'"
(interactive)
(let ((home (getenv "HOME")))
(if (string-prefix-p home path)
(string-join `("~" ,(string-remove-prefix home path)) "")
path)))
(defun org-zotxt-copy-attachment-path ()
"Open attachment of Zotero items linked at point.
Opens with `org-open-file', see for more information about ARG."
(interactive)
(deferred:$
(deferred:next
(lambda ()
(org-zotxt-get-attachment-path)))
(deferred:nextc it
(lambda (path)
(let ((new-path (+my/tilde-home-path path)))
(kill-new new-path)
(message "\"%s\" send to system clipboard!" new-path))))))A research workflow with Zotero and Org mode | mkbehr.com
(use-package zotxt
:hook (org-mode . org-zotxt-mode)
:config
(setq zotxt-default-bibliography-style "mkbehr-short"))
(map! :map org-zotxt-mode-map
:desc "org-zotxt-insert-selected"
;; use <quote> in in case it pollute balanced brackets
"C-c <quote> <quote>" (cmd!! #'org-zotxt-insert-reference-link '(4))
:desc "org-zotxt-copy-attachment-path"
"C-c <quote> c" #'org-zotxt-copy-attachment-path)open attachment with point at arxiv link
(defun org-zotxt-open-arxiv-attachment ()
"open attachment from arxiv link, by zotxt"
(interactive)
(let* ((link (org-element-context))
(desc (buffer-substring-no-properties (org-element-property :contents-begin link)
(org-element-property :contents-end link))))
(org-zotxt-insert-reference-link)))(defvar rx-arxiv-regexp
(rx (= 4 num) "." (= 5 num))
"Regular expression for arxiv id.")
(defun org-insert-arxiv-link ()
"Insert arxiv link with arxiv id as description."
(interactive)
(let* ((ring (current-kill 0))
(id (if (string-match rx-arxiv-regexp ring)
(match-string 0 ring)
(read-string "Input arxiv id:"))))
(insert " ")
(org-insert-link nil (concat "https://arxiv.org/abs/" id) id)))format org-src
(map! :after org :map evil-org-mode-map
:n "g Q" #'+format:region)org-format
(use-package org-format
:defer 10
;; dawn lazy load
;; :commands (apheleia-format-org-buffer)
:init
(add-to-list 'load-path (expand-file-name "org-format" org-directory)))Local org extensions
(load! "lisp/org-babel-inline-results")(cl-defun apheleia-format-org-buffer
(&key buffer scratch callback &allow-other-keys)
"Copy BUFFER to SCRATCH, then format scratch, then call CALLBACK."
;; ugly implement!
(with-current-buffer scratch
;; FIXME so many local value to copy
(let ((buffer-file-name (buffer-local-value 'buffer-file-name buffer))
(org-format-ignore-link (buffer-local-value 'org-format-ignore-link buffer))
(org-format-keep-empty-below-heading (buffer-local-value 'org-format-keep-empty-below-heading buffer)))
(org-format-buffer))
(funcall callback)))
(after! org
(set-formatter! 'orgfmt #'apheleia-format-org-buffer :modes '(org-mode)))Do not track remote files in org-persist; it may reconnect to TRAMP hosts on exit.
(after! org-persist
(setq org-persist-remote-files nil))kbd:SPC s b should work as expected
revert native org-cycle style, see doom-modules:lang/org/README.org
(after! evil-org
(remove-hook 'org-tab-first-hook #'+org-cycle-only-current-subtree-h))hlissner/doom-emacs#5436 org-src-window-setup not working correctly
(after! org-src
(setq org-src-window-setup 'reorganize-frame)
(set-popup-rule! "^\\*Org Src" :ignore t))(after! org
(setq org-archive-location ".bak/%s_archive::"))Avoid lengthy title
(after! org-cliplink
(setq org-cliplink-max-length 40))(package! org-modern-indent
:recipe (:type git :host github :repo "jdtsmith/org-modern-indent"))(use-package org-modern-indent
:config ; add late to hook
(add-hook 'org-mode-hook #'org-modern-indent-mode 90))Smaller org src-block font size, more contents displayed.
(add-hook! org-mode
(set-face-attribute 'org-code nil :height 0.8)
;; max height of org-indent and org-code will be applied
(set-face-attribute 'org-indent nil :height 0.8))[python]
packages=[
"ty", "python-black", "python-isort",
"python-nose", "python-pytest",
"tree-sitter-python"
](python +lsp +pyright +tree-sitter)doomemacs/doomemacs#3171 if: Need a v…
(with-eval-after-load 'ob-core
(setq org-babel-default-header-args:jupyter-python
'((:kernel . "python3"))))Emacs 30.2 on Arch once had a distro packaging issue that broke tree-sitter
font-lock queries in *-ts-mode. See the Emacs China discussion.
[sh]
packages=["shellcheck-bin", "bash-language-server", "shfmt"](sh +fish +lsp +powershell)fish shell ligatures
(after! fish-mode
(set-ligatures! 'fish-mode
:def "function"
:return "return"
:and "&&"
:or "||"
:not "not"
:true "true"
:false "false"
:for "for"))Here is for var:shell-file-name, will remove doom doctor’s complain.
(setq shell-file-name (executable-find "bash"))
(setq-default vterm-shell "/bin/fish")
(setq-default explicit-shell-file-name "/bin/fish")[yaml]
packages=["yaml-language-server"](yaml +lsp)[wolfram]
enabled="not is_wsl"
packages=["mathematica"](package! wolfram-mode
:recipe (:local-repo "~/lib/wolfram-mode/"))(use-package! wolfram-mode
:defer t
:config
(with-eval-after-load 'dirvish
(add-to-list 'dirvish-binary-exts "nb")))wolfram-format
(after! apheleia
(load "~/lib/wolframFormatter/wolfram-format.el"))Note that apheleia-formatter not work well with org-src block, turn
org-indent-mode off and then run kbd:g Q and then turn on org-indent-mode.
(after! (wolfram-mode ligature)
(set-ligatures! 'wolfram-mode
:and "&&"
:or "||"
:not "!"
:null "None"
:true "True"
:false "False"))WLPATH="~/.local/lib/lsp-wl/"
[[ -d "$WLPATH" ]] || git clone https://github.com/kenkangxgwe/lsp-wl.git "$WLPATH"PacletInstall["CodeParser"]
PacletInstall["CodeInspector"]
PacletInstall["ZeroMQLink"] (* 1.2.6+ *)eglot is far faster than LSP!
(let ((wlserver (expand-file-name "~/.local/lib/lsp-wl/init.wls")))
(when (and (file-exists-p wlserver) (executable-find "wolframscript"))
(with-eval-after-load 'eglot
(add-to-list 'eglot-server-programs
`(wolfram-mode . ("wolframscript" "-f" ,wlserver
"--tcp-server" :autoport))))))Completion seems not work with 12.3.
(add-to-list '+my/jupyter-langs "Wolfram-Language")
(after! org-src
(add-to-list 'org-src-lang-modes '("Wolfram-Language" . wolfram))
(setq org-babel-default-header-args:Wolfram-Language
'((:kernel . "wolframforjupyter")
(:async . "yes")
(:results . "scalar"))))(add-to-list '+org-babel-native-async-langs 'Wolfram-Language)<> in Wolfram org-src break parentheses match.
(defun +my/remove-angle-brackets-from-syntax ()
(modify-syntax-entry ?< "." (syntax-table)) ;; Treat < as punctuation
(modify-syntax-entry ?> "." (syntax-table))) ;; Treat > as punctuation
(add-hook 'org-mode-hook #'+my/remove-angle-brackets-from-syntax)(after! (wolfram-mode yasnippet)
(let ((key-templates '()))
(dolist (key wolfram-structure-keywords)
(push `(,key ,(format "(* ::%s:: *)\n(*$1*)\n\n" key)) key-templates))
(yas-define-snippets 'wolfram-mode key-templates)))["typst-mode"]
packages=[
"typst",
"tree-sitter-typst-git", # tree sitter grammar for typst, demand by typst-ts-mode
"tinymist" # language server
# "typst-lsp-bin" # alternative language server
](package! typst-ts-mode
:recipe (:host codeberg
:repo "meow_king/typst-ts-mode"
:files (:defaults "*.el")))(use-package! typst-ts-mode
:mode ("\\.typ\\'")
:custom
;; (typst-ts-watch-options "--open")
(typst-ts-mode-grammar-location (expand-file-name "tree-sitter/libtree-sitter-typst.so" user-emacs-directory))
(typst-ts-mode-enable-raw-blocks-highlight t)
:config
(setq typst-ts-mode-indent-offset 2)
(keymap-set typst-ts-mode-map "C-c C-c" #'typst-ts-tmenu))use pdf-tools to preview
(after! typst-ts-mode
(setq typst-ts-compile-hide-compilation-buffer-if-success nil)
(setq typst-ts-preview-function
(lambda (pdf)
(let ((buffer (find-file-noselect pdf)))
(display-buffer buffer)
(with-current-buffer buffer
(when (derived-mode-p 'pdf-view-mode)
(pdf-view-revert-buffer nil t)))))))language server
(with-eval-after-load 'eglot
(with-eval-after-load 'typst-ts-mode
(add-to-list 'eglot-server-programs
`((typst-ts-mode) .
,(eglot-alternatives `(,typst-ts-lsp-download-path
"tinymist"
"typst-lsp"))))))more configuration on Tinymist Docs
(defun +my/tex2typ-region-to-kill-ring (beg end)
"Convert selected LaTeX math to Typst and copy result to kill-ring."
(interactive "r")
(let* ((text (buffer-substring-no-properties beg end))
(cmd (format "tex2typ %s" (shell-quote-argument text)))
(output (string-trim (shell-command-to-string cmd))))
(kill-new output)
(message "Copied Typst: %s" output)))
(map! :v "C-c t" #'+my/tex2typ-region-to-kill-ring)(package! kdl-mode)(use-package kdl-mode)[mu4e]
packages=[
"isync",
"mu",
"pass",
"msmtp"
#"imagemagick"
]Example for initialing mu and mbsync
mkdir -p ~/.mail/$mailname
mu init --maildir=~/.mail --my-address=...@...
mu index
mbsync -c ~/.config/isync/$mbsyncrc -V -a(mu4e +gmail +org)(set-popup-rule! "^\\*mu4e-\\(main\\|headers\\)\\*" :ignore t)(setq mu4e-update-interval 300)
(after! mu4e
(setq mu4e-split-view 'vertical
mu4e-change-filenames-when-moving t
mu4e-attachment-dir "~/Downloads"
;; every new email composition gets its own frame!
mu4e-compose-in-new-frame t
mu4e-use-fancy-chars t))send email by msmtp, see ~/.config/msmtp/config
(after! mu4e
(setq sendmail-program (executable-find "msmtp")
send-mail-function #'smtpmail-send-it
message-sendmail-f-is-evil t
message-sendmail-extra-arguments '("--read-envelope-from")
message-send-mail-function #'message-send-mail-with-sendmail))private variables mu4e-get-mail-command and mu4e-contexts
(after! mu4e
(load-in-doom-dir "private/mu4e.el"))(rss +org)Read your RSS feeds in emacs with elfeed | Pragmatic Emacs
(after! elfeed
(add-hook! 'elfeed-search-mode-hook 'elfeed-update)
(setq elfeed-db-directory (concat (getenv "NCDIR") "elfeed/db/")
elfeed-enclosure-default-dir (concat (getenv "NCDIR") "elfeed/enclosures/")
;; elfeed-search-filter "@1-month-ago +unread"
rmh-elfeed-org-files (list (expand-file-name "elfeed.org" org-directory))))
(map! :leader
(:prefix "o"
:desc "elfeed" "e" #'elfeed))
(after! elfeed-goodies
(setq elfeed-goodies/entry-pane-size 0.5))everywhere[everywhere]
packages=[
"xclip",
"xdotool",
"xorg-xprop",
"xorg-xwininfo"
](package! screenshot
:recipe (:host github :repo "yangsheng6810/screenshot"))(use-package! screenshot
:commands screenshot
:init
(map! (:leader :prefix "o"
:desc "sreenshot within emacs" "S" #'screenshot)))Allow attach the screenshot.
(after! screenshot
(screenshot--def-action
"attach"
(set-process-sentinel
;; NOTE feh not transparent well when shadow is included
(start-process "feh" nil "feh"
"--class=attached_window"
screenshot--tmp-file)
(lambda (process event) (delete-file screenshot--tmp-file))))
(transient-append-suffix 'screenshot-transient '(-1 1)
'("a" "Attach" screenshot-attach)))alias --save convert magick in fish shell to suppress the warning
WARNING: The convert command is deprecated in IMv7, use “magick” instead of “convert” or “magick convert”
Issues
- Region not correctly selected in
vtermbuffer - When select in visual line mode, newline not stripped