Skip to content

fakeGenuis/doom

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 

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 +interpolate)
 (vc-gutter +pretty)
 (window-select +numbers)
 workspaces
 ;zen

 :editor
 <<editor>>
 (format +onsave)
 ;file-templates
 ;multiple-cursors
 parinfer
 word-wrap

 :emacs
 <<emacs>>
 ;electric
 ;ibuffer
 undo
 vc

 :term
 <<term>>

 :checkers
 <<checkers>>
 (syntax +childframe)

 :tools
 <<tools>>
 ;biblio
 debugger
 ;editorconfig
 (eval +overlay)
 tree-sitter
 ;upload
 tree-sitter

 :os
 ;tty

 :lang
 <<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

(setq user-full-name "fakeGenius")

Default install all packages in packages.toml

enable_default=true

30.1 specific

doomemacs/doomemacs#8287 eldoc error: (invalid-function incf)

(package! eldoc :built-in t)
(package! track-changes :built-in t)

Predefined

(defun +my/is-utility-daemon ()
  "If current session run from daemon called `utility`."
  (and (daemonp) (boundp 'server-name) (string= server-name "utility")))
(defun +my/reverse-words (beg end)
  "Reverse the order of words in region."
  (interactive "*r")
  (apply
   'insert
   (reverse
    (split-string
     (delete-and-extract-region beg end) "\\b"))))

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

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

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))))
(defun my-posframe-poshandler-frame-top-center-n-lines (info)
  (let* ((pos (posframe-poshandler-frame-top-center info))
         (y (cdr pos)))
    (cons (car pos)
          (+ y (* 3 (frame-char-height))))))  ;; 3 lines offset
(after! vertico-posframe
  (setq vertico-posframe-poshandler #'my-posframe-poshandler-frame-top-center-n-lines)
  (setq vertico-posframe-width 88))

UI

Extra themes

(package! ewal-doom-themes)
(use-package! ewal-doom-themes)

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))
(custom-set-faces
 ;; quoted text in info
 '(fixed-pitch-serif ((t (:slant italic :foreground "tomato"))))
 ;; prefer italic comment font
 '(font-lock-comment-face ((t (:slant italic)))))

doom-dashboard

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

splash image not loaded in the first frame of daemon mode

;; Refresh the Doom dashboard on the first frame in daemon mode.
(defun +my/load-doom-theme (frame)
  (select-frame frame)
  (load-theme doom-theme t))

(add-hook 'after-make-frame-functions #'+my/load-doom-theme 90)
;; Remove make frame hook to avoid delays when opening a new frame.
(add-hook! 'doom-first-buffer-hook
           (remove-hook 'after-make-frame-functions #'+my/load-doom-theme))

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)

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=[
    "quivira",              # org ellipsis ⤵, ℤ
    "ttf-dejavu",           # org heading ◉ ✸ ∈
    "ttf-sarasa-gothic-sc", #
    "noto-fonts-emoji",     # color emoji
    "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 Gothic 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 "Maple6 SC NF")))
  ;; 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)

Others

(after! nerd-icons
  (setq nerd-icons-scale-factor 0.9))

Transparency

(set-frame-parameter (selected-frame) 'alpha '(85 . 50))
(add-to-list 'default-frame-alist '(alpha . (85 . 50)))

(defun toggle-transparency ()
  (interactive)
  (let ((alpha (frame-parameter nil 'alpha)))
    (set-frame-parameter
     nil 'alpha
     (if (eql (cond ((numberp alpha) alpha)
                    ((numberp (cdr alpha)) (cdr alpha))
                    ;; Also handle undocumented (<active> <inactive>) form.
                    ((numberp (cadr alpha)) (cadr alpha)))
              100)
         '(85 . 50) '(100 . 100)))))

(map! :leader
      (:prefix "t"
       :desc "Toggle transparency"    "T" #'toggle-transparency))

Line numbers

(setq display-line-numbers-type nil)

notify initial time

(defun notify-init-time ()
  (require 'notifications)
  (notifications-notify
   :image-path (expand-file-name "assets/notify.jpg" doom-user-dir)
   :title "Daemon"
   :sound-name "bell"
   :body (format "%s initialed in %0.3fs" server-name doom-init-time)))

;; NOTE Why this keep one workspace in `emacsclient -c'?
(add-hook! 'doom-init-ui-hook
  (if (and (daemonp) (not (+my/is-utility-daemon)))
      (notify-init-time)))

Editor

evil

(evil +everywhere)
(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))
(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)

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

Emacs

dired

(dired +dirvish +icons)
(after! dired
  (setq delete-by-moving-to-trash nil)
  (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))
(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", # include in =base=
    "unzip"
]
(after! dirvish
  (setq dirvish-quick-access-entries
   '(("h" "~/"                          "Home")
     ("d" "~/Downloads/"                "Downloads")
     ("c" "~/.config/"                  "Config")
     ("D" "~/Documents/"                "Documents")
     ("l" "~/lib/"                      "Personal Library")
     ("L" "~/.local/lib/"               "Library")
     ("m" "/mnt/"                       "Mounts")
     ("n" "~/.Nextcloud/"               "Nextcloud")
     ("p" "~/Pictures/"                 "Pictures")
     ("t" "~/.local/share/Trash/files/" "TrashCan")))
  (remove-hook 'dired-mode-hook #'+dired-update-mode-line-height-h))
(after! dirvish
  (setq dirvish-side-width 25)
  (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-tide (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-tide 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)))

alexluigit/dirvish#353 When opening m…

(defun dirvish-pre-redisplay-h (window)
  "Record root WINDOW and redisplay sessions in selected frame."
  (when (eq (frame-selected-window) window)
    (setq dirvish--selected-window (frame-selected-window))
    (when-let* ((dv (dirvish-curr))) (setf (dv-root-window dv) window))
    (dirvish--redisplay)))

Term

vterm

[vterm]
packages=[
    "libvterm",
    "cmake",
    "inetutils" # =hostname= command
]
vterm

have a function to disable close confirmation on terms. work on all terms but…

(defun set-no-process-query-on-exit ()
  (let ((proc (get-buffer-process (current-buffer))))
    (when (processp proc)
      (set-process-query-on-exit-flag proc nil))))

(after! vterm
  (if (+my/is-utility-daemon)
      (add-hook 'vterm-mode-hook #'set-no-process-query-on-exit)))
(defun +my/vterm-switch ()
  "Switch to vterm buffer in `Term' workspace.
If `Term' workspace not exist, create it.
If no vterm buffer in `Term' workspace, create it."
  (interactive)
  (+workspace-switch "Term" t)
  (let ((vterm-buffer
         ;; return first vterm buffer in `Term' workspace
         (catch 'foo
           (dolist (buffer (+workspace-buffer-list))
             (let ((bn (buffer-name buffer)))
               (when (and bn
                          ;; https://stackoverflow.com/a/2238589
                          (with-current-buffer bn
                            (eq major-mode 'vterm-mode)))
                 (throw 'foo bn))))))
        (display-buffer-alist))
    (if vterm-buffer
        (switch-to-buffer vterm-buffer)
      ;; create vterm buffer if not exist
      (+vterm/here t))))

(map! :leader
 :prefix "TAB"
 :desc "Switch to vterm buffer" "v" #'+my/vterm-switch)
(defun +my/vterm-cd-project-root ()
  (interactive)
  (vterm-send-string "cd $PROOT")
  (vterm-send-return))

(after! vterm
  (setq vterm-buffer-name-string "%s - vterm"
        vterm-ignore-blink-cursor nil)
  (map! :leader
        (:prefix "o"
         ;; vterm to current file directory (not project root)
         ;; use `C-Return' to project root
         :desc "Toggle vterm popup" "t" (cmd!! #'+vterm/toggle t)
         :desc "Open vterm here" "T" (cmd!! #'+vterm/here t)))
  ;; TODO fixed-pitch in bpytop like
  ;; (add-hook 'vterm-mode-hook
  ;;           (lambda ()
  ;;             (set (make-local-variable 'buffer-face-mode-face) 'fixed-pitch
  ;;                  (buffer-face-mode t))))
  (define-key vterm-mode-map (kbd "M-q") #'vterm-send-escape)
  (define-key vterm-mode-map [ (control return) ] #'+my/vterm-cd-project-root)
  (dolist (num (number-sequence 0 9))
      (define-key vterm-mode-map (kbd (format "M-%d" num)) nil)))

save vterm buffers with command history

No text properties saved.

(after! persp-mode
  ;; vterm
  (persp-def-buffer-save/load
   :mode 'vterm-mode :tag-symbol 'def-vterm-buffer
   :save-vars '(default-directory)
   :save-function (lambda (buf tag vars)
                    (list tag (buffer-name buf) vars
                          (string-trim-right (buffer-string))))
                          ;; no face and other text properties saved
                          ;; (string-trim-right (buffer-substring-no-properties (point-min) (point-max)))))
   :load-function (lambda (savelist &rest _)
                    (cl-destructuring-bind (_ buf-name vars buf-string) savelist
                      (let ((default-directory (alist-get 'default-directory vars)))
                        (require 'vterm)
                        (with-current-buffer (get-buffer-create buf-name)
                          (insert buf-string)
                          (vterm-mode)))))))

S cd into dirvish current directory

like ranger

(defun dirvish-cd-into-vterm ()
  "Switch into recent vterm buffer, and cd into `default-directory` of dirvish buffer."
  (interactive)
  (let ((cur-dirvish-dir default-directory)
        (vterm-buffer (catch 'foo
                       (dolist (buffer (+workspace-buffer-list))
                         (let ((bn (buffer-name buffer)))
                           (when (and bn
                                      ;; https://stackoverflow.com/a/2238589
                                      (with-current-buffer bn
                                        (eq major-mode 'vterm-mode)))
                             (throw 'foo bn)))))))
    (dirvish-quit)
    (if vterm-buffer
        (let ((cur-vterm-dir (with-current-buffer vterm-buffer
                                  default-directory)))
          (switch-to-buffer vterm-buffer)
          (unless (or (string= cur-vterm-dir cur-dirvish-dir)
                      (not (vterm--safe-send-p)))
            ; NOTE only fish shell support directory jump by dir-name
            ;      add space to ignore command from history
            (vterm-send-string (concat " " (file-relative-name cur-dirvish-dir cur-vterm-dir)))
            (vterm-send-return)))
      (with-temp-buffer (setq-local default-directory cur-dirvish-dir)
                        (+vterm/here t)))))

!!! Just ensure no one type rm -rf before navigate in dirvish.

(defun vterm--safe-send-p ()
  "Tell if current point safe to send string (no input after prompt)."
  (let ((flag (save-excursion
                (vterm-reset-cursor-point)
                (evil-collection-vterm-append)
                (vterm--at-prompt-p))))
    (evil-normal-state)
    flag))

Checkers

Spell checker

[spell]
packages=["aspell", "aspell-en"]
(spell +aspell
       +everywhere)
(after! ispell
  (setq ispell-personal-dictionary
        (expand-file-name ".pws" "~/.Nextcloud/ispell/")))
(setf (alist-get 'org-mode +spell-excluded-faces-alist)
      (append '(org-level-1 font-lock-function-name-face help-key-binding)
              (alist-get 'org-mode +spell-excluded-faces-alist)))

(setf (alist-get 'latex-mode +spell-excluded-faces-alist)
      (append '(font-lock-constant-face tex-math font-lock-comment-face)
              (alist-get 'latex-mode +spell-excluded-faces-alist)))

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

llm

llm

Should always catch up with newest commit.

(unpin! gptel)
(package! gptel :recipe (:nonrecursive t))
(map! (:leader :prefix "o" :desc "gptel" "g" #'gptel))
(map! "C-c g q" #'gptel-quick
      "C-c g m" #'gptel-menu
      "C-c g t" #'gptel-org-set-topic)

Who in their right mind would map enter in org-mode to gptel-send?

(after! gptel
  (evil-define-key 'normal gptel-mode-map (kbd "RET") nil))
(after! gptel
  (setq gptel-default-mode #'org-mode)
  (setq gptel-include-reasoning 'ignore)
  (setq gptel-expert-commands t)
  ;; avoid headings as prompt
  (setf (alist-get 'org-mode gptel-prompt-prefix-alist) "** "
        (alist-get 'org-mode gptel-response-prefix-alist) "*Response:*\n")
  (load-in-doom-dir "private/gpt.el"))
  ;; (add-hook 'gptel-post-stream-hook 'gptel-auto-scroll)
  ;; (add-hook 'gptel-post-response-functions 'gptel-end-of-response))

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)

Ask standalone question anywhere.

(defun +my/gptel-org-make-standalone ()
  "Restrict prompt to system message and things belong previous heading only."
  (when (and (org-back-to-heading t)
             (member "standalone"
                     (mapcar #'substring-no-properties (org-get-tags))))
    (forward-line)
    ;; (org-set-tags nil)
    (delete-region (point-min) (point))))

(add-hook 'gptel-prompt-filter-hook #'+my/gptel-org-make-standalone)

Chat buffer should be real!

(defun gptel-buffer-p (buf)
  (with-current-buffer buf (and (boundp 'gptel-mode) (eq gptel-mode t))))
(add-hook 'doom-real-buffer-functions #'gptel-buffer-p)

gptel-quick

(after! gptel-quick
  (setq gptel-quick-model 'qwen3:14b)
  (setq gptel-quick-backend (cdr (assoc-string "ollama" gptel--known-backends))))
(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
    ;; Exclude end line if starting with '** '
    (let* ((orig-end end))
      (goto-char end)
      (beginning-of-line)
      (when (looking-at "^\\*\\* ")
        ;; Move END to previous line's end (but not before START)
        (forward-line -1)
        (end-of-line)
        (setq end (max start (point))))
      ;; STEP 1: Find minimum heading level in adjusted region
      (let ((min-level nil)
            (heading-re "^\\(\\*+\\) "))
        (goto-char start)
        (while (re-search-forward heading-re end t)
          (let ((level (length (match-string 1))))
            (when (or (null min-level) (< level min-level))
              (setq min-level level))))
        ;; STEP 2: Shift all headings, if needed
        (when (and min-level (< min-level 3))
          (let ((shift (- 3 min-level)))
            (goto-char start)
            (while (re-search-forward heading-re end t)
              (let ((level (length (match-string 1))))
                (replace-match
                 (concat (make-string (+ level shift) ?*) " ")
                 t t)))))))))

(add-hook 'gptel-post-response-functions #'gptel-shift-response-heading-level)
(after! gptel
  (set-popup-rule!
    (lambda (bname _action)
      (and (null gptel-display-buffer-action)
           (buffer-local-value 'gptel-mode (get-buffer bname))))
    :select t
    :side 'right
    :size 88
    :quit 'current
    :ttl nil))

lsp

(lsp +eglot)

magit

magit
(use-package! tramp
  :commands yadm-status
  :init
  (defun yadm-status ()
    (interactive)
    (magit-status "/yadm::"))
  (map! :leader
        (:prefix "g"
         :desc "yadm-status" "a" #'yadm-status))
  :config
  ;; see `man yadm'
  (add-to-list 'tramp-methods
               '("yadm"
                 (tramp-remote-shell "/bin/bash")
                 (tramp-remote-shell-args ("-c"))
                 (tramp-login-program "yadm")
                 (tramp-login-args (("enter"))))))

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

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

Lang

Latex

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

cdlatex

(map! :map cdlatex-mode-map
  :i "TAB" #'cdlatex-tab)

retain .bbl as it required by APS journals. synctex.gz kept to sync tex view.

(after! latex
  (setq LaTeX-clean-intermediate-suffixes
        (seq-difference LaTeX-clean-intermediate-suffixes
                        '("\\.bbl" "\\.synctex\\.gz"))))

add XeTeX mode in TeX/LaTeX

(after! tex
  (add-to-list 'TeX-command-list
               '("XeLaTeX" "%`xelatex%(mode) %(extraopts) %S%(PDFout)%' %t" TeX-run-TeX nil t)))

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

formatting - LaTeXTidy in Emacs - TeX - LaTeX Stack Exchange

lua

[lua]
enabled="not is_wsl"
packages=["lua-language-server"]
(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

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

org modern

Fix function link recognized as footnote.

(use-package org-modern
  :init
  (setq org-modern-footnote nil))
(after! org-modern
  (setq org-modern-star 'replace))

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

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

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]
packages=["jupyter-notebook"]
(package! jupyter
  :pin nil
  :recipe (:host github :repo "fakeGenuis/jupyter"))

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

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 jupyter-org-interaction-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"))

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

others

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

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)))
(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)))
(after! org
  (setq org-archive-location ".bak/%s_archive::")
  ;; always display _{} and ^{}, please
  (setq org-pretty-entities-include-sub-superscripts nil))

Avoid lengthy title

(after! org-cliplink
  (setq org-cliplink-max-length 40))

python

[python]
packages=[
    "python-pytest",
    "python-nose",
    "python-black",
    "python-pyflakes",
    "python-isort",
    "python-pipenv",
    "pyright"
]
(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"))))

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

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)

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

markdown

[markdown]
packages=[
    "pandoc-bin"
]
markdown

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

(defun typst-pdf-tools-preview (&optional buffer)
  "Use `pdf-tools' to preview compiled pdf."
  (interactive)
  (find-file-other-window (typst-ts-compile-get-result-pdf-filename buffer)))

(advice-add 'typst-ts-preview :override #'typst-pdf-tools-preview)

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

Others

css

(prependq! auto-mode-alist '(("\\.rasi\\'" . css-mode)))

defer is not work, same to immediate!

(after! parinfer-rust-mode
  (setq parinfer-rust-check-before-enable 'defer))

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

Other packages

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

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

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

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 "american-physical-society-et-al"))

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

figlet

converting comments into ascii arts

[figlet]
packages=["figlet"]
(package! figlet)
(use-package! figlet
  :defer t
  :config
  (setq figlet-options '("-W" "-f" "script")))

tldr

(package! tldr)
:config

Other configs

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)

tramp

thank you, fish 4.0😄️

(after! tramp
  (add-to-list 'process-environment "SHELL=/bin/bash"))

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`

projectile

Configuration :: Projectile

(after! projectile
  (setq projectile-indexing-method 'alien
        projectile-sort-order 'recently-active
        projectile-file-exists-remote-cache-expire (* 10 60)
        projectile-track-known-projects-automatically nil
        ;; projectile-require-project-root t
        projectile-auto-discover t)
        ;; (projectile-file-exists-local-cache-expire (* 5 60)))
  (add-to-list 'projectile-globally-ignored-directories
               "*\\.run\\.tmp$")
  (pushnew! projectile-globally-ignored-modes
            "helpful-mode" "dired-mode")
  (add-to-list 'projectile-globally-ignored-buffers "*doom*"))

For non git project, better add following in project root .dir-locals.el

((nil . ((projectile-indexing-method . hybrid))))

and add ignored files in .projectile.

proxy

(setq url-proxy-services
   `(("no_proxy" . "^\\(localhost\\|10\\..*\\|192\\.168\\..*\\)")
     ("http" . ,(shell-command-to-string "echo -n $ALL_PROXY"))
     ("https" . ,(shell-command-to-string "echo -n $ALL_PROXY"))))

Windows Librewolf in wsl

Open link with host Librewolf browser

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

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

search online

(add-to-list '+lookup-provider-url-alist '("Inspire" "https://inspirehep.net/literature?q=%s"))

shortcuts

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

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Packages

No packages published

Languages