Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

608 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Doom Emacs Configuration

Literate and modular Doom Emacs configuration.

Table of contents

About

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 (Arch only)
  • Bootstrap scripts tangled to install.d/
  • Comment a module with C-c ; at heading
One line config goes into * Tangle
Often a declare in init.el

Tangle

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

Helpers

Predefined

(defun +my/is-utility-daemon ()
  "If current session run from daemon called `utility`."
  (equal (daemonp) "utility"))

Windows Librewolf in wsl

Open link with host Librewolf browser

(if (featurep :system 'wsl)
  (setq browse-url-firefox-program "librewolf.exe"))

Shortcuts

(map! :leader
      :desc "Eval expression"       ":"    #'pp-eval-expression
      :desc "M-x"                   ";"    #'execute-extended-command)

Window

(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)

ediff

(add-hook 'ediff-prepare-buffer-hook
          (lambda ()
            (+word-wrap-mode 1)
            (when (derived-mode-p 'org-mode)
              (org-fold-show-all))))

Input

Chinese

[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)))

cns

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))

Completion

corfu

[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

(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))

UI

Extra themes

(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

doom

doom

Themes

restore 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)

Fonts

bold italic underline stride

Set default font size, WSL currently not aware dpi settings in ~/.Xresources while float size makes it work on Linux.

(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)))

doom-dashboard

dashboard
(setq fancy-splash-image (expand-file-name "assets/bitmap_512x.png" doom-user-dir))

modeline

[modeline]
packages=["otf-comicshanns-nerd"]
modeline

custom 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)

keycast

(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

(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

[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
]
unicode

change font by block

To 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

Chinese fonts

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.

(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)

rainbow-mode

(package! rainbow-mode)

Workspace

(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)

projectile

Configuration :: Projectile

(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.

Others

(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)

Editor

whitespace

(whitespace +guess +trim)

parinfer

(with-eval-after-load 'parinfer-rust-mode
  (setq parinfer-rust-check-before-enable nil))

evil

(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

snippets
(setq +snippets-dir
      (expand-file-name "~/Documents/Templates/snippets"))

fold

fold

work 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

Undo

(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))

unfill

;;; 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)

Emacs

dired

(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

[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/directory

tramp

tramp

dirvish/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))

tramp-rpc

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.

  1. Please check if you have GNU ls installed over remote host.
  2. Adjust your `direct-async’ tramp settings, for example:

See (info “(tramp) Improving performance of asynchronous remote processes”) for details.

proxy

(setq url-proxy-services
   `(("no_proxy" . "^\\(localhost\\|10\\..*\\|192\\.168\\..*\\)")
     ("http"  . ,(getenv "ALL_PROXY"))
     ("https" . ,(getenv "ALL_PROXY"))))

Term

ghostel

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.

Setup

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.

Checkers

Spell checker

[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)))

Tools

lookup

[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

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"))

gptel tweaks

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

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)

Others

(after! gptel-quick
  (setq gptel-quick-model 'qwen3:14b)
  (setq gptel-quick-backend (cdr (assoc-string "ollama" gptel--known-backends))))

agent

agent-shell

[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/"))

claude code ide

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 tools

The 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)

MCP server

(package! emcp)
(use-package! emcp
  :after agent-shell
  :config
  (add-hook 'emcp-confirm-mode-hook #'evil-emacs-state))

magit

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

transient

(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/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)

pdf

pdf

default 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)

go-translate

(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))

pacdiff

(package! pacdiff)
(use-package! pacdiff)

tldr

(package! tldr)

Lang

Latex

Setup

[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)))

Format

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)))

Editing

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

[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

Setup

[org]
packages=[
    "xclip",
    "maim",
    "graphviz"
]
(org
 +hugo
 +dragndrop
 +jupyter
 +noter
 +present
 +pandoc
 +pretty
 +roam)

Core

org modern

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)

org appear

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))

agenda

(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))))

Babel

org-babel

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

jupyter

[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

jupyter-ext

(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)

ob-async

Session async have been include in org mode, see how to implement async using built in method.

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"))

Knowledge

org-noter

(after! org-noter
  (org-noter-set-doc-split-fraction '(0.75 . 0.25)))

org-roam

(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")))

zotxt

(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)))

insert arxiv links

(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

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)))

Others

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

[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

[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

[yaml]
packages=["yaml-language-server"]
(yaml +lsp)

wolfram

[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.

ligatures

(after! (wolfram-mode ligature)
  (set-ligatures! 'wolfram-mode
    :and "&&"
    :or "||"
    :not "!"
    :null "None"
    :true "True"
    :false "False"))

lsp-wl with eglot

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.

wolfram in jupyter

(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)

snippets

(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

["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)

kdl

(package! kdl-mode)
(use-package kdl-mode)

Email

mu4e

[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"))

App

rss

(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
[everywhere]
packages=[
    "xclip",
    "xdotool",
    "xorg-xprop",
    "xorg-xwininfo"
]

screenshot

(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

  1. Region not correctly selected in vterm buffer
  2. When select in visual line mode, newline not stripped

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages